bottlerocket icon indicating copy to clipboard operation
bottlerocket copied to clipboard

Ability to set ndots in /etc/resolv.conf

Open joesteffee opened this issue 2 years ago • 11 comments

What I'd like: The ability to set ndots in /etc/resolv.conf. Similar to how nameservers and search-list is set

The default ndots value of 5 is not useful in the majority of aws k8s deployments.

Any alternatives you've considered: Custom admissions controller to set ndots on every pod in the cluster (why not just do it at the host level? This is far too complicated) Custom userdata script to run echo "ndots: 5" >> /etc/resolv.conf (custom bash userdata not supported?)

joesteffee avatar Oct 20 '23 19:10 joesteffee

Thanks for the request @joesteffee! We'll look into it.

The man page is telling me the default value is 1 - does this value not work well in your setup? (You mentioned 5)

zmrow avatar Oct 20 '23 20:10 zmrow

Thanks for the request @joesteffee! We'll look into it.

The man page is telling me the default value is 1 - does this value not work well in your setup? (You mentioned 5)

AWS EKS defaults this to 5. The default in linux is 1, the default recommended for coredns is 3.

joesteffee avatar Oct 23 '23 18:10 joesteffee

@joesteffee out of curiosity - where does EKS set this?

Starting with aws-k8s-1.28, the distro as a whole has started moving away from wicked towards systemd-networkd/resolved. That means for the short term we are supporting both, but need to be cognizant about taking on new settings that explicitly work for systemd-resolved in the longer term.

I had some time to research this a little bit and came across this systemd issue, and more specifically this comment where @poettering explains the rationale behind systemd-resolved not supporting ndots-like functionality.

I haven't had time to go digging, but I'd be curious what other distros that use systemd-resolved do for this particular setting.

zmrow avatar Oct 24 '23 15:10 zmrow

Regardless of what resolver the host distro uses, kubelet will pass a modified resolv.conf into containers that use the overlay network. If they're using glibc they will understand and respect the ndots option.

As I understand it, this request is to be able to configure kubelet's behavior.

bcressey avatar Oct 24 '23 16:10 bcressey

Regardless of what resolver the host distro uses, kubelet will pass a modified resolv.conf into containers that use the overlay network. If they're using glibc they will understand and respect the ndots option.

As I understand it, this request is to be able to configure kubelet's behavior.

I'm not 100% sure where the configuration is coming from initially (VPC DHCP maybe?) but if you look at /etc/resolv.conf on any EKS node using bottlerocket it has ndots:5 set by default. Its my understanding that the /etc/resolv.conf on the host is inherited when it is injected into pod overlay networks, as modifying the /etc/resolv.conf ndots setting on the host causes the new setting to be present in containers.

We have worked around this issue for now by setting up node-local DNS caching: https://kubernetes.io/docs/tasks/administer-cluster/nodelocaldns/ So, we have a caching DNS server (local-dns) sitting in front of our caching DNS servers (coredns), which then sits in front of our VPC-level caching DNS servers provided by AWS. All because every local domain lookup was driving us over rate limits set by AWS due to all the NXDOMAIN responses that should never have been made to begin with.

Here's a clearer example of what is happening: lookup: github.com causes lookups on:

  • github.com.svc.cluster.local: NXDOMAIN
  • github.com.cluster.local: NXDOMAIN
  • github.com

As you can see, every external domain uses at least 3x as many DNS requests (or more if additional search domains are used) to resolve as is necessary, impacting performance and potentially hitting limits imposed by upstream DNS servers. A more desireable behavior is to try the local resolvers last, as controlled by the ndots setting.

joesteffee avatar Oct 30 '23 17:10 joesteffee

It looks like many people end up using https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config to configure pods to have dnsConfig set with lower ndots if they aren't using . terminated queries. Something like:

spec:
  dnsConfig
    options:
      - name: ndots
        value: "2"
      - name: edns0

This works for each pod but doesn't set it for the entire node. I haven't found anything that let's one set ndots for the node and it seems most use dnsConfig to change this on specific pods. I think most setups configure CoreDNS at the cluster level.

Its my understanding that the /etc/resolv.conf on the host is inherited when it is injected into pod overlay networks, as modifying the /etc/resolv.conf ndots setting on the host causes the new setting to be present in containers.

I don't believe this is how it works on Bottlerocket since ndots isn't set by default on the node, so this appears to be coming in from the cluster or kubelet. I haven't been able to pinpoint this so far but wanted to share what I've found so far.

I do think it would be useful for users to specify these settings since it looks like you can specify a /etc/resolv.conf file explicitly to kubelet with the --resolv-conf flag: https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/. It seems useful to be able to provide a way to override this but I don't believe you can do this today in Bottlerocket.

I did notice that systemd-resolved is called out as a known issue here: https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/#known-issues so we might need to see if that logic is kicking in and causing additional difficulties.

yeazelm avatar Oct 31 '23 03:10 yeazelm

I have also been trying to adjust the ndots setting in the /etc/resolv.conf that the kubelet injects into the containers.

I found the source of the ndots 5 setting in the kubelet code here which is then used here. Unfortunately, the ndots value is hard coded and there isn't a way to override that default value to something lower. It does seem like if we could set the --resolv-conf flag then we could override the default resolv.conf that the kubelet is generating, but it doesn't seem like Bottlerocket supports that. Is there another option for this that is supported?

ClareCat avatar Sep 10 '24 16:09 ClareCat

Thanks for the links to the code @ClareCat! I did some looking and agree that providing --resolv-conf is one way to do this but is deprecated: https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/. I don't actually see a way to do this via kubelet configuration files. From reading the code, I think the providing an empty string would achieve this result but it may stop working in the future if it is currently deprecated. I'll aim to follow up with something more targeted to recommend but it does seem that choosing different DNS configuration for your pods could work too https://github.com/kubernetes/kubernetes/blob/release-1.31/pkg/kubelet/network/dns/dns.go#L440 but I'd have to do more reading of the code to figure out how you might do this.

yeazelm avatar Sep 24 '24 19:09 yeazelm

Any update on this?

We are facing the same issue here. I would love to be able to pass in ndots as a config without asking every dev to change their deployments or writing a mutating admission webhook

adamnoll avatar Feb 25 '25 21:02 adamnoll

KubeletConfiguration does support specifying the ResolveConfig which is the equivalent to --resolve-conf flag see the code. In By default it reads from /etc/resolv.conf.

We could potentially expose an API to pipe through the ndots config to the desired resolv.conf file.

ytsssun avatar Apr 04 '25 19:04 ytsssun

Clarification on ResolveConfig and dnsPolicy

One clarification here is that, per the official doc from k8s, the default value of dnsPolicy is ClusterFirst for pods, which would inject the ndots: 5 value to the pod's resolv.conf. If you use Default value in your pod spec, the resolv.conf will be inherit from the node.

For example, on a testing Bottlerocket node, if I deploy below simple nginx pod:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-ecr-2
  labels:
    app: nginx
spec:
  containers:
  - name: public-ecr
    image: public.ecr.aws/nginx/nginx:mainline
    imagePullPolicy: Always
    ports:
    - containerPort: 80

The clusterFirst dns policy will be used and the /etc/resolv.conf on the pod shows the injected ndots: 5 setting

k exec -it nginx-ecr-2 -- bash
root@nginx-ecr-2:/# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local us-west-2.compute.internal
nameserver 10.100.0.10
options ndots:5                   <-- injected by kubelet

If I explicitly set Default for the dnsPolicy:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-ecr-2
  labels:
    app: nginx
spec:
  containers:
  - name: public-ecr
    image: public.ecr.aws/nginx/nginx:mainline
    imagePullPolicy: Always
    ports:
    - containerPort: 80
  dnsPolicy: Default       <----- Add this

The resolv.conf will be inherited from the host:

k exec -it nginx-ecr-2 -- bash
root@nginx-ecr-2:/# cat /etc/resolv.conf
search us-west-2.compute.internal
nameserver 192.168.0.2

The kubelet config on the node did use ResolvConf already,

bash-5.1# cat /etc/kubernetes/kubelet/config
---
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
...
resolvConf: "/run/netdog/resolv.conf"
...

And the content of the /run/netdog/resolv.conf is exactly what the pod uses:

bash-5.1# cat /run/netdog/resolv.conf
# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).
# Do not edit.
#
......
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 192.168.0.2
search us-west-2.compute.internal

In this case, I do see the potential of using the resolv-conf override. The down side is it would require changing the dnsPolicy of the pod to Default (not ClusterFirst) and you would need to make sure other config like search and nameserver ip is correct, which would introduce other overhead there.


Extended Reading

I found this thread in uptream - https://github.com/kubernetes/kubernetes/issues/127137.

According to this comment, MutatingAdmissionPolicy seems going to be the recommended approach for cluster wise ndots configuration.

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: MutatingAdmissionPolicy
metadata:
  name: default-dns-ndots
spec:
  matchConstraints:
    resourceRules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        resources: ["deployments", "statefulsets", "daemonsets"]
        operations:  ["CREATE", "UPDATE"]
  reinvocationPolicy: IfNeeded
  mutations:
    - patchType: "JSONPatch"
      jsonPatch:
        expression: >
          [
            JSONPatch{
              op: "add",
              path: "/spec/template/spec/dnsConfig",
              value: {
                "options": [
                  {
                    "name": "ndots",
                    "value": "1"
                  }
                ]
               }
            }
          ]
---
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: MutatingAdmissionPolicyBinding
metadata:
  name: default-dns-ndots
spec:
  policyName: default-dns-ndots
  matchResources:

It is worth noting that right now MutatingAdmissionPolicy is an alpha feature in Kubernetes v1.32, it will take some time for it to become available in EKS. It offers a more maintainable solution than custom webhooks or node-level modifications.

ytsssun avatar Apr 04 '25 23:04 ytsssun