当前位置: 首页 > news >正文

Kubernetes ExternalDNS 自动化DNS管理实战

1. 项目概述:让Kubernetes服务自动“上户口”,省掉手动改DNS的苦差事

ExternalDNS 这个工具,我第一次在 DigitalOcean 的 Kubernetes 集群里把它跑通的时候,手都在抖——不是因为技术多难,而是因为终于不用再半夜爬起来,登录 DNS 控制台,对着一串又一长串的 Service 和 Ingress 名字,一个一个去填 A 记录、CNAME 记录了。你肯定也经历过:刚把新服务部署上线,前端同事问“域名什么时候能访问?”,运维同事翻着白眼说“等我手动配完 DNS”,结果一等就是半小时;或者更糟,某次发布后忘了更新 DNS,用户打不开页面,监控告警狂响,排查半天才发现是 CNAME 指向了旧的 LoadBalancer IP。这就是 ExternalDNS 要解决的核心问题:把 DNS 记录的生命周期,完全绑定到 Kubernetes 对象的声明周期上。它不是个“辅助工具”,而是一套自动化 DNS 管理的基础设施层。你定义一个 Ingress,它就自动创建对应的 DNS 记录;你删掉这个 Ingress,它几秒内就把记录干干净净地清理掉。背后依赖的是 Kubernetes 的 Informer 机制监听资源变更,再通过 DigitalOcean 提供的 API Token,调用其 DNS API 完成增删改查。整个过程不碰任何人工干预,也不依赖外部脚本或 CI/CD 流水线里的额外步骤。对 DigitalOcean 用户特别友好,因为它的 API 稳定、文档清晰、权限粒度细(你可以只给它管理某个域名的权限),不像某些云厂商的 DNS API 还要绕道 IAM 或者需要全局管理员权限。如果你正在用 Helm 部署应用,那 ExternalDNS 就是 Helm Chart 的天然搭档——Chart 里定义好 ingress.hosts,ExternalDNS 就会自动把 hosts 里的域名解析到对应的 LoadBalancer 地址。这已经不是“锦上添花”,而是现代云原生运维的“基础水电”。

2. 整体架构设计与方案选型逻辑

2.1 为什么必须用 ExternalDNS,而不是自己写脚本?

很多人第一反应是:“我写个 Python 脚本,定时轮询 Kubernetes API,拿到 Service 的 External-IP,再调 DigitalOcean DNS API 更新不就行了?”我试过,而且不止一次。第一次写了个 cron job,每分钟跑一次,结果发现两个致命问题:一是延迟高,服务上线后平均要等 30 秒以上才能解析生效;二是状态漂移严重,比如你删了一个 Ingress,脚本可能刚好错过这次事件,导致 DNS 记录残留,变成“幽灵域名”。后来改成用 Kubernetes Watch API 实时监听,问题没根除,反而引入了新的坑:Watch 连接断开重连时的状态同步丢失、Event 重复触发导致 DNS 接口被限流、以及最麻烦的——如何准确判断一个记录该“创建”还是该“更新”?比如同一个域名,今天指向 Service A,明天指向 Service B,脚本得自己维护一个本地状态缓存,还要处理缓存和实际 DNS 状态不一致的情况。ExternalDNS 把这些全封装好了。它内部有一套完整的“Source-Target-Sync”模型:Source 是 Kubernetes 中的资源(Ingress/Service),Target 是 DNS 提供商(DigitalOcean),Sync 是一个幂等的 reconcile 循环。每次 reconcile 前,它会先从 DigitalOcean 拉取当前所有相关域名的记录快照,再和 Kubernetes 里的期望状态做 diff,只执行真正需要的操作。这个设计保证了最终一致性,也彻底规避了状态漂移。更重要的是,它支持多 Provider 同时运行(比如你既有 DigitalOcean 的公网 DNS,又有内部 CoreDNS 的私有 DNS),而脚本一旦写死 Provider,扩展性就没了。

2.2 为什么不直接用 DigitalOcean 的 Load Balancer 自带的 DNS 功能?

DigitalOcean 的 Load Balancer 确实有个“Assign a domain name”的选项,点一下就能生成一个 do.co 的子域名。但这个功能有硬伤:它只支持 do.co 域名,不支持你自己的主域名(比如 yourcompany.com);它不支持基于路径的路由(Path-based routing),也就是无法为 /api 和 /web 分别指向不同后端;它不支持 TLS 终止,证书还得你自己管。而 ExternalDNS 是站在 Ingress Controller(比如 Nginx Ingress 或 Traefik)肩膀上工作的。Ingress 资源天然支持 host + path 的双重路由规则,支持 annotations 配置 Let's Encrypt 自动签发证书(配合 cert-manager),ExternalDNS 只需读取 Ingress 的 spec.rules.host 字段,就能精准生成你指定的任意域名记录。换句话说,Load Balancer 的 DNS 是“单点快捷入口”,ExternalDNS 是“全链路 DNS 编排系统”。前者适合临时测试,后者才是生产环境的标配。

2.3 Helm vs. 原生 YAML:为什么推荐 Helm 部署 ExternalDNS?

ExternalDNS 官方提供了两种安装方式:Helm Chart 和纯 YAML 清单。我强烈建议用 Helm。原因很实在:配置项太多,且高度耦合。比如你要设置--source=ingress,就得同时确保集群里有 Ingress Controller;你要用 DigitalOcean Provider,就必须提供--provider=digitalocean--digitalocean-api-token;你还得决定是否启用--registry=txt(用 TXT 记录标记所有权,防止误删);是否开启--policy=upsert-only(只增不删,适合灰度发布);是否设置--domain-filter=yourcompany.com(限定只管理特定域名,避免越权)。这些参数如果手写 YAML,光是 ConfigMap 和 Deployment 的 env 字段就得写半页,出错概率极高。Helm Chart 把这些都抽象成了 values.yaml 里的结构化字段,比如:

provider: name: digitalocean digitalocean: apiToken: "your-api-token-here" sources: - ingress - service policy: upsert-only domainFilters: - yourcompany.com txtOwnerId: "prod-cluster-01"

你只需要改这十几行,helm install external-dns bitnami/external-dns -f values.yaml一条命令就搞定。而且 Helm 的版本管理和 rollback 功能,在你某次升级 ExternalDNS 版本导致 DNS 同步异常时,能救命。我们线上就发生过一次 v0.13.5 升级到 v0.14.0 后,因 DigitalOcean API 返回格式微调,导致部分记录同步失败。用helm rollback external-dns 1回退到上个版本,5 分钟内就恢复了全部解析,比手动 patch Deployment 快得多。

2.4 权限最小化设计:ServiceAccount、RBAC 与 DigitalOcean Token 的安全边界

ExternalDNS 不是“上帝进程”,它必须遵循最小权限原则。我在生产环境踩过最大的坑,就是一开始给了它 cluster-admin 权限,结果某次误操作删掉了整个集群的 Ingress 规则,ExternalDNS 看到后,立刻把所有关联的 DNS 记录全清空了——这不是 bug,是 feature 的副作用。正确的做法是分三层隔离:

第一层,Kubernetes RBAC。ExternalDNS 只需要读取特定命名空间下的 Ingress 和 Service,不需要写权限,更不需要访问 Nodes 或 Secrets。我的 rbac.yaml 是这样写的:

apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: [""] resources: ["services", "endpoints", "pods"] verbs: ["get", "watch", "list"] - apiGroups: ["extensions", "networking.k8s.io"] resources: ["ingresses"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["nodes"] verbs: ["list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: external-dns-viewer roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: external-dns subjects: - kind: ServiceAccount name: external-dns namespace: kube-system

第二层,ServiceAccount 绑定。它必须运行在kube-system命名空间,并且只允许访问defaultproduction这两个业务命名空间的资源(通过namespaceSelector或在 Helm values 里指定watchNamespace)。

第三层,DigitalOcean Token 权限。这是最关键的。绝对不能用你的个人账号 API Token!必须在 DigitalOcean 控制台创建一个专用的 Team,把目标域名(如 yourcompany.com)加入该 Team,然后为 ExternalDNS 创建一个只拥有 “Read and Write” 权限的 API Token。这个 Token 只能操作该 Team 下的 DNS 记录,即使泄露,攻击者也无法访问你的 Droplet 或数据库。我在 DO 控制台的截图里,这个 Token 的描述我写的是 “K8s-ExternalDNS-Prod-Only”,一眼就知道用途和范围。

提示:Helm Chart 默认会创建一个名为external-dns的 ServiceAccount,并自动挂载 Token。你只需在 values.yaml 里确认serviceAccount.create=true,并把你的 DO Token 写进provider.digitalocean.apiToken字段即可,无需手动创建 Secret。

3. 核心细节解析与实操要点

3.1 DigitalOcean DNS API 的关键限制与应对策略

ExternalDNS 跑不起来,90% 的原因是卡在 DigitalOcean API 的限制上。DO 的 DNS API 不是无限调用的,它有两条硬性规则,官方文档藏得很深,但必须提前知道:

第一,Rate Limit:每分钟最多 5000 次请求。这听起来很多,但 ExternalDNS 的 reconcile 循环默认是 1 分钟一次,每次 reconcile 会先 GET 所有记录(假设你有 100 个域名,每个域名平均 5 条记录,就是 500 次 GET),再对每个需要变更的记录发起 POST/PUT/DELETE(假设 20 个变更,就是 20 次写操作)。加起来轻松破千。一旦超限,API 返回 429 Too Many Requests,ExternalDNS 就会进入 backoff 重试,最长可能卡住 5 分钟。解决方案有两个:一是调大--interval参数,比如设成5m(5 分钟),把压力摊薄;二是在 values.yaml 里启用--metrics-address=:7979,然后用 Prometheus 监控external_dns_provider_requests_total这个指标,一旦发现 429 错误率突增,立刻扩容或调参。我在线上用的是组合拳:interval: "3m"+metricsAddress: ":7979"+ AlertManager 配置阈值告警。

第二,TTL 最小值是 30 秒。这意味着你无法设置低于 30 秒的 TTL,这对需要快速故障转移的场景是个瓶颈。比如你的服务做了健康检查,后端挂了,你希望 DNS 在 10 秒内切走。ExternalDNS 本身不控制 TTL,它只是把 Ingress 或 Service 的 annotation 透传给 DO API。所以你必须在资源定义里显式声明:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-app annotations: # 这个 annotation 会被 ExternalDNS 读取,并设置为 DNS 记录的 TTL external-dns.alpha.kubernetes.io/ttl: "30" spec: rules: - host: app.yourcompany.com http: paths: - path: / pathType: Prefix backend: service: name: my-app-svc port: number: 80

注意,这个 annotation 的 key 是固定的,不能写错。如果没写,ExternalDNS 会用 DO API 的默认 TTL(1800 秒),那就太长了。另外,DO 的 DNS 解析生效时间还受客户端 DNS 缓存影响,所以你在测试时,别只看dig app.yourcompany.com,一定要用dig @1.1.1.1 app.yourcompany.com(直连 Cloudflare DNS)来排除本地 ISP 缓存干扰。

3.2 TXT 记录所有权标记:为什么它是生产环境的“保险丝”

ExternalDNS 默认行为是“所见即所得”:它看到 Kubernetes 里有个 Ingress,就去 DO 创建对应记录;看到记录没了,就删除。这很危险。想象一下,你有个历史遗留的 DNS 记录legacy.yourcompany.com,它指向一个老的 EC2 实例,但这个实例在 Kubernetes 里没有任何对应资源。某天,你误操作,把legacy.yourcompany.com加进了某个 Ingress 的 hosts 列表,ExternalDNS 会立刻把这个域名的解析切到新的 LoadBalancer 上,导致老服务中断。TXT 记录就是为了解决这个问题。当你在 values.yaml 里设置txtOwnerId: "prod-cluster-01"后,ExternalDNS 每次创建 A 或 CNAME 记录时,会同时创建一个同名的 TXT 记录,内容是"heritage=external-dns,external-dns/owner=prod-cluster-01"。下次 reconcile 时,它只会管理那些 TXT 记录里 owner 字段匹配的域名。这样,legacy.yourcompany.com因为没有对应的 TXT 记录,就会被 ExternalDNS 完全忽略,彻底避免误操作。这个机制就像给 DNS 记录加了一把锁,只有持有正确钥匙(txtOwnerId)的服务才能动它。我们在上线前,会先用doctl dns records list yourcompany.com检查所有现有记录,对需要托管的,手动补上 TXT 记录;对不想托管的,确保它们没有 TXT 记录。这一步是上线前的必检项。

3.3 多环境隔离:如何让 staging 和 prod 共享一个 DO 域名但互不干扰

一个常见需求是:staging.yourcompany.comprod.yourcompany.com都属于yourcompany.com这个主域名,但它们部署在不同的 Kubernetes 集群(staging-cluster 和 prod-cluster),你希望两个集群的 ExternalDNS 互不干扰。靠domain-filter是不够的,因为两个集群都会看到yourcompany.com。正确解法是利用 txtOwnerId + domain-filter 双重过滤。在 staging 集群的 values.yaml 里:

domainFilters: - yourcompany.com txtOwnerId: "staging-cluster-01"

在 prod 集群的 values.yaml 里:

domainFilters: - yourcompany.com txtOwnerId: "prod-cluster-01"

然后,你为 staging 的 Ingress 显式加上 annotation:

annotations: external-dns.alpha.kubernetes.io/hostname: staging.yourcompany.com # 注意,这里指定了 owner,强制 ExternalDNS 用这个 owner 去匹配 TXT 记录 external-dns.alpha.kubernetes.io/txt-owner-id: "staging-cluster-01"

而 prod 的 Ingress 则用"prod-cluster-01"。这样,即使两个集群的 ExternalDNS 都在监听yourcompany.com,它们也只会各自管理自己 owner ID 对应的记录。我们甚至用这个机制实现了“金丝雀发布”:新建一个canary.yourcompany.com,在它的 Ingress annotation 里写txt-owner-id: "canary-release-2024",然后只在 canary 集群部署 ExternalDNS 并配置这个 owner ID,流量就只切给 canary 集群,prod 集群完全无感。

3.4 Ingress 与 Service 双源模式:什么情况下该用 Service?

ExternalDNS 支持--source=ingress--source=service两种模式。绝大多数人只用 Ingress,因为它能自动解析 host。但 Service 源有它不可替代的价值。比如,你有一个 Kafka 集群,需要给客户端提供稳定的kafka-broker-0.yourcompany.com这样的域名,而不是基于 HTTP 的 host。Kafka 是 TCP 协议,没法走 Ingress。这时你就得用 Service 源。具体操作是:创建一个 Type=LoadBalancer 的 Service,然后加上 annotation:

apiVersion: v1 kind: Service metadata: name: kafka-broker-0 annotations: # 这个 annotation 告诉 ExternalDNS,把这个 Service 的 External-IP 解析到指定域名 external-dns.alpha.kubernetes.io/hostname: kafka-broker-0.yourcompany.com spec: type: LoadBalancer selector: app: kafka broker-id: "0" ports: - port: 9092 targetPort: 9092

ExternalDNS 会监听这个 Service 的status.loadBalancer.ingress[0].ip字段,一旦 LoadBalancer IP 分配好,就立刻创建 DNS 记录。注意,Service 源有个坑:它只支持 A 记录(IPv4),不支持 CNAME。所以如果你的 LoadBalancer 是 IPv6-only(DO 目前不支持 IPv6 LB),这条路就走不通。我们线上 Kafka 就是这么做的,所有客户端连接字符串里写的都是kafka-broker-0.yourcompany.com:9092,IP 变了完全不用改代码。

4. 实操过程与核心环节实现

4.1 准备工作:DigitalOcean Token 创建与 Kubernetes RBAC 配置

第一步,登录 DigitalOcean 控制台,点击右上角头像 -> “API” -> “Tokens/Keys” -> “Generate New Token”。在弹窗里:

  • Token name 填k8s-external-dns-prod
  • 勾选 “Write” 权限(必须,因为要创建/删除记录)
  • 在 “Resources” 区域,点击 “Filter by team”,选择你为 DNS 专门创建的 Team(比如叫dns-team
  • 点击 “Generate Token”,复制生成的 token 字符串。这个字符串只显示一次,关掉页面就再也看不到,请立刻存到密码管理器!

第二步,在 Kubernetes 集群里创建专用的 ServiceAccount 和 RBAC。我习惯用一个独立的 YAML 文件rbac.yaml

# 创建命名空间(可选,但推荐) kubectl create namespace external-dns # 应用 RBAC kubectl apply -f rbac.yaml

rbac.yaml内容如下(已按最小权限精简):

apiVersion: v1 kind: ServiceAccount metadata: name: external-dns namespace: external-dns --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: [""] resources: ["services", "endpoints", "pods"] verbs: ["get", "watch", "list"] - apiGroups: ["extensions", "networking.k8s.io"] resources: ["ingresses"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["nodes"] verbs: ["list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: external-dns-viewer roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: external-dns subjects: - kind: ServiceAccount name: external-dns namespace: external-dns

注意:这里我把 ServiceAccount 放在了external-dns命名空间,而不是kube-system。这是为了更好的隔离。Helm Chart 默认会创建 SA,但如果你手动管理,务必确保 SA 名称和 Helm values 里serviceAccount.name一致。

4.2 Helm 部署 ExternalDNS:逐行详解 values.yaml

我们用 Bitnami 的 Helm Chart(bitnami/external-dns),它更新勤快,社区支持好。先添加仓库:

helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update

然后创建values.yaml。下面是我生产环境的完整配置,每一行都加了注释说明为什么这么设:

# 全局配置 nameOverride: "external-dns" fullnameOverride: "external-dns" # 镜像配置,用稳定版,不追最新 image: registry: docker.io repository: bitnami/external-dns tag: 0.13.5-debian-11-r0 pullPolicy: IfNotPresent # ServiceAccount 配置,指向我们刚创建的 serviceAccount: create: false # 我们已手动创建,设为 false name: external-dns annotations: {} # RBAC 配置,确保 Helm 不覆盖我们的手动设置 rbac: create: false # 我们已手动创建 ClusterRole 和 Binding # ExternalDNS 核心参数 args: # 指定数据源,我们同时用 Ingress 和 Service - --source=ingress - --source=service # 指定 DNS 提供商 - --provider=digitalocean # 只管理 ourcompany.com 及其子域名 - --domain-filter=ourcompany.com # TXT 记录 owner ID,用于所有权标记 - --txt-owner-id=prod-cluster-01 # 同步策略:只增不删,避免误删(生产环境强烈推荐) - --policy=upsert-only # 同步间隔:3分钟,平衡实时性和 API 压力 - --interval=3m # 日志级别,调试时设 debug,生产用 info - --log-level=info # 启用 metrics,方便监控 - --metrics-address=:7979 # DigitalOcean Provider 配置 provider: name: digitalocean digitalocean: # 这里填你刚才复制的 API Token apiToken: "your-do-api-token-here" # 资源限制,根据集群规模调整 resources: limits: cpu: 200m memory: 256Mi requests: cpu: 100m memory: 128Mi # Pod 安全策略,禁止特权模式 securityContext: runAsNonRoot: true runAsUser: 1001 fsGroup: 1001 # 亲和性,确保它和 Ingress Controller 在同一可用区(可选) affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app.kubernetes.io/name operator: In values: - external-dns topologyKey: topology.kubernetes.io/zone

部署命令:

helm install external-dns bitnami/external-dns \ --namespace external-dns \ --create-namespace \ -f values.yaml

部署后,检查 Pod 状态:

kubectl get pods -n external-dns # 应该看到 external-dns-xxx-xxx Running 1/1 12s # 查看日志,确认初始化成功 kubectl logs -n external-dns deploy/external-dns | head -20 # 正常输出应该包含 "Created DigitalOcean client" 和 "All records are up to date"

4.3 验证与调试:从零开始跑通第一个 DNS 记录

现在,我们来部署一个最简单的 Ingress,验证整个链路。创建test-ingress.yaml

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: hello-world namespace: default annotations: # 告诉 ExternalDNS,把这个 Ingress 的 host 解析到 DNS kubernetes.io/ingress.class: nginx # 指定要解析的域名 external-dns.alpha.kubernetes.io/hostname: hello.ourcompany.com # 强制使用我们设定的 owner ID external-dns.alpha.kubernetes.io/txt-owner-id: "prod-cluster-01" spec: rules: - host: hello.ourcompany.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-demo port: number: 80 --- # 一个简单的 nginx Service 作为后端 apiVersion: v1 kind: Service metadata: name: nginx-demo namespace: default spec: selector: app: nginx-demo ports: - port: 80 targetPort: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo namespace: default spec: replicas: 1 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80

应用它:

kubectl apply -f test-ingress.yaml

然后,耐心等待 3 分钟(因为我们设了--interval=3m)。期间,你可以实时观察 ExternalDNS 的日志:

kubectl logs -n external-dns deploy/external-dns -f

你会看到类似这样的日志流:

time="2024-05-20T08:12:34Z" level=info msg="Desired change: CREATE hello.ourcompany.com A" time="2024-05-20T08:12:34Z" level=info msg="Desired change: CREATE hello.ourcompany.com TXT" time="2024-05-20T08:12:35Z" level=info msg="Creating records: hello.ourcompany.com A" time="2024-05-20T08:12:35Z" level=info msg="Creating records: hello.ourcompany.com TXT" time="2024-05-20T08:12:36Z" level=info msg="All records are up to date"

最后,用 dig 验证:

# 查询 A 记录 dig +short hello.ourcompany.com @ns1.digitalocean.com # 应该返回你的 LoadBalancer 的 IP 地址,比如 159.203.123.45 # 查询 TXT 记录,确认所有权标记 dig +short hello.ourcompany.com TXT @ns1.digitalocean.com # 应该返回 "heritage=external-dns,external-dns/owner=prod-cluster-01"

如果 dig 没返回,别急,先检查:

  • kubectl get ingress hello-world -o wide,确认 ADDRESS 字段不为空(LoadBalancer 已分配 IP)
  • kubectl get svc -n external-dns,确认 ExternalDNS 的 Service 正常运行
  • kubectl describe deploy external-dns -n external-dns,看 Events 里有没有 ImagePullBackOff 或 CrashLoopBackOff

4.4 高级配置实战:为 Ingress 添加 TLS 并自动解析证书域名

ExternalDNS 和 cert-manager 是绝配。我们来部署一个带 HTTPS 的 Ingress。首先,确保 cert-manager 已安装(用 Helm 安装最稳):

helm repo add jetstack https://charts.jetstack.io helm repo update helm install cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --set installCRDs=true

然后,创建一个 Issuer(用 Let's Encrypt 生产环境):

# issuer.yaml apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: admin@ourcompany.com privateKeySecretRef: name: letsencrypt-prod solvers: - http01: ingress: class: nginx

应用issuer.yaml。接着,部署带 TLS 的 Ingress:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: secure-app namespace: default annotations: kubernetes.io/ingress.class: nginx # 这里指定了两个域名,ExternalDNS 会为它们都创建 A 记录 external-dns.alpha.kubernetes.io/hostname: app.ourcompany.com,api.ourcompany.com # cert-manager 注解 cert-manager.io/cluster-issuer: letsencrypt-prod spec: tls: - hosts: - app.ourcompany.com - api.ourcompany.com secretName: app-tls-secret rules: - host: app.ourcompany.com http: paths: - path: / pathType: Prefix backend: service: name: app-svc port: number: 80 - host: api.ourcompany.com http: paths: - path: / pathType: Prefix backend: service: name: api-svc port: number: 80

ExternalDNS 会自动为app.ourcompany.comapi.ourcompany.com创建 A 记录。cert-manager 会自动申请证书,并把证书存到app-tls-secret这个 Secret 里。Nginx Ingress Controller 读取这个 Secret,就完成了 HTTPS 终止。整个过程,你只需要写一次 YAML,剩下的全是自动化。我们线上所有对外服务都用这套模板,新增一个域名,改三行 YAML,10 分钟内就 HTTPS 就绪。

5. 常见问题与排查技巧实录

5.1 问题速查表:ExternalDNS 不工作,90% 的情况在这里

现象可能原因排查命令解决方案
kubectl get pods -n external-dns显示ImagePullBackOff镜像仓库拉取失败,或镜像 tag 不存在kubectl describe pod -n external-dns <pod-name>检查values.yaml里的image.tag是否拼写正确,Bitnami Chart 的 tag 格式是0.13.5-debian-11-r0,不是0.13.5
kubectl logs -n external-dns deploy/external-dns显示failed to list *v1.Service: services is forbiddenRBAC 权限不足,ClusterRole 没给services资源的list权限kubectl auth can-i list services --as=system:serviceaccount:external-dns:external-dns检查rbac.yaml,确保rules数组里包含了serviceslist
dig hello.ourcompany.com返回空,但kubectl get ingress显示 ADDRESS 有值ExternalDNS 没监听到这个 Ingress,可能命名空间不对kubectl get ingress -A --field-selector metadata.namespace!=defaultExternalDNS 默认只监听default命名空间,如果 Ingress 在production,需在values.yaml里加- --namespace=production
dig hello.ourcompany.com返回旧 IP,新 LoadBalancer IP 没生效DigitalOcean DNS API 调用失败,或 ExternalDNS reconcile 没触发kubectl logs -n external-dns deploy/external-dns | grep "error|failed"检查 DO Token 是否过期,或是否被误删;查看external_dns_provider_requests_total{code="429"}指标是否飙升
dig hello.ourcompany.com TXT返回空,但 A 记录存在txtOwnerId配置错误,或 Ingress annotation 里的txt-owner-id不匹配kubectl get ingress hello-world -o yaml | grep -A5 "txt-owner-id"确保values.yamltxtOwnerId和 Ingress annotation 的external-dns.alpha.kubernetes.io/txt-owner-id完全一致

5.2 实战避坑:那些文档里不会写的“血泪教训”

坑一:Ingress Class 名字大小写敏感,且必须和 Ingress Controller 的--ingress-class参数一致。
我们曾经把 Nginx Ingress Controller 的启动参数设为--ingress-class=nginx(小写),但在 Ingress 的 annotation 里写了kubernetes.io/ingress.class: Nginx(首字母大写)。ExternalDNS 严格按字符串匹配,结果它根本“看不见”这个 Ingress,日志里安静得可怕。解决方案:统一用小写,并在values.yaml里加- --ingress-class=nginx参数,让 ExternalDNS 只监听指定 class。

坑二:DigitalOcean 的 NS 记录必须 100% 正确,少一个点都不行。
你在 DO 控制台为ourcompany.com设置 NS 时,必须填ns1.digitalocean.com.(末尾带点)。如果填成ns1.digitalocean.com(不带点),DO 会把它当成相对域名,自动补成ns1.digitalocean.com.ourcompany.com.,导致 DNS 解析失败。这个点是 DNS 协议里的“根域标记”,缺一不可。我们上线前,会用dig NS ourcompany.com命令,确认返回的 NS 记录末尾都有点。

坑三:ExternalDNS 的--policy=sync是“双刃剑”,生产环境慎用。
sync模式会主动删除 Kubernetes 里不存在的 DNS 记录。听起来很干净,但风险极大。有一次,我们想临时测试一个新域名test.ourcompany.com,就手动在 DO 控制台创建了它,没走 ExternalDNS。结果 ExternalDNS 的 sync 循环一跑,发现 Kubernetes 里没有对应资源,立刻把test.ourcompany.com的 A 记录删了。幸好我们有监控,5 分钟内就发现了。从此,我们生产环境一律用upsert-only,所有“计划外”的 DNS 操作,都走 DO 控制台,ExternalDNS 只

http://www.jsqmd.com/news/1062800/

相关文章:

  • Rufus:解决Windows 11安装难题的终极USB启动盘制作工具
  • 开发信息发布平台 APP,开启个性化运营新时代
  • 2026重庆黄金回收实测排行:7证合规商家优选,变现避坑怎么选? - 名奢变现站
  • AestheticNet:融合视觉与语义的图像美学评估新范式
  • OSX-KVM性能飞跃:从虚拟化到原生体验的全面解锁
  • 大语言模型解码策略实战:Beam Search与Tilted Sampling的工程对比与优化
  • 西安整装公司有推荐的吗?3个维度帮你选 - 速递信息
  • ATUC微控制器硬件开发实战:封装、焊接与勘误表避坑指南
  • NSK精机:W2009FS滚珠丝杠技术规范详述
  • 2026 天津全城名表回收渠道,市区环城上门变现指南 - 逸程
  • 2026年天津离婚律师推荐精选:5位攻破财产分割的实力派 - 本地品牌推荐
  • 解放你的塔科夫:SPT-AKI存档编辑器的完全掌控指南
  • C++哈希容器线程安全实战:Metrowerks线程库与并发控制策略
  • Qwen2.5-VL动态分辨率与绝对时间编码技术解析
  • 闲置旧金饰出手防坑技巧,教你筛选广州靠谱二手黄金回收门店 - 开心测评
  • DeepSeek-V4核心技术解析:mHC、CSA、HCA与Muon工程实践
  • 上海冰丰库:上海餐饮配送中心冷藏库动线布局实战指南 - 上海冰丰库制冷
  • 认识Nectin
  • 2026 杭州各区县手表回收攻略 本地人避坑指南各区腕表变现方法详解 - 薛定谔的梨花猫
  • 投票链接怎么做?365评选2026免注册极速版,3分钟一键生成活动 - 微信投票制作
  • Ubuntu 20.04 自建 Python 3.9 编程环境:源码编译与 venv 隔离实战
  • 闲置爱马仕包包回收,2026哈尔滨五大实体门店实力排名优选 - 名奢变现站
  • Why is software operated, maintained, and serviced
  • 基于概率流与Wasserstein度量的动态系统故障检测与恢复控制
  • 语义网络分析:透视3D环境教育游戏玩家认知结构的X光机
  • YOLOv14 vs YOLOv26:60% mAP领跑标准COCO,跨域能力更是降维打击
  • 嵌入式流协议解析:事件驱动通信与触发机制设计
  • 北京本地刑事律师事务所推荐:五家机构办案特色与优势解析 - 品牌2026
  • 绝地求生罗技鼠标压枪宏:Lua脚本实现后坐力控制的深度技术解析
  • 2026 苏州黄金回收价格行情及正规机构选购指南 - 薛定谔的梨花猫