Kubernetes ExternalDNS:自动化管理公网 DNS 的必备工具
English | 中文
在 Kubernetes 生产环境中,将服务暴露到公网是常态。我们通常通过 Ingress
或 LoadBalancer
类型的 Service
实现对外访问。然而,每当上线新服务或变更域名时,手动在云厂商 DNS 控制台添加 A 记录不仅效率低下,还容易出错。
有没有一种方式,能让 DNS 记录随着服务部署自动创建、更新和清理?答案就是:ExternalDNS。
🔗 GitHub 项目地址:https://github.com/kubernetes-sigs/external-dns
📚 官方文档:https://external-dns.github.io/
ExternalDNS 是由 Kubernetes SIGs 维护的开源控制器,它能自动将集群内的 Service 和 Ingress 资源同步到外部 DNS 系统(如 AWS Route 53、Cloudflare等),实现“服务即上线,域名即生效”的自动化流程。
ExternalDNS 是什么?
ExternalDNS 是一个运行在 Kubernetes 集群中的控制器,它持续监听以下资源:
Ingress
资源(最常用)Service
(类型为LoadBalancer
)Gateway
(支持 Kubernetes Gateway API)
当这些资源发生变化时,ExternalDNS 会提取其域名信息,并调用外部 DNS 提供商的 API,自动创建、更新或删除对应的 DNS 记录(A、CNAME、TXT 等)。
核心能力
- ✅ 自动同步 DNS 记录
- ✅ 支持主流云厂商和第三方 DNS 服务商
- ✅ 支持多集群、多租户隔离
- ✅ 基于注解(Annotations)灵活控制
- ✅ 可与 GitOps 流程无缝集成
工作原理
ExternalDNS 的核心流程如下:
- 监听 Kubernetes 中的
Ingress
和Service
资源 - 解析资源中的域名注解(如
external-dns.alpha.kubernetes.io/hostname
) - 获取服务的公网 IP(来自 LoadBalancer 或 Ingress Controller)
- 调用 DNS 提供商 API 创建/更新 A 记录
- 同时创建一条 TXT 记录用于所有权标识(防止冲突)
- 资源删除时自动清理 DNS 记录
graph LR
A[Ingress / Service] --> B(ExternalDNS Controller)
B --> C[Cloud DNS Provider]
C --> D[Public DNS Resolution]
快速上手:以 AWS Route 53 为例
1. 安装 ExternalDNS(Helm)
我们推荐使用 Helm 安装,便于版本管理和配置维护。
✅ 明确版本信息(2025年9月最新稳定版)
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
helm install external-dns external-dns/external-dns \
--version 1.19.0 \ # 指定 Chart 版本(对应 appVersion v1.19.0)
--namespace kube-system \
--set provider.name=aws \
--set aws.region=us-west-2 \
--set txtOwnerId=my-cluster \
--set domainFilters[0]=example.com \
--set logLevel=info \
--set policy=upsert-only # 推荐生产环境使用
📌 版本说明:
- Helm Chart
1.19.0
对应 ExternalDNS 应用版本 v1.19.0 - Kubernetes 版本建议:v1.22+
- 更多版本信息见:Helm Chart Releases
2. 配置 AWS IAM 权限
ExternalDNS 需要权限操作 Route 53。若权限不足,将无法创建 DNS 记录。
推荐 IAM Policy 示例:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets"
],
"Resource": "arn:aws:route53:::hostedzone/YOUR_HOSTED_ZONE_ID"
},
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": "*"
}
]
}
🔐 安全建议:
- 尽量使用 IAM Role 绑定给节点或使用 IRSA(IAM Roles for Service Accounts)
- 避免使用长期 AccessKey
ChangeResourceRecordSets
是核心写权限,必须授予ListHostedZones
用于查找匹配的托管区域
🔒 生产环境推荐配置:
policy=upsert-only
默认行为
sync
会在资源删除时移除 DNS 记录。但在多租户或混合管理场景中,可能误删非本集群创建的记录。使用
--set policy=upsert-only
后,ExternalDNS 只创建或更新记录,从不删除,极大降低误操作风险。删除操作需由其他机制(如 CI/CD 清理 Job)或人工完成。
3. 部署示例 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
kubernetes.io/ingress.class: "nginx"
external-dns.alpha.kubernetes.io/hostname: "app.example.com"
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
部署后,ExternalDNS 将自动:
- 获取 Nginx Ingress Controller 的 LoadBalancer IP
- 在
example.com
托管区创建app.example.com
的 A 记录 - 在
app.example.com
同名位置创建一条 TXT 记录,内容包含:"heritage=external-dns,external-dns/owner=my-cluster,external-dns/resource=ingress/my-app"
深入理解:TXT 记录的作用
你可能注意到 ExternalDNS 会为每条 DNS 记录创建一个对应的 TXT 记录。它的作用是:
🔐 所有权标识与防冲突机制
当多个 ExternalDNS 实例(如多集群、多环境)管理同一个域名时,TXT 记录中的 txtOwnerId
字段用于标识该 DNS 记录的归属。ExternalDNS 在删除或更新记录前会先检查 TXT 记录是否匹配自己的 txtOwnerId
,避免误删其他实例创建的记录。
📌 因此,在多集群场景下,务必为每个集群设置不同的 txtOwnerId
,例如:
--set txtOwnerId=prod-cluster
--set txtOwnerId=staging-cluster
支持的资源与 DNS 提供商
支持的资源类型
资源 | 说明 |
---|---|
Ingress |
最常用,适合 HTTP/HTTPS 服务 |
Service (LoadBalancer) |
直接暴露服务 IP,适合 TCP/UDP 服务 |
Gateway (Kubernetes Gateway API) |
支持新一代网关标准 |
支持的 DNS 提供商
服务商 | 支持情况 |
---|---|
AWS Route 53 | ✅ |
Google Cloud DNS | ✅ |
Azure DNS | ✅ |
Cloudflare | ✅ |
DigitalOcean | ✅ |
Alibaba Cloud | ✅ |
CoreDNS (自建) | ✅ |
PowerDNS | ✅ |
只需通过 --set provider=xxx
切换即可。
高级用法与最佳实践
1. 多域名支持
external-dns.alpha.kubernetes.io/hostname: "web.example.com,api.example.com"
2. 自定义 TTL
external-dns.alpha.kubernetes.io/ttl: "60"
3. 忽略某些资源
external-dns.alpha.kubernetes.io/ignore: "true"
4. 手动指定目标(target)
适用于非 LoadBalancer 场景:
external-dns.alpha.kubernetes.io/target: "1.2.3.4"
⚠️ 注意:
- 技术上可以把 A 记录指向内网 IP(如 10.x.x.x),但公网客户端无法访问,通常无效
- 此功能更适用于 CNAME 或私有 DNS 场景
- 已知问题:在部分版本/配置下,如果
policy=sync
,target
注解可能被忽略;建议搭配policy=upsert-only
使用,更稳健
适用与不适用场景
场景 | 是否适用 | 说明 |
---|---|---|
✅ 公有云环境(AWS/GCP/Aliyun) | ✔️ | 最佳实践场景 |
✅ CI/CD 自动化部署 | ✔️ | 每次发布自动绑定域名 |
✅ 多环境(dev/stg/prod) | ✔️ | 配合 domainFilter 实现隔离 |
❌ 纯内网环境 | ⚠️ | 可用但需私有 DNS 支持(如 CoreDNS + 自建) |
❌ 域名由运维团队统一手工管理 | ❌ | 可能与现有流程冲突,需谨慎评估 |
调试与排错
1. 查看日志
kubectl logs -n kube-system deployment/external-dns
启用 Debug 日志获取更详细输出:
--set logLevel=debug
输出示例:
time="2025-04-05T10:00:00Z" level=debug msg="Adding DNS record: app.example.com -> 203.0.113.10"
time="2025-04-05T10:00:01Z" level=info msg="Desired change: CREATE app.example.com A [Id: /hostedzone/Z12345]"
2. Dry-run 模式测试
首次部署建议使用 --set dryRun=true
,–dry-run 为控制器级别的只演示模式,预览将要执行的操作而不实际修改 DNS。
3. 监控与可观测性(Prometheus Metrics)
ExternalDNS 内置 Prometheus 指标,暴露在 /metrics
端点(默认端口 7979
,可通过 --metrics-address
修改),可用于监控 DNS 同步状态、错误率和记录数量。
ExternalDNS 官方 FAQ 提供的常见指标:
指标 | 说明 |
---|---|
external_dns_controller_last_sync_timestamp_seconds |
上一次成功与 DNS 提供商同步的时间戳(Unix 时间) |
external_dns_registry_endpoints_total |
Registry 中管理的 DNS 记录数量(ExternalDNS 认为应当存在的目标状态) |
external_dns_registry_errors_total |
与 DNS 提供商交互时发生的错误数(如 API 调用失败) |
external_dns_source_endpoints_total |
从 Kubernetes 资源(Ingress/Service 等)读取到的记录数量(期望状态) |
external_dns_source_errors_total |
读取 Kubernetes 资源时的错误数(如 API 访问失败) |
external_dns_controller_verified_records |
已在 DNS 提供商处验证存在的记录数量(源与目标一致) |
external_dns_registry_a_records |
Registry 中的 A 记录数量 |
external_dns_source_a_records |
Kubernetes 集群声明的 A 记录数量 |
📊 建议:
- 在 Prometheus 中配置抓取任务,目标为
http://<external-dns-pod>:7979/metrics
- 使用 Grafana 构建监控面板,重点关注:
registry_errors_total
与source_errors_total
的变化趋势last_sync_timestamp_seconds
是否持续更新verified_records
与source_a_records
是否大致相等(表示同步正常)
Edge Cases 与注意事项
1. 泛域名支持
external-dns.alpha.kubernetes.io/hostname: "*.example.com"
✅ 大部分 DNS 提供商支持
⚠️ 但部分服务商(如 Cloudflare)对二级泛解析有限制(如不支持 *.staging.example.com
)
2. Target 指向内网 IP
external-dns.alpha.kubernetes.io/target: "10.0.0.1"
⚠️ 公网 DNS 虽然技术上允许将 A 记录指向私有 IP 地址,但对公网客户端无效
✅ 仅在私有 DNS 或内部解析场景中有意义(如 VPC 内部 CoreDNS)
总结
ExternalDNS 是 Kubernetes 环境中实现 DNS 自动化管理的关键组件。它通过声明式配置,将服务暴露与域名管理解耦,极大提升了 DevOps 效率。
核心价值
- 🚀 自动化:服务上线 = 域名生效
- 🔐 安全:通过 TXT 记录防止冲突,
upsert-only
策略防误删 - 🧩 灵活:支持多云、多环境、多租户
- 📊 可观测:内置 Prometheus 指标,易于监控
- 🛠️ 易集成:与 Helm、GitOps、CI/CD 无缝协作
💡 一句话 takeaway:
ExternalDNS = 声明式 DNS + 自动化运维
✅ 推荐将其纳入标准 Kubernetes 发布流程,尤其适用于微服务、SaaS、CI/CD 密集型架构。
参考资料
- GitHub 项目:kubernetes-sigs/external-dns
- 官方文档:ExternalDNS 文档
- Helm Chart:ArtifactHub - external-dns
- AWS IAM 权限参考:Route 53 API 权限
- Prometheus Metrics:ExternalDNS FAQ - 指标说明
本文基于 ExternalDNS v1.19.0、Helm Chart 1.19.0、Kubernetes v1.22+ 编写,配置和行为可能随版本变化,请以官方文档为准。
If you found this post useful, feel free to bookmark, share, or follow my blog at astromen.github.io!