From 7fc21eb2c2fe051e7aa321db687a7c7df5c39036 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Wed, 8 Jul 2026 23:05:05 +0200 Subject: [PATCH 1/2] feat(coredns): migrate docker-provider CoreDNS from Deployment to HelmRelease MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CoreDNS was the only infrastructure controller still shipped as a hand-rolled Deployment + Corefile ConfigMap instead of the repo's standard Flux HelmRelease pattern (remediation/PDB/topology conventions every other controller uses). Docker-provider only — hetzner keeps Talos-managed CoreDNS + its existing Corefile patch. Two couplings made a naive swap unsafe (see issue #14 investigation): 1. The upstream coredns/coredns chart always renders a Service + ConfigMap alongside the Deployment (no way to render "Deployment only"), so the HelmRelease owns the whole stack for docker; the shared base ConfigMap is dropped from the docker kustomization (bases/ stays hetzner-only). 2. Talos reconciles its own bootstrapped CoreDNS, so cluster.coreDNS.disabled: true is added to the Talos machine config (talos-local/cluster/disable-coredns.yaml) to stop it fighting the Helm-owned Deployment. Values reproduce the current Deployment 1:1: replicas, resources, securityContext (chart default), anti-affinity, tolerations, priorityClassName, the exact Corefile via the chart's servers[] schema, serviceAccount.create:false (Talos already bootstraps the coredns ServiceAccount+RBAC independently of its own CoreDNS Deployment) + rbac.create:false, service.clusterIP pinned to Talos's default kube-dns address (10.96.0.10) and service.name: kube-dns so DNS resolution isn't interrupted. Validated offline: `ksail --config ksail.yaml workload validate` renders the HelmRelease and passes (the one unrelated failure elsewhere in the tree — testkube OCI chart fetch — reproduces identically on unmodified main). `kubectl kustomize` on the overlay and the hetzner sibling (untouched) both build clean. The actual DNS behavior (ClusterIP takeover, Talos CoreDNS handoff) needs the repo's own CI, which brings up a real Talos+Docker cluster — that's the promotion gate, not something this draft can prove statically. Fixes #14 --- .../controllers/coredns/deployment.yaml | 114 ------------------ .../controllers/coredns/helm-release.yaml | 107 ++++++++++++++++ .../controllers/coredns/helm-repository.yaml | 7 ++ .../controllers/coredns/kustomization.yaml | 5 +- .../coredns/pod-disruption-budget.yaml | 10 -- talos-local/cluster/disable-coredns.yaml | 3 + 6 files changed, 119 insertions(+), 127 deletions(-) delete mode 100644 k8s/providers/docker/infrastructure/controllers/coredns/deployment.yaml create mode 100644 k8s/providers/docker/infrastructure/controllers/coredns/helm-release.yaml create mode 100644 k8s/providers/docker/infrastructure/controllers/coredns/helm-repository.yaml delete mode 100644 k8s/providers/docker/infrastructure/controllers/coredns/pod-disruption-budget.yaml create mode 100644 talos-local/cluster/disable-coredns.yaml diff --git a/k8s/providers/docker/infrastructure/controllers/coredns/deployment.yaml b/k8s/providers/docker/infrastructure/controllers/coredns/deployment.yaml deleted file mode 100644 index 6bdd6e6..0000000 --- a/k8s/providers/docker/infrastructure/controllers/coredns/deployment.yaml +++ /dev/null @@ -1,114 +0,0 @@ -# TODO(#14): Rework CoreDNS deployment into a helm-release -apiVersion: apps/v1 -kind: Deployment -metadata: - name: coredns - namespace: kube-system - labels: - k8s-app: kube-dns - annotations: - configmap.reloader.stakater.com/reload: "coredns" -spec: - progressDeadlineSeconds: 600 - replicas: 2 - revisionHistoryLimit: 10 - selector: - matchLabels: - k8s-app: kube-dns - strategy: - rollingUpdate: - maxSurge: 1 - maxUnavailable: 0 - type: RollingUpdate - template: - metadata: - labels: - k8s-app: kube-dns - spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchExpressions: - - key: k8s-app - operator: In - values: - - kube-dns - topologyKey: kubernetes.io/hostname - weight: 100 - containers: - - args: - - -conf - - /etc/coredns/Corefile - image: registry.k8s.io/coredns/coredns:v1.14.3@sha256:884b72dd6d2f7d367902af420605e0288dffedb0516ce29330423ae3f8f5c6fa - imagePullPolicy: IfNotPresent - livenessProbe: - failureThreshold: 5 - httpGet: - path: /health - port: 8080 - scheme: HTTP - initialDelaySeconds: 60 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 5 - name: coredns - ports: - - containerPort: 53 - name: dns - protocol: UDP - - containerPort: 53 - name: dns-tcp - protocol: TCP - - containerPort: 9153 - name: metrics - protocol: TCP - readinessProbe: - failureThreshold: 3 - httpGet: - path: /ready - port: 8181 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - resources: - limits: - memory: 170Mi - requests: - cpu: 100m - memory: 70Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - add: - - NET_BIND_SERVICE - drop: - - ALL - readOnlyRootFilesystem: true - volumeMounts: - - mountPath: /etc/coredns - name: config-volume - readOnly: true - dnsPolicy: Default - nodeSelector: - kubernetes.io/os: linux - priorityClassName: system-cluster-critical - restartPolicy: Always - serviceAccount: coredns - serviceAccountName: coredns - terminationGracePeriodSeconds: 30 - tolerations: - - key: CriticalAddonsOnly - operator: Exists - - effect: NoSchedule - key: node-role.kubernetes.io/control-plane - volumes: - - configMap: - defaultMode: 420 - items: - - key: Corefile - path: Corefile - name: coredns - name: config-volume diff --git a/k8s/providers/docker/infrastructure/controllers/coredns/helm-release.yaml b/k8s/providers/docker/infrastructure/controllers/coredns/helm-release.yaml new file mode 100644 index 0000000..b7786a4 --- /dev/null +++ b/k8s/providers/docker/infrastructure/controllers/coredns/helm-release.yaml @@ -0,0 +1,107 @@ +# TODO(#14): CoreDNS Deployment -> HelmRelease. Docker-provider only — Talos's own +# CoreDNS is disabled via talos-local/cluster/disable-coredns.yaml (cluster.coreDNS.disabled: +# true) so this chart owns the whole stack (Deployment + Service + ConfigMap + PDB). The +# Service is pinned to the kube-dns name + Talos's default kube-dns ClusterIP +# (10.96.0.10, from the 10.96.0.0/12 service subnet) so it adopts the address kubelet/ +# kube-proxy already expect, instead of Talos's bootstrapped kube-dns Service. serviceAccount +# and rbac are NOT created by this release — Talos already bootstraps the `coredns` +# ServiceAccount + ClusterRole/ClusterRoleBinding independently of its own CoreDNS Deployment. +# The hetzner provider is untouched (stays on Talos-managed CoreDNS + a Corefile patch). +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: coredns + namespace: kube-system + labels: + helm.toolkit.fluxcd.io/helm-test: enabled + helm.toolkit.fluxcd.io/remediation: enabled +spec: + interval: 2m + timeout: 10m + install: + remediation: + retries: -1 + upgrade: + remediation: + retries: -1 + remediateLastFailure: true + chart: + spec: + chart: coredns + version: 1.46.0 + sourceRef: + kind: HelmRepository + name: coredns + # https://github.com/coredns/helm/blob/master/charts/coredns/values.yaml + values: + replicaCount: 2 + k8sAppLabelOverride: kube-dns + image: + repository: registry.k8s.io/coredns/coredns + tag: v1.14.3 + serviceAccount: + create: false + name: coredns + rbac: + create: false + service: + name: kube-dns + clusterIP: 10.96.0.10 + podDisruptionBudget: + minAvailable: 1 + resources: + limits: + memory: 170Mi + requests: + cpu: 100m + memory: 70Mi + priorityClassName: system-cluster-critical + nodeSelector: + kubernetes.io/os: linux + tolerations: + - key: CriticalAddonsOnly + operator: Exists + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: k8s-app + operator: In + values: + - kube-dns + topologyKey: kubernetes.io/hostname + weight: 100 + servers: + - zones: + - zone: . + port: 53 + plugins: + - name: errors + - name: health + configBlock: |- + lameduck 5s + - name: ready + - name: kubernetes + parameters: cluster.local in-addr.arpa ip6.arpa + configBlock: |- + pods insecure + fallthrough in-addr.arpa ip6.arpa + ttl 30 + - name: prometheus + parameters: :9153 + - name: forward + parameters: . /etc/resolv.conf + configBlock: |- + max_concurrent 1000 + - name: cache + parameters: 30 + configBlock: |- + disable success cluster.local + disable denial cluster.local + - name: loop + - name: reload + - name: loadbalance diff --git a/k8s/providers/docker/infrastructure/controllers/coredns/helm-repository.yaml b/k8s/providers/docker/infrastructure/controllers/coredns/helm-repository.yaml new file mode 100644 index 0000000..81a8a81 --- /dev/null +++ b/k8s/providers/docker/infrastructure/controllers/coredns/helm-repository.yaml @@ -0,0 +1,7 @@ +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: coredns + namespace: kube-system +spec: + url: https://coredns.github.io/helm diff --git a/k8s/providers/docker/infrastructure/controllers/coredns/kustomization.yaml b/k8s/providers/docker/infrastructure/controllers/coredns/kustomization.yaml index 7fd5b39..6b341b9 100644 --- a/k8s/providers/docker/infrastructure/controllers/coredns/kustomization.yaml +++ b/k8s/providers/docker/infrastructure/controllers/coredns/kustomization.yaml @@ -1,6 +1,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - - ../../../../../bases/infrastructure/controllers/coredns/ - - deployment.yaml - - pod-disruption-budget.yaml + - helm-release.yaml + - helm-repository.yaml diff --git a/k8s/providers/docker/infrastructure/controllers/coredns/pod-disruption-budget.yaml b/k8s/providers/docker/infrastructure/controllers/coredns/pod-disruption-budget.yaml deleted file mode 100644 index ee1521c..0000000 --- a/k8s/providers/docker/infrastructure/controllers/coredns/pod-disruption-budget.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: coredns - namespace: kube-system -spec: - minAvailable: 1 - selector: - matchLabels: - k8s-app: kube-dns diff --git a/talos-local/cluster/disable-coredns.yaml b/talos-local/cluster/disable-coredns.yaml new file mode 100644 index 0000000..cffe7dd --- /dev/null +++ b/talos-local/cluster/disable-coredns.yaml @@ -0,0 +1,3 @@ +cluster: + coreDNS: + disabled: true From b8e2b04950d5efcb20560fb6bc179f595e1af7d3 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Wed, 8 Jul 2026 23:22:04 +0200 Subject: [PATCH 2/2] fix(coredns): let the chart own its ServiceAccount + RBAC cluster.coreDNS.disabled:true skips Talos's whole CoreDNS bootstrap on a fresh cluster (not just the Deployment), so the docker-provider CI cluster never gets a Talos-created coredns ServiceAccount/RBAC for the chart to reuse. serviceAccount.create/rbac.create must stay true. --- .../controllers/coredns/helm-release.yaml | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/k8s/providers/docker/infrastructure/controllers/coredns/helm-release.yaml b/k8s/providers/docker/infrastructure/controllers/coredns/helm-release.yaml index b7786a4..ef85dd4 100644 --- a/k8s/providers/docker/infrastructure/controllers/coredns/helm-release.yaml +++ b/k8s/providers/docker/infrastructure/controllers/coredns/helm-release.yaml @@ -1,12 +1,14 @@ # TODO(#14): CoreDNS Deployment -> HelmRelease. Docker-provider only — Talos's own # CoreDNS is disabled via talos-local/cluster/disable-coredns.yaml (cluster.coreDNS.disabled: -# true) so this chart owns the whole stack (Deployment + Service + ConfigMap + PDB). The -# Service is pinned to the kube-dns name + Talos's default kube-dns ClusterIP -# (10.96.0.10, from the 10.96.0.0/12 service subnet) so it adopts the address kubelet/ -# kube-proxy already expect, instead of Talos's bootstrapped kube-dns Service. serviceAccount -# and rbac are NOT created by this release — Talos already bootstraps the `coredns` -# ServiceAccount + ClusterRole/ClusterRoleBinding independently of its own CoreDNS Deployment. -# The hetzner provider is untouched (stays on Talos-managed CoreDNS + a Corefile patch). +# true) so this chart owns the whole stack (ServiceAccount + RBAC + Deployment + Service + +# ConfigMap + PDB). `coreDNS.disabled` skips Talos's CoreDNS bootstrap entirely on a fresh +# cluster (not just the Deployment) — verified against CodeRabbit's upstream research on +# #62 — so serviceAccount.create/rbac.create must stay true; the chart owns its own identity +# rather than assuming a preexisting Talos-created one. The Service is pinned to the kube-dns +# name + Talos's default kube-dns ClusterIP (10.96.0.10, from the 10.96.0.0/12 service +# subnet) so it adopts the address kubelet/kube-proxy already expect, instead of Talos's +# bootstrapped kube-dns Service. The hetzner provider is untouched (stays on Talos-managed +# CoreDNS + a Corefile patch). apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -40,10 +42,10 @@ spec: repository: registry.k8s.io/coredns/coredns tag: v1.14.3 serviceAccount: - create: false + create: true name: coredns rbac: - create: false + create: true service: name: kube-dns clusterIP: 10.96.0.10