mcpField
E
external-dns

external-dns

github.com/julianobarbosaクラウド・DevOps14104 回インストール2 日前に更新
リポジトリ

external-dns について

Kubernetes 運用者向けの External-DNS 操作スキルです。Azure と Cloudflare を中心に、複数の DNS プロバイダーの設定、運用指針、トラブルシューティングを扱います。

主な機能

  1. 01Azure、Cloudflare、AWS、Google DNS の設定
  2. 02Service と Ingress のアノテーション設定
  3. 03環境別の設定ガイダンス
  4. 04認証や DNS 同期の問題の調査

活用シーン

  1. 01Kubernetes Service の DNS 自動管理
  2. 02Azure または Cloudflare での External-DNS 導入
  3. 03DNS レコード未同期や権限エラーの診断

インストール

コマンドをコピーしてターミナルで実行します。

$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 クラスターで DNS を自動管理するための External-DNS の包括的な操作ガイドです。

概要

External-DNS は、外部に公開された Kubernetes の Service と Ingress を DNS プロバイダーと同期し、DNS レコードの手動管理を不要にします。このスキルでは、Azure と Cloudflare を中心に、複数の DNS プロバイダーにわたる設定、ベストプラクティス、トラブルシューティングを説明します。

プロバイダーのクイックリファレンス

プロバイダー認証方式状態リファレンス
Azure DNSWorkload Identity(推奨)または Service Principal安定references/azure-dns.md
CloudflareAPI Tokenベータreferences/cloudflare.md
AWS Route53IRSA(推奨)または Access Keys安定以下
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 トークンを Kubernetes Secrets に保存する
  • Pod Security Standards(restricted)を有効にする
  • 本番環境では policy: upsert-only を使用する

リファレンス


注意点

  • txtOwnerId の衝突により、クラスター間で DNS が気付かないうちに破損します: 同じオーナー ID を持つ 2 つのクラスターは、互いのレコードを調整してすべて消してしまいます。必ずクラスター名 + 環境(例: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 認証情報を使用すると、監視対象リソースにイベントが一切出力されず、何も起きないままになります。認証エラーはポッドのログにのみ表示されます。
  • Workload Identity には 1 つではなく 3 つの要素が必要です: ServiceAccount のアノテーション + ポッドのラベル + マネージド ID のフェデレーション資格情報です。フェデレーション資格情報がない場合、トークンの問題に見える ManagedIdentityCredential: 400 が発生しますが、実際には ID バインディングの問題です。
  • domainFilters は完全一致ではなく、プレフィックスで照合します: domainFilters: [example.com] の場合、悪意のある Ingress がそのホスト名を指定すると、evil-example.com も管理対象になります。マルチテナントクラスターでは、--exclude-domains またはより厳格なフィルタリングを使用してください。
  • Azure Private DNS Zone には、パブリックゾーンとは異なるロールが必要です: 「DNS Zone Contributor」はパブリックゾーンでのみ機能します。プライベートゾーンには「Private DNS Zone Contributor」が必要です。誤ったロールを割り当てると、デプロイ時ではなく、最初のレコード同期が実行されたときにのみ 403 が返されます。

コメント