From f1baf5fabe28945e3eb76140aeccca75b695b58b Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Tue, 25 Aug 2026 15:10:06 -0400 Subject: [PATCH] Default calico-node to the node's own DNS resolver calico-node runs before CNI is installed, so cluster DNS is not reachable when it starts. It now inherits the operator's DNS settings only when the operator has an explicit dnsConfig. --- api/v1/calico_node_types.go | 3 +- .../installation/core_controller.go | 13 +--- .../installation/core_controller_test.go | 60 +++++++++++++++++++ .../operator.tigera.io_installations.yaml | 12 ++-- pkg/render/node.go | 2 +- pkg/render/node_test.go | 11 ++++ 6 files changed, 83 insertions(+), 18 deletions(-) diff --git a/api/v1/calico_node_types.go b/api/v1/calico_node_types.go index 3ded162756..b26aab7d47 100644 --- a/api/v1/calico_node_types.go +++ b/api/v1/calico_node_types.go @@ -86,7 +86,8 @@ type CalicoNodeDaemonSetPodSpec struct { // +optional Tolerations []v1.Toleration `json:"tolerations"` - // DNSPolicy is the DNS policy for the calico-node pods. + // DNSPolicy is the DNS policy for the calico-node pods. Defaults to Default, which uses the node's + // own resolver, since calico-node runs before cluster DNS is reachable. // +kubebuilder:validation:Enum="";Default;ClusterFirst;ClusterFirstWithHostNet;None // +optional DNSPolicy *v1.DNSPolicy `json:"dnsPolicy,omitempty"` diff --git a/pkg/controller/installation/core_controller.go b/pkg/controller/installation/core_controller.go index d588182db1..2b195f1968 100644 --- a/pkg/controller/installation/core_controller.go +++ b/pkg/controller/installation/core_controller.go @@ -1441,15 +1441,8 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile goldmaneRunning = goldmaneCR != nil } - // Calico node DNS configuration and policy should be inherited from the tigera/operator Deployment by default since: - // - // - they are both host networked and run prior to CNI being installed (and thus beofre kube-dns is available) - // - they both need access to in-cluster serivces via kube-dns, as well as external services such as the API server. - // - // So, they will require the same DNS configuration. - // - // Users can override this with explicit configuration in the Installation resource, but using the operator as - // a baseline is a reasonable default. + // calico/node runs before CNI is installed, so cluster DNS is not yet reachable on the node. Use the + // node's own resolver, and inherit the operator's DNS settings only when it has an explicit dnsConfig. operatorDeployment := &appsv1.Deployment{} defaultDNSPolicy := corev1.DNSDefault var defaultDNSConfig *corev1.PodDNSConfig @@ -1459,7 +1452,7 @@ func (r *ReconcileInstallation) Reconcile(ctx context.Context, request reconcile return reconcile.Result{}, err } reqLogger.Info("Operator Deployment not found, using default DNS configuration") - } else { + } else if operatorDeployment.Spec.Template.Spec.DNSConfig != nil { defaultDNSPolicy = operatorDeployment.Spec.Template.Spec.DNSPolicy defaultDNSConfig = operatorDeployment.Spec.Template.Spec.DNSConfig } diff --git a/pkg/controller/installation/core_controller_test.go b/pkg/controller/installation/core_controller_test.go index 6e9f6ebae5..250e7506d8 100644 --- a/pkg/controller/installation/core_controller_test.go +++ b/pkg/controller/installation/core_controller_test.go @@ -388,6 +388,66 @@ var _ = Describe("Testing core-controller installation", func() { }) }) + Context("DNS configuration", func() { + operatorDeployment := func(policy corev1.DNSPolicy, dnsConfig *corev1.PodDNSConfig) *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: common.OperatorName(), Namespace: common.OperatorNamespace()}, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{DNSPolicy: policy, DNSConfig: dnsConfig}, + }, + }, + } + } + + nodeDaemonSet := func() appsv1.DaemonSet { + _, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).ShouldNot(HaveOccurred()) + + ds := appsv1.DaemonSet{} + key := types.NamespacedName{Name: common.NodeDaemonSetName, Namespace: common.CalicoNamespace} + Expect(c.Get(ctx, key, &ds)).NotTo(HaveOccurred()) + return ds + } + + It("should use the node's resolver when the operator has no explicit dnsConfig", func() { + Expect(c.Create(ctx, operatorDeployment(corev1.DNSClusterFirstWithHostNet, nil))).NotTo(HaveOccurred()) + + ds := nodeDaemonSet() + Expect(ds.Spec.Template.Spec.DNSPolicy).To(Equal(corev1.DNSDefault)) + Expect(ds.Spec.Template.Spec.DNSConfig).To(BeNil()) + }) + + It("should inherit the operator's DNS settings when it has an explicit dnsConfig", func() { + dnsConfig := &corev1.PodDNSConfig{Nameservers: []string{"10.96.0.10", "169.254.169.253"}} + Expect(c.Create(ctx, operatorDeployment(corev1.DNSNone, dnsConfig))).NotTo(HaveOccurred()) + + ds := nodeDaemonSet() + Expect(ds.Spec.Template.Spec.DNSPolicy).To(Equal(corev1.DNSNone)) + Expect(ds.Spec.Template.Spec.DNSConfig).To(Equal(dnsConfig)) + }) + + It("should use the node's resolver when the operator Deployment is missing", func() { + ds := nodeDaemonSet() + Expect(ds.Spec.Template.Spec.DNSPolicy).To(Equal(corev1.DNSDefault)) + Expect(ds.Spec.Template.Spec.DNSConfig).To(BeNil()) + }) + }) + + It("degrades with a configuration reason when the extension rejects the configuration", func() { + mockStatus.On("SetDegraded", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() + + port := 0 + Expect(c.Create(ctx, &v3.FelixConfiguration{ + ObjectMeta: metav1.ObjectMeta{Name: "default"}, + Spec: v3.FelixConfigurationSpec{PrometheusReporterPort: &port}, + })).NotTo(HaveOccurred()) + + _, err := r.Reconcile(ctx, reconcile.Request{}) + Expect(err).To(HaveOccurred()) + mockStatus.AssertCalled(GinkgoT(), "SetDegraded", operator.InvalidConfigurationError, "invalid metrics port: felixConfiguration prometheusReporterPort=0 not supported", mock.Anything, mock.Anything) + }) + Context("image tests", func() { It("should use builtin images", func() { _, err := r.Reconcile(ctx, reconcile.Request{}) diff --git a/pkg/crds/operator/operator.tigera.io_installations.yaml b/pkg/crds/operator/operator.tigera.io_installations.yaml index ab1bf3d1f4..b95b0386b6 100644 --- a/pkg/crds/operator/operator.tigera.io_installations.yaml +++ b/pkg/crds/operator/operator.tigera.io_installations.yaml @@ -2712,9 +2712,9 @@ spec: x-kubernetes-list-type: atomic type: object dnsPolicy: - description: - DNSPolicy is the DNS policy for the calico-node - pods. + description: |- + DNSPolicy is the DNS policy for the calico-node pods. Defaults to Default, which uses the node's + own resolver, since calico-node runs before cluster DNS is reachable. enum: - "" - Default @@ -11560,9 +11560,9 @@ spec: x-kubernetes-list-type: atomic type: object dnsPolicy: - description: - DNSPolicy is the DNS policy for the - calico-node pods. + description: |- + DNSPolicy is the DNS policy for the calico-node pods. Defaults to Default, which uses the node's + own resolver, since calico-node runs before cluster DNS is reachable. enum: - "" - Default diff --git a/pkg/render/node.go b/pkg/render/node.go index 33879ea49b..721db3cd3d 100644 --- a/pkg/render/node.go +++ b/pkg/render/node.go @@ -155,7 +155,7 @@ type NodeConfiguration struct { func Node(cfg *NodeConfiguration) Component { // Configure default values for any fields that might not be set. if cfg.DefaultDNSPolicy == "" { - cfg.DefaultDNSPolicy = corev1.DNSClusterFirstWithHostNet + cfg.DefaultDNSPolicy = corev1.DNSDefault } return &nodeComponent{cfg: cfg} } diff --git a/pkg/render/node_test.go b/pkg/render/node_test.go index a71ac5d915..f42bda8310 100644 --- a/pkg/render/node_test.go +++ b/pkg/render/node_test.go @@ -3093,6 +3093,17 @@ var _ = Describe("Node rendering tests", func() { }, } + It("should default to the node's own DNS resolver", func() { + component := render.Node(&cfg) + resources, _ := component.Objects() + dsResource := rtest.GetResource(resources, "calico-node", "calico-system", "apps", "v1", "DaemonSet") + Expect(dsResource).ToNot(BeNil()) + + ds := dsResource.(*appsv1.DaemonSet) + Expect(ds.Spec.Template.Spec.DNSPolicy).To(Equal(corev1.DNSDefault)) + Expect(ds.Spec.Template.Spec.DNSConfig).To(BeNil()) + }) + It("should handle calicoNodeDaemonSet overrides", func() { var minReadySeconds int32 = 20