DNS v2.0
DNS v2.0
NICo answers DNS for everything it manages. Records are never authored by hand: they derive from the machine, BMC, and instance inventory in the nico-api database, appear when an interface or instance gains an address, and disappear when it loses one. This page covers the names NICo serves, site zones and per-segment subdomains, reverse (PTR) resolution, and IPv6 Service configuration. For the overall DNS deployment, including the recursive resolver and fixed infrastructure service names, refer to IP and Network Configuration.
What Gets a Name
Every managed machine interface gets a hostname; Host Naming covers how that label is chosen. These are the forward records NICo serves:
Records serve as A or AAAA per address family, with a 300-second TTL by default. An interface that loses all of its addresses drops out of DNS until it has one again. Instances on host_inband segments publish no instance record: their address is the host’s own interface address, which the machine records above already serve.
The Site Zone and Segment Subdomains
The site’s forward zone is seeded once, at first startup, from the nico-api config:
The domain is created only when no domain exists yet, and most sites use a single domain for their lifetime. Network segments then decide which of their interfaces and instances get names:
- Segments seeded from the config (
[networks.*]) inherit the site domain automatically. - Segments created later through the API or CLI name their subdomain explicitly:
nico-admin-cli network-segment create ... --subdomain-id <domain-id>(required forhost-inbandsegments; list domains withnico-admin-cli domain show). - A segment with no subdomain produces no DNS at all: its interfaces and instances have no zone to live in.
Reverse DNS
Reverse resolution mirrors the forward records that matter to humans:
- An address on a primary interface or a BMC interface answers PTR with
<hostname>.<domain>. - An overlay instance address answers PTR with its instance name.
- The
admandbmcmachine-id forms are forward-only.
Reverse zones are derived, not managed. When a network segment is created, NICo derives the matching in-addr.arpa / ip6.arpa zone from each of its prefixes (for example, 192.0.2.0/24 becomes 2.0.192.in-addr.arpa) and removes it again when the segment is deleted. Only octet-aligned IPv4 prefixes (/8, /16, /24, /32) and nibble-aligned IPv6 prefixes get a zone; anything else is skipped, since RFC 2317 classless delegation is out of scope. PTR answers themselves are matched by address, so alignment never blocks an answer; the derived zone rows define which reverse zones you delegate.
Like the forward zone, reverse zones must be delegated from your upstream DNS - or forwarded by your recursive resolver - to the nico-dns service address. NICo creates the zones but cannot delegate them for you.
Server Behavior Worth Knowing
nico-dnsforwards A, AAAA, PTR, SOA, NS, CNAME, MX, and TXT queries to the API; any other type, and zone transfers, answer “not implemented”. It never recurses. It publishes no NS or CNAME/MX/TXT records, so those types answer NOERROR with an empty answer section inside a held zone. SOA at a zone apex is answered. Put it behind your recursive resolver (a forward zone works best) rather than in a client’s resolver list.- Positive answers reflect the database live: there is no positive cache and no zone-serial machinery. A new or changed record is visible on the next query for it unless a negative answer for that name and type is cached as described in the next items. In that case, the record appears when the cache entry expires.
- Inside a zone NICo holds, a name that exists without the requested type (for example, only the other address family) answers NOERROR with an empty answer section, and a name that does not exist answers NXDomain; both carry the zone SOA and set the AA bit. A name outside every held zone answers Refused rather than NXDomain, since it may exist elsewhere.
nico-dnscaches the negative answers it gets from the API. A cached answer replays the same response code, AA bit, and SOA.- NOERROR-with-no-data and NXDomain are cached for the shortest of the zone SOA record TTL, the SOA minimum, and
negative_cache_ttl_secs, which defaults to 120 seconds. The SOA TTL in the first answer and in each replay is the time the cache entry has left. - Refused carries no SOA and is cached for
negative_cache_ttl_secs. ServFail from an upstream failure is cached fornegative_cache_servfail_ttl_secs, which defaults to 5 seconds and is clamped to 1–300 seconds. This collapses a retry storm into one upstream call without outliving the recovery. - The “not implemented” answer for an unsupported query type and the FORMERR for an unreadable query are decided before the API is asked and are not cached.
- Hosts learn their own FQDN over DHCP option 12 on every path; hosts served by a DPU also receive it as DHCP option 15.
Resolvers Handed to Hosts
DHCP option 6 tells managed machines where to resolve, and it must point at the recursive resolver, never at nico-dns directly (nico-dns cannot answer external names or the infrastructure service names):
- On the site DHCP path, option 6 comes from the
nico-dhcpKea hook parametercarbide-nameservers(theconfig.kea.hookParameters.nameserversHelm value emits it). - Hosts behind a DPU receive the DPU’s own resolver set.
- The opt-in DHCPv6 deployment advertises IPv6 recursive resolvers in option 23 through the
dhcp.v6DnsServersHelm value.
nico-dns External Services
The nico-dns chart disables external Services by default (externalService.enabled: false). Enabling them creates separate UDP and TCP Services on port 53 for each replica (two replicas by default). These expose authoritative DNS only; they do not replace the recursive resolver handed to hosts.
The optional externalService.ipFamilyPolicy and externalService.ipFamilies values control every replica’s external Services. Their chart defaults are "" and []. Empty or omitted values omit the corresponding Service fields, leaving Kubernetes defaults in effect. For a new Service with both fields omitted, Kubernetes selects SingleStack and the cluster’s default Service address family.
For IPv6-only external Services, use the following umbrella-chart values. Replace the example virtual IP addresses (VIPs) with addresses from your MetalLB pools. When installing the nico-dns chart directly, omit the outer nico-dns: key.
For dual-stack external Services with IPv4 primary, use the following values. RequireDualStack requires cluster support for both families; Kubernetes rejects the Service if dual-stack is unavailable.
Use PreferDualStack when single-stack fallback is acceptable, and leave ipFamilies empty to let the cluster select the available families. Pinned VIPs must match the resulting Service families: on a single-stack cluster, replace each VIP pair with an address from the selected family.
An explicit ipFamilies list contains unique IPv4 or IPv6 entries; SingleStack accepts only one. The first entry is the primary family. Kubernetes permits adding or removing a secondary family, but not changing an existing Service’s primary family in place. Plan Service replacement if you need to change it. Refer to Kubernetes IPv4/IPv6 Dual-Stack for the policy and update rules.
perPodAnnotations[0] applies to both Services for pod nico-dns-0, [1] to both for nico-dns-1, and so on, as described in Assign Service VIPs. Supply an entry for every replica when pinning VIPs. The chart sets a shared-IP annotation so each replica’s UDP and TCP Services can use the same VIPs. Each dual-stack entry requests one IPv4 and one IPv6 VIP. MetalLB needs a pool containing both families for dual-stack allocation; refer to MetalLB IPv6 and Dual-Stack Services.
The nico-dns.listenAddress umbrella value defaults to "[::]:53"; an IPv4-only override such as "0.0.0.0:53" cannot serve IPv6 clients. Matching VIPs and pools alone do not establish connectivity: the cluster network, DNS listeners, load balancer, and client routes must support the selected families. Validate DNS answers over both UDP and TCP from the intended client network before relying on the VIPs.
Unbound IPv6 Transport
The combined nico-unbound Service serves DNS on UDP/TCP 53 and, when exporter.enabled is true (chart default), exporter metrics on TCP 9167. unbound.ipv6.enabled is an optional Boolean with chart default false. An omitted or null map, or an omitted flag, also leaves the Service IPv4-only. true requests PreferDualStack while retaining IPv4 as the primary family. The flag controls this Service’s exposure only. It does not change the image, startup command, mounted configuration, exporter, or the separate external DNS Services.
You supply the Unbound and exporter images. IPv6 enablement requires that Unbound consumes the configuration mounted at /etc/unbound/local.conf.d, listens on IPv6 UDP and TCP 53, retains its existing IPv4 listeners, and permits the intended IPv6 clients. The exporter must separately listen on IPv6 TCP 9167 when enabled. Both containers need the pod’s working IPv6 network. Image tags alone do not establish these capabilities.
Use the image’s existing configuration mechanism. For an image that includes the chart’s fragments and has no listener directives elsewhere, the following umbrella-chart values illustrate the complete DNS listener and access configuration. Replace the IPv6 prefix with the source network Unbound will actually see. Preserve your site’s existing IPv4 listener addresses and access rules. The example repeats the chart’s default IPv4 access rule. Omit listener directives already present elsewhere in the image’s effective configuration, since explicit interfaces accumulate. When installing the Unbound chart directly, omit the outer unbound: key.
This string replaces the existing access_control.conf value. Merge all required site access rules into it, including any source addresses introduced by network address translation (NAT). do-ip6: yes also permits outbound IPv6 DNS traffic. You can still use existing IPv4 forwarders. Apply listener configuration first, restart the resolver through the site’s normal rollout workflow, and verify it before enabling the Service’s IPv6 exposure. Interface changes require a restart. A configuration reload alone is insufficient. The Unbound configuration reference describes listener and access-control semantics.
Configure the separate Unbound External Services for external exposure. An internal dual-stack Service does not provide an external IPv6 resolver VIP for bare-metal hosts.
Before advertising an IPv6 resolver to clients, validate the deployed images from an intended client network:
- Query a configured local name and a name resolved through the existing forwarders over IPv6 UDP and TCP 53. Verify the expected DNS answers. Check the pod first, then the Service’s IPv6 address after enabling exposure.
- Repeat representative DNS queries over IPv4 and confirm the existing IPv4 Service address is retained.
- Fetch the exporter’s metrics through IPv6 and the existing
nico-unbound:9167IPv4 endpoint when the exporter is enabled. - Confirm an IPv4-only installation still upgrades with its existing values. The default-off flag introduces no listener configuration or pod-template change.
DNS transport and record type are independent: either IPv4 or IPv6 transport can carry A or AAAA queries. A TCP readiness connection proves neither DNS answers nor IPv6 Service routing. External IPv6 DNS additionally requires a reachable resolver VIP and advertising it to clients through the site’s DHCPv6 configuration.
Unbound External Services
The Unbound chart disables external Services by default (externalService.enabled: false). Enabling them creates separate UDP and TCP Services on port 53, with LoadBalancer as the default type. Both Services use the same address-family settings and VIP annotations. They expose recursive DNS, not exporter metrics.
Set unbound.externalService.ipFamilies and unbound.externalService.ipFamilyPolicy in the umbrella-chart values to select IPv4, IPv6, or both. Their chart defaults are [IPv4] and SingleStack. An omitted, null, or empty setting uses its chart default; an empty list does not select the cluster’s default family. These settings do not change the combined internal Service or its independent unbound.ipv6.enabled flag.
For IPv6-only external DNS Services, use the following values. Replace the example VIP with an address from your MetalLB pool. When installing the Unbound chart directly, omit the outer unbound: key and its enabled field.
For dual-stack external DNS Services with IPv4 primary, use the following values. RequireDualStack requires both address families; Kubernetes rejects the Service if the cluster does not support both.
The ipFamilies list contains unique IPv4 or IPv6 entries. SingleStack accepts one family; a dual-stack list accepts either order. The first entry is primary. Kubernetes permits adding or removing a secondary family but rejects an in-place primary-family change, causing helm upgrade to fail. Plan Service replacement and an interruption to resolver access when changing the primary family, including a change from IPv4-only to IPv6-only. Refer to Kubernetes IPv4/IPv6 Dual-Stack for policy and update rules.
With PreferDualStack and one preferred family in ipFamilies, Kubernetes adds the other family on a dual-stack cluster. On a single-stack cluster, it uses only the listed family, which the cluster must support. Omit pinned VIP annotations to let MetalLB allocate addresses for the resulting Service families, or supply VIPs that match those families.
The chart adds a shared-IP annotation so the UDP and TCP Services can share the configured VIPs. A pinned dual-stack configuration needs one VIP per family and a MetalLB pool containing both families. Refer to MetalLB IPv6 and Dual-Stack Services. Setup preflight checks supplied VIPs against configured pools and rejects a VIP from the wrong family for SingleStack. Omitting VIP annotations permits automatic allocation.
Configure and verify the Unbound IPv6 Transport listeners and access rules before enabling external IPv6 exposure. These Service settings do not configure pod networking, DNS listeners, client access, or routing. Validate DNS answers over UDP and TCP from the intended client network through each enabled VIP family before advertising the resolver.
Troubleshooting
The following table lists common situations and where to look: