External DNS: sincronizando DNS com Ingress resources

1. Introdução ao External DNS e seus fundamentos

Gerenciar registros DNS manualmente em ambientes Kubernetes é uma tarefa repetitiva, propensa a erros e que não escala. Cada novo serviço exposto via Ingress exige a criação manual de um registro A, CNAME ou TXT no provedor DNS, além da manutenção contínua quando endpoints mudam ou são removidos.

O External DNS resolve esse problema automatizando a sincronização entre os recursos Ingress do Kubernetes e os registros DNS no provedor externo. A arquitetura é simples: um pod watcher monitora continuamente os recursos Ingress no cluster. Quando um Ingress é criado, atualizado ou removido, o External DNS reflete automaticamente essa mudança no provedor DNS configurado, criando, atualizando ou excluindo os registros correspondentes.

Isso elimina o gargalo operacional e garante que o DNS esteja sempre consistente com o estado real do cluster, permitindo que equipes de DevOps entreguem aplicações com roteamento de tráfego funcional sem intervenção manual.

2. Pré-requisitos e preparação do ambiente

Antes de instalar o External DNS, é necessário:

  • Cluster Kubernetes funcional com acesso ao kubeconfig e permissões de cluster-admin.
  • Ingress Controller instalado (NGINX Ingress Controller, Traefik, Contour, etc.). O External DNS depende dos recursos Ingress para extrair os hostnames.
  • Provedor DNS suportado: AWS Route53, Google Cloud DNS, Azure DNS, Cloudflare, DigitalOcean, entre outros.
  • Credenciais de acesso ao provedor DNS:
  • AWS Route53: IAM Role ou Access Key com permissão route53:ChangeResourceRecordSets e route53:ListHostedZones.
  • Google Cloud DNS: Service Account com papel dns.admin.
  • Cloudflare: Token de API com permissão Zone:Read e DNS:Edit.

Para este artigo, usaremos AWS Route53 como provedor de exemplo.

3. Instalação e configuração do External DNS

A instalação pode ser feita via Helm chart oficial ou via manifestos YAML manuais.

Instalação via Helm

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 --create-namespace \
  --set provider=aws \
  --set aws.zoneType=public \
  --set domainFilters[0]=example.com \
  --set policy=sync \
  --set registry=txt \
  --set txtOwnerId=my-cluster

Instalação via manifestos YAML

Crie um arquivo external-dns.yaml:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-dns
rules:
  - apiGroups: [""]
    resources: ["services", "endpoints", "pods"]
    verbs: ["get", "watch", "list"]
  - apiGroups: ["extensions", "networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "watch", "list"]
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
  - kind: ServiceAccount
    name: external-dns
    namespace: external-dns
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
  namespace: external-dns
spec:
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
        - name: external-dns
          image: registry.k8s.io/external-dns/external-dns:v0.14.2
          args:
            - --source=ingress
            - --provider=aws
            - --domain-filter=example.com
            - --policy=sync
            - --registry=txt
            - --txt-owner-id=my-cluster
            - --aws-zone-type=public
          env:
            - name: AWS_REGION
              value: us-east-1

Aplique o manifesto:

kubectl apply -f external-dns.yaml

4. Sincronizando DNS com Ingress resources

O External DNS monitora automaticamente o campo spec.rules[].host dos recursos Ingress. Quando um Ingress com hostname é criado, o External DNS cria o registro DNS correspondente.

Exemplo prático

Crie um Ingress para uma aplicação de exemplo:

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

Aplique o Ingress:

kubectl apply -f my-app-ingress.yaml

Após alguns segundos, verifique se o registro foi criado no Route53:

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

A saída deve mostrar:

time="2025-04-10T14:30:00Z" level=info msg="Updating DNS records" action=CREATE record=app.example.com type=A

5. Estratégias avançadas de sincronização

TTL customizado

Use a anotação external-dns.alpha.kubernetes.io/ttl para definir o TTL do registro DNS:

metadata:
  annotations:
    external-dns.alpha.kubernetes.io/ttl: "60"

Múltiplos domínios e filtros

Para sincronizar múltiplos domínios, use --domain-filter repetidamente ou --regex-domain-filter:

args:
  - --domain-filter=example.com
  - --domain-filter=test.org

Ou com regex:

args:
  - --regex-domain-filter=.*\.example\.com

Integração com cert-manager para HTTPS

Combine External DNS com cert-manager para provisionamento automático de certificados TLS:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - secure.example.com
      secretName: secure-tls
  rules:
    - host: secure.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: secure-app-service
                port:
                  number: 443

6. Tratamento de conflitos e resolução de problemas

Modo dry-run

Antes de aplicar alterações reais, use --dry-run para validar:

args:
  - --dry-run

O External DNS mostrará o que faria sem efetuar mudanças.

Logs e debugging

Ative logs detalhados:

args:
  - --log-level=debug

Políticas de ownership

  • sync (padrão): o External DNS assume controle total dos registros, removendo registros órfãos.
  • upsert-only: apenas cria e atualiza registros, nunca os remove. Útil para evitar exclusões acidentais.

Configure com:

args:
  - --policy=upsert-only

7. Boas práticas e considerações de segurança

  • Namespaces isolados: instale o External DNS em um namespace dedicado (external-dns).
  • RBAC mínimo: conceda apenas as permissões necessárias (ClusterRole com acesso a ingress, services, endpoints).
  • Credenciais seguras: use secrets do Kubernetes para armazenar tokens de API ou chaves de acesso, nunca em texto plano no Deployment.
  • Monitoramento: exponha métricas Prometheus na porta 7979 e configure alertas para falhas de sincronização:
args:
  - --metrics-address=:7979

8. Cenários avançados e integrações

External DNS com Istio Gateway e VirtualService

Para ambientes service mesh, o External DNS pode sincronizar com recursos Istio:

args:
  - --source=ingress
  - --source=istio-gateway

Sincronização para zonas privadas

Para endpoints internos, configure a zona privada:

args:
  - --aws-zone-type=private

Atualização zero-downtime

Combine health checks do Ingress Controller com o External DNS para garantir que registros DNS só apontem para endpoints saudáveis. Use probes de readiness nos Deployments e configure o Ingress com nginx.ingress.kubernetes.io/service-upstream: "true" para balanceamento adequado.


Referências