external-dns について
Kubernetes 運用者向けの External-DNS 操作スキルです。Azure と Cloudflare を中心に、複数の DNS プロバイダーの設定、運用指針、トラブルシューティングを扱います。
主な機能
- 01Azure、Cloudflare、AWS、Google DNS の設定
- 02Service と Ingress のアノテーション設定
- 03環境別の設定ガイダンス
- 04認証や DNS 同期の問題の調査
活用シーン
- 01Kubernetes Service の DNS 自動管理
- 02Azure または Cloudflare での External-DNS 導入
- 03DNS レコード未同期や権限エラーの診断
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ julianobarbosa/ claude-code-skills --skill external-dnsインストール後、Claude Code で/external-dnsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/azure-dns.mdcloudflare.mdtroubleshooting.md詳細
External-DNS スキル
Kubernetes クラスターで DNS を自動管理するための External-DNS の包括的な操作ガイドです。
概要
External-DNS は、外部に公開された Kubernetes の Service と Ingress を DNS プロバイダーと同期し、DNS レコードの手動管理を不要にします。このスキルでは、Azure と Cloudflare を中心に、複数の DNS プロバイダーにわたる設定、ベストプラクティス、トラブルシューティングを説明します。
プロバイダーのクイックリファレンス
| プロバイダー | 認証方式 | 状態 | リファレンス |
|---|---|---|---|
| Azure DNS | Workload Identity(推奨)または Service Principal | 安定 | references/azure-dns.md |
| Cloudflare | API Token | ベータ | references/cloudflare.md |
| AWS Route53 | IRSA(推奨)または Access Keys | 安定 | 以下 |
| Google Cloud DNS | Workload 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を使用する
リファレンス
references/azure-dns.md- Azure DNS の包括的な設定ガイドreferences/cloudflare.md- Cloudflare の包括的な設定ガイドreferences/troubleshooting.md- よくある問題と解決方法- 公式ドキュメント:https://kubernetes-sigs.github.io/external-dns/
- Helm チャート:https://artifacthub.io/packages/helm/external-dns/external-dns
注意点
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 が返されます。
コメント
Markdown 対応