返回 Skill 列表
extension
分类: 开发与工程无需 API Key

external-dns

全面指南,用于在Azure DNS、AWS Route53、Cloudflare和Google Cloud DNS中配置、故障排除和实施External-DNS。当在Kubernetes中实现自动DNS管理、配置特定于提供商的身份验证(托管身份、IRSA、API令牌)、解决DNS同步问题、设置安全的生产级external-dns部署、优化性能、避免速率限制或使用ArgoCD实现GitOps模式时,请参考此指南。

person作者: jakexiaohubgithub

External-DNS Skill

Complete External-DNS operations for automatic DNS management in Kubernetes clusters.

Overview

External-DNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers, eliminating manual DNS record management. This skill covers configuration, best practices, and troubleshooting across multiple DNS providers with emphasis on Azure and Cloudflare.

Provider Quick Reference

| Provider | Auth Method | Status | Reference | |----------|-------------|--------|-----------| | Azure DNS | Workload Identity (recommended) or Service Principal | Stable | references/azure-dns.md | | Cloudflare | API Token | Beta | references/cloudflare.md | | AWS Route53 | IRSA (recommended) or Access Keys | Stable | Below | | Google Cloud DNS | Workload Identity | Stable | Below |

Essential Helm Values Structure

# kubernetes-sigs/external-dns chart (v1.18.0+)
fullnameOverride: external-dns

provider:
  name: <provider>  # azure, cloudflare, aws, google

# Sources to watch
sources:
  - service
  - ingress

# Domain restrictions
domainFilters:
  - example.com

# Policy: sync (creates/updates/deletes) or upsert-only (creates/updates only)
policy: upsert-only  # Recommended for production

# Sync interval
interval: "5m"

# TXT record ownership (MUST be unique per cluster)
txtOwnerId: "aks-cluster-name"
txtPrefix: "_externaldns."

# Logging
logLevel: info  # debug, info, warning, error
logFormat: json

# Resources
resources:
  requests:
    memory: "64Mi"
    cpu: "25m"
  limits:
    memory: "128Mi"
    # cpu: REMOVED per best practice (no CPU limits)

# Security context
securityContext:
  runAsNonRoot: true
  runAsUser: 65534
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]

# Prometheus metrics
serviceMonitor:
  enabled: true
  interval: 30s

Azure DNS Configuration

Workload Identity (Recommended)

provider:
  name: azure

serviceAccount:
  labels:
    azure.workload.identity/use: "true"
  annotations:
    azure.workload.identity/client-id: "<MANAGED_IDENTITY_CLIENT_ID>"

podLabels:
  azure.workload.identity/use: "true"

env:
  - name: AZURE_TENANT_ID
    value: "<TENANT_ID>"
  - name: AZURE_SUBSCRIPTION_ID
    value: "<SUBSCRIPTION_ID>"
  - name: AZURE_RESOURCE_GROUP
    value: "<DNS_ZONE_RESOURCE_GROUP>"

domainFilters:
  - example.com

txtOwnerId: "aks-cluster-name"
policy: upsert-only
interval: "5m"

Required Azure RBAC Permissions

# Assign DNS Zone Contributor role to the managed identity
az role assignment create \
  --role "DNS Zone Contributor" \
  --assignee "<MANAGED_IDENTITY_OBJECT_ID>" \
  --scope "/subscriptions/<SUB_ID>/resourceGroups/<RG>/providers/Microsoft.Network/dnszones/<ZONE>"

# For Private DNS Zones
az role assignment create \
  --role "Private DNS Zone Contributor" \
  --assignee "<MANAGED_IDENTITY_OBJECT_ID>" \
  --scope "/subscriptions/<SUB_ID>/resourceGroups/<RG>/providers/Microsoft.Network/privateDnsZones/<ZONE>"

Service Principal Alternative

provider:
  name: azure

env:
  - name: AZURE_TENANT_ID
    value: "<TENANT_ID>"
  - name: AZURE_SUBSCRIPTION_ID
    value: "<SUBSCRIPTION_ID>"
  - name: AZURE_RESOURCE_GROUP
    value: "<DNS_ZONE_RESOURCE_GROUP>"
  - name: AZURE_CLIENT_ID
    valueFrom:
      secretKeyRef:
        name: azure-credentials
        key: client-id
  - name: AZURE_CLIENT_SECRET
    valueFrom:
      secretKeyRef:
        name: azure-credentials
        key: client-secret

Cloudflare Configuration

provider:
  name: cloudflare

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

extraArgs:
  cloudflare-proxied: true  # Enable CDN/DDoS protection
  cloudflare-dns-records-per-page: 5000  # Optimize API calls

domainFilters:
  - example.com

txtOwnerId: "aks-cluster-name"
policy: upsert-only

Cloudflare API Token Permissions

  • Zone:Read - List zones
  • DNS:Edit - Create/update/delete DNS records
  • Zone Resources: All zones or specific zones

AWS Route53 Configuration (IRSA)

provider:
  name: aws

env:
  - name: AWS_DEFAULT_REGION
    value: "us-east-1"

serviceAccount:
  annotations:
    eks.amazonaws.com/role-arn: "arn:aws:iam::<ACCOUNT_ID>:role/external-dns"

extraArgs:
  aws-zone-type: public  # or private
  aws-batch-change-size: 4000

domainFilters:
  - example.com

txtOwnerId: "eks-cluster-name"

Required AWS IAM Policy

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

Google Cloud DNS Configuration

provider:
  name: google

env:
  - name: GOOGLE_PROJECT
    value: "<GCP_PROJECT_ID>"

serviceAccount:
  annotations:
    iam.gke.io/gcp-service-account: "external-dns@<PROJECT_ID>.iam.gserviceaccount.com"

domainFilters:
  - example.com

txtOwnerId: "gke-cluster-name"

Kubernetes Resource Annotations

Basic Usage

# On Service or Ingress
metadata:
  annotations:
    external-dns.alpha.kubernetes.io/hostname: "app.example.com"
    external-dns.alpha.kubernetes.io/ttl: "300"

Multiple Hostnames

metadata:
  annotations:
    external-dns.alpha.kubernetes.io/hostname: "app1.example.com,app2.example.com"

Provider-Specific Annotations

# Cloudflare - disable proxy for specific record
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"

# AWS Route53 - create ALIAS record
external-dns.alpha.kubernetes.io/alias: "true"

# Custom TTL
external-dns.alpha.kubernetes.io/ttl: "60"

Environment-Specific Best Practices

Development

policy: sync  # Auto-delete orphaned records
interval: "1m"  # Fast sync for rapid iteration
logLevel: info
resources:
  requests:
    memory: "50Mi"
    cpu: "10m"
  limits:
    memory: "50Mi"

Production

policy: upsert-only  # NEVER auto-delete
interval: "10m"  # Conservative to reduce API load
logLevel: error  # Minimal logging

# High Availability
replicaCount: 2

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
            - key: app.kubernetes.io/name
              operator: In
              values: [external-dns]
        topologyKey: kubernetes.io/hostname

podDisruptionBudget:
  enabled: true
  minAvailable: 1

priorityClassName: high-priority

Common Commands

# Check external-dns pods
kubectl get pods -n external-dns

# View logs
kubectl logs -n external-dns deployment/external-dns --tail=100 -f

# Check configuration
kubectl get deployment external-dns -n external-dns -o yaml | grep -A20 args

# Verify DNS records (Cloudflare)
dig @1.1.1.1 app.example.com

# Verify DNS records (Azure)
az network dns record-set list -g <RESOURCE_GROUP> -z example.com -o table

# Check TXT ownership records
dig TXT _externaldns.app.example.com

# Force restart
kubectl rollout restart deployment external-dns -n external-dns

# Dry-run mode (add to extraArgs)
extraArgs:
  dry-run: true

Key Metrics

# Total endpoints managed
external_dns_registry_endpoints_total

# Sync errors
external_dns_controller_sync_errors_total

# Last sync timestamp
external_dns_controller_last_sync_timestamp_seconds

# DNS records by type
external_dns_registry_a_records
external_dns_registry_aaaa_records
external_dns_registry_cname_records

ArgoCD ApplicationSet Pattern

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: external-dns
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - cluster: dev
            branch: main
          - cluster: prd
            branch: main
  template:
    metadata:
      name: 'external-dns-{{cluster}}'
    spec:
      project: infrastructure
      sources:
        - chart: external-dns
          repoURL: https://kubernetes-sigs.github.io/external-dns/
          targetRevision: "1.18.0"
          helm:
            releaseName: external-dns
            valueFiles:
              - $values/argo-cd-helm-values/kube-addons/external-dns/{{cluster}}/values.yaml
        - repoURL: https://your-repo.git
          targetRevision: "{{branch}}"
          ref: values
      destination:
        server: '{{url}}'
        namespace: external-dns
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

Security Checklist

  • [ ] Use Workload Identity/IRSA instead of static credentials
  • [ ] Grant least privilege permissions to DNS zones
  • [ ] Set runAsNonRoot: true and readOnlyRootFilesystem: true
  • [ ] Use unique txtOwnerId per cluster
  • [ ] Restrict domainFilters to necessary domains
  • [ ] Store API tokens in Kubernetes Secrets
  • [ ] Enable Pod Security Standards (restricted)
  • [ ] Use policy: upsert-only in production

References