mcpField
E
external-dns

external-dns

github.com/julianobarbosa云与 DevOps14104 次安装2 天前更新
代码仓库

关于 external-dns

面向 Kubernetes 运维人员的 External-DNS 操作技能,提供多 DNS 提供商的配置、最佳实践与故障排查,重点涵盖 Azure 和 Cloudflare。

主要功能

  1. 01配置 Azure、Cloudflare、AWS 和 Google DNS
  2. 02设置服务与 Ingress 注解
  3. 03提供环境配置建议
  4. 04排查身份验证与 DNS 同步问题

适用场景

  1. 01为 Kubernetes 服务配置自动 DNS 管理
  2. 02部署 External-DNS 到 Azure 或 Cloudflare
  3. 03诊断 DNS 记录未同步或权限错误

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/julianobarbosa/claude-code-skills --skill external-dns

安装后在 Claude Code 中输入/external-dns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
azure-dns.md
cloudflare.md
troubleshooting.md

详细介绍

External-DNS 技能

在 Kubernetes 集群中执行完整的 External-DNS 操作,实现 DNS 自动管理。

概述

External-DNS 会将对外暴露的 Kubernetes Service 和 Ingress 与 DNS 提供商同步,从而免去手动管理 DNS 记录。本技能涵盖多个 DNS 提供商的配置、最佳实践和故障排除,重点介绍 Azure 和 Cloudflare。

提供商快速参考

提供商身份验证方法状态参考资料
Azure DNSWorkload Identity(推荐)或 Service Principal稳定references/azure-dns.md
CloudflareAPI Token测试版references/cloudflare.md
AWS Route53IRSA(推荐)或访问密钥稳定下文
Google Cloud DNSWorkload Identity稳定下文

Helm Values 基本结构

# 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 配置

Workload Identity(推荐)
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"
Azure RBAC 所需权限
# 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 替代方案
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 配置

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 权限
  • Zone:Read - 列出区域
  • DNS:Edit - 创建/更新/删除 DNS 记录
  • Zone Resources:所有区域或指定区域

AWS Route53 配置(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"
AWS IAM 策略要求
{
  "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 配置

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 资源注解

基本用法
# On Service or Ingress
metadata:
  annotations:
    external-dns.alpha.kubernetes.io/hostname: "app.example.com"
    external-dns.alpha.kubernetes.io/ttl: "300"
多个主机名
metadata:
  annotations:
    external-dns.alpha.kubernetes.io/hostname: "app1.example.com,app2.example.com"
提供商专属注解
# 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"

特定环境的最佳实践

开发环境
policy: sync  # Auto-delete orphaned records
interval: "1m"  # Fast sync for rapid iteration
logLevel: info
resources:
  requests:
    memory: "50Mi"
    cpu: "10m"
  limits:
    memory: "50Mi"
生产环境
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

常用命令

# 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

关键指标

# 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 模式

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

安全检查清单

  • 使用 Workload Identity/IRSA,而非静态凭据
  • 为 DNS 区域授予最小权限
  • 设置 runAsNonRoot: true 和 readOnlyRootFilesystem: true
  • 为每个集群使用唯一的 txtOwnerId
  • 将 domainFilters 限制为必要的域名
  • 将 API Token 存储在 Kubernetes Secrets 中
  • 启用 Pod Security Standards(restricted)
  • 在生产环境中使用 policy: upsert-only

参考资料


常见陷阱

  • 不同集群的 txtOwnerId 冲突会静默破坏 DNS: 两个集群使用相同的所有者 ID 时,会在协调过程中互相覆盖对方的记录,直至记录全部消失。务必使用“集群名称 + 环境”(例如 aks-example-app-prd),并使用 dig TXT _externaldns.<host> 验证。
  • 当记录名称符合匹配模式时,policy: sync 会删除并非由 External-DNS 创建的记录: 如果手动创建的 A 记录与受管理的主机名匹配,它将在下一次协调时被删除。生产环境必须使用 upsert-only;只有开发集群才使用 sync。
  • K8s 侧的 RBAC 和 DNS 提供商凭据缺一不可: External-DNS 既需要读取 Ingress/Service 对象,也需要写入 DNS 区域的权限。只读 DNS 凭据会导致操作静默无效,并且被监视的资源不会生成任何事件——只有 Pod 日志会显示身份验证错误。
  • Workload Identity 需要三项配置,而不是一项: ServiceAccount 注解 + Pod 标签 + 托管身份上的联合凭据。缺少联合凭据时会出现 ManagedIdentityCredential: 400 错误,看起来像是令牌问题,实际却是身份绑定问题。
  • domainFilters 使用前缀匹配,而非精确匹配: 如果恶意 Ingress 声明了该主机名,domainFilters: [example.com] 也会管理 evil-example.com。在多租户集群上使用 --exclude-domains 或更严格的过滤规则。
  • Azure Private DNS Zone 需要不同于公共 DNS 区域的角色: “DNS Zone Contributor” 仅适用于公共 DNS 区域;私有 DNS 区域需要“Private DNS Zone Contributor”。如果分配了错误的角色,只有在首次触发记录同步时才会返回 403,而不是在部署时。

评论

支持 Markdown