Veröffentlicht am

external-dns: Automatische DNS-Einträge für Kubernetes

Teilen:
Authors

TL;DR

external-dns beobachtet Ingress-Ressourcen und Services in deinem Kubernetes-Cluster und erstellt automatisch DNS-Einträge beim DNS-Provider. Kein manuelles Pflegen von DNS-Zonen mehr. Dieses Tutorial zeigt die Einrichtung mit Cloudflare und AWS Route53.


external-dns: Nie wieder manuell DNS pflegen

Neuen Ingress erstellt, DNS-Eintrag vergessen — kennst du das? external-dns macht Schluss damit. Das Tool überwacht deinen Cluster und synchronisiert Hostnamen aus Ingress- und Service-Annotationen direkt mit deinem DNS-Provider.

Der Ablauf ist simpel:

Ingress mit host: app.example.com
external-dns erkennt neuen Host
DNS A-Record wird automatisch erstellt
app.example.comLoadBalancer-IP

Voraussetzungen

  • Kubernetes-Cluster mit Ingress-Controller (z.B. nginx-ingress)
  • DNS-Zone bei einem unterstützten Provider (Cloudflare, Route53, Google Cloud DNS, Azure DNS, u.v.m.)
  • API-Token/-Key für den DNS-Provider
  • Helm v3
kubectl get nodes
helm version --short

Installation mit Cloudflare

Cloudflare ist einer der beliebtesten DNS-Provider. Hier die komplette Einrichtung.

API-Token erstellen

Erstelle in Cloudflare unter "My Profile" > "API Tokens" einen Token mit diesen Berechtigungen:

  • Zone / DNS / Edit
  • Zone / Zone / Read

Beschränke den Token auf die Zonen, die external-dns verwalten soll.

Secret mit API-Token anlegen

kubectl create namespace external-dns

kubectl create secret generic cloudflare-api-token \
  --namespace external-dns \
  --from-literal=cf-api-token=DEIN-CLOUDFLARE-API-TOKEN

Helm-Installation

helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
helm repo update

helm install external-dns external-dns/external-dns \
  --namespace external-dns \
  --set provider.name=cloudflare \
  --set env[0].name=CF_API_TOKEN \
  --set env[0].valueFrom.secretKeyRef.name=cloudflare-api-token \
  --set env[0].valueFrom.secretKeyRef.key=cf-api-token \
  --set policy=sync \
  --set txtOwnerId=k8s-cluster-01 \
  --set domainFilters[0]=example.com

Prüfe ob der Pod läuft:

kubectl get pods -n external-dns
kubectl logs -n external-dns deployment/external-dns

Values-Datei für Cloudflare

Für mehr Kontrolle nutze eine Values-Datei:

# values-external-dns-cloudflare.yaml
provider:
  name: cloudflare

env:
  - name: CF_API_TOKEN
    valueFrom:
      secretKeyRef:
        name: cloudflare-api-token
        key: cf-api-token

# Nur diese Domains verwalten
domainFilters:
  - example.com
  - staging.example.com

# sync = Einträge erstellen, updaten UND löschen
# upsert-only = nur erstellen/updaten, nie löschen
policy: sync

# Identifier, damit mehrere Cluster sich nicht gegenseitig überschreiben
txtOwnerId: "k8s-cluster-01"

# Wie oft external-dns nach Änderungen schaut
interval: 1m

# Nur Ingress-Ressourcen beobachten (nicht Services)
sources:
  - ingress

# Log-Level für Debugging
logLevel: info
helm upgrade --install external-dns external-dns/external-dns \
  --namespace external-dns \
  -f values-external-dns-cloudflare.yaml

Installation mit AWS Route53

Für AWS brauchst du IAM-Berechtigungen statt eines API-Tokens.

IAM Policy erstellen

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets"
      ],
      "Resource": [
        "arn:aws:route53:::hostedzone/DEINE-HOSTED-ZONE-ID"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets",
        "route53:ListTagsForResource"
      ],
      "Resource": ["*"]
    }
  ]
}

Option A: IRSA (EKS — empfohlen)

Wenn du EKS mit IRSA nutzt:

# ServiceAccount mit IAM-Role verknüpfen
eksctl create iamserviceaccount \
  --name external-dns \
  --namespace external-dns \
  --cluster dein-cluster-name \
  --attach-policy-arn arn:aws:iam::ACCOUNT-ID:policy/ExternalDNSPolicy \
  --approve

Option B: Secret mit AWS Credentials

kubectl create secret generic aws-credentials \
  --namespace external-dns \
  --from-literal=aws_access_key_id=AKIAXXXXXXXX \
  --from-literal=aws_secret_access_key=DEIN-SECRET-KEY

Helm Values für Route53

# values-external-dns-route53.yaml
provider:
  name: aws

env:
  - name: AWS_DEFAULT_REGION
    value: eu-central-1
  # Nur bei Option B (Secret):
  - name: AWS_ACCESS_KEY_ID
    valueFrom:
      secretKeyRef:
        name: aws-credentials
        key: aws_access_key_id
  - name: AWS_SECRET_ACCESS_KEY
    valueFrom:
      secretKeyRef:
        name: aws-credentials
        key: aws_secret_access_key

# Bei IRSA: ServiceAccount ist bereits erstellt
serviceAccount:
  create: false    # true bei Option B
  name: external-dns

domainFilters:
  - example.com

policy: sync
txtOwnerId: "k8s-cluster-01"
sources:
  - ingress
  - service
helm upgrade --install external-dns external-dns/external-dns \
  --namespace external-dns \
  -f values-external-dns-route53.yaml

Testen: Ingress mit automatischem DNS

Jetzt der spannende Teil — deploye einen Ingress und beobachte, wie der DNS-Eintrag erscheint:

# test-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-app
  namespace: default
  annotations:
    # Optional: external-dns spezifische Annotationen
    external-dns.alpha.kubernetes.io/hostname: test.example.com
    external-dns.alpha.kubernetes.io/ttl: "300"
spec:
  ingressClassName: nginx
  rules:
    - host: test.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: test-app
                port:
                  number: 80
kubectl apply -f test-ingress.yaml

# Logs beobachten — external-dns sollte den Record erstellen
kubectl logs -n external-dns deployment/external-dns -f

Nach 1-2 Minuten (je nach interval-Einstellung) sollte in den Logs stehen:

level=info msg="Desired change: CREATE test.example.com A"
level=info msg="Desired change: CREATE test.example.com TXT"

Prüfe den DNS-Eintrag:

dig test.example.com +short
# oder
nslookup test.example.com

DNS für Services (LoadBalancer)

external-dns kann auch Services vom Typ LoadBalancer überwachen:

apiVersion: v1
kind: Service
metadata:
  name: my-api
  namespace: production
  annotations:
    external-dns.alpha.kubernetes.io/hostname: api.example.com
    external-dns.alpha.kubernetes.io/ttl: "120"
spec:
  type: LoadBalancer
  selector:
    app: my-api
  ports:
    - port: 443
      targetPort: 8443

Damit das funktioniert, muss service in der sources-Liste stehen:

sources:
  - ingress
  - service

Mehrere Cluster, eine DNS-Zone

Wenn mehrere Cluster DNS-Einträge in derselben Zone verwalten, ist der txtOwnerId entscheidend. Jeder Cluster braucht eine eindeutige ID:

ClustertxtOwnerIdBeschreibung
Productionk8s-prod-01Verwaltet *.example.com
Stagingk8s-staging-01Verwaltet *.staging.example.com

external-dns erstellt TXT-Records als "Ownership-Marker". Cluster A löscht keine Records, die Cluster B erstellt hat.

Troubleshooting

Keine DNS-Einträge werden erstellt:

# 1. Logs prüfen
kubectl logs -n external-dns deployment/external-dns --tail=100

# 2. Häufigste Ursachen:
# - API-Token hat nicht die richtigen Berechtigungen
# - domainFilter passt nicht zum Ingress-Hostname
# - source: ingress fehlt in der Config

# 3. Dry-Run testen
helm upgrade external-dns external-dns/external-dns \
  --namespace external-dns \
  -f values-external-dns-cloudflare.yaml \
  --set dryRun=true

Records werden nicht gelöscht:

Prüfe, ob policy: sync gesetzt ist. Bei upsert-only löscht external-dns keine Records. Außerdem: Records ohne passenden TXT-Ownership-Record werden nicht angefasst.

FAQ

Welche DNS-Provider werden unterstützt?

Über 30 Provider: Cloudflare, AWS Route53, Google Cloud DNS, Azure DNS, DigitalOcean, Hetzner DNS, Pi-hole und viele mehr. Die vollständige Liste findest du in der external-dns-Dokumentation auf GitHub.

Was passiert, wenn ich einen Ingress lösche?

Bei policy: sync löscht external-dns den zugehörigen DNS-Eintrag automatisch. Bei policy: upsert-only bleibt der Eintrag bestehen.

Kann external-dns auch CNAME-Records erstellen?

Ja. Bei Cloud-LoadBalancern, die einen Hostnamen statt einer IP zurückgeben (z.B. AWS ELB), erstellt external-dns automatisch CNAME-Records statt A-Records.

Wie verhindere ich, dass external-dns bestimmte Einträge ändert?

Nutze --exclude-domains oder setze die Annotation external-dns.alpha.kubernetes.io/exclude=true am Ingress. Oder beschränke die domainFilters auf eine Subdomain.

Ist external-dns sicher? Was wenn jemand einen falschen Ingress erstellt?

external-dns selbst hat keine Authentifizierung. Schütze dich mit RBAC: Beschränke, wer Ingress-Ressourcen erstellen darf, und nutze domainFilters, um nur bestimmte Domains zu erlauben.


Nächster Schritt: Kombiniere external-dns mit cert-manager für automatische TLS-Zertifikate. Oder richte Velero-Backups ein, damit deine DNS-Konfiguration bei einem Cluster-Verlust schnell wiederhergestellt werden kann.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen