在租用的物理机、裸金属服务器上自建 Kubernetes 的团队,几乎迟早会撞上同一道坎:集群里 Service 和 Ingress 声明的主机名一天比一天多,而域名控制台里的解析记录还靠人在页面上一条条点。三五条记录的时候这不是问题,服务数量一旦上到几十个、上百个,人工维护 DNS 就会稳定地产出两类事故——该改的没改,不该改的改错了。ExternalDNS 就是为这一件具体的事而生的控制器,它做的事情只有一句:把集群里声明的主机名,同步成外部权威 DNS 上的记录。本文面向在租用服务器和自建集群上跑业务的运维与平台工程团队,把「谁拥有哪条记录」「为什么会误删」「多套集群共用一个 zone 会怎样」「和证书签发怎么共存」「哪些场景其实不该上」这几个真问题讲透。
需要先划清一条边界:ExternalDNS 不是服务发现组件,也不参与集群内的域名解析。它不碰 CoreDNS 在集群里给 Pod 用的那一套 svc.cluster.local 解析,它面向的是集群之外的权威 DNS 服务商。把这两件事分开,后面所有关于生效时间和故障域的讨论才不会跑偏。
手工维护记录的问题不在于「慢」,而在于每一次业务变更都被拆成了两次彼此独立的提交:一次提交到 Git 里的 YAML,一次提交到域名控制台。这两次提交之间没有事务性,也没有一致性校验,迟早会分叉。最常见的四个失控点分别是改名、扩容、迁移和回滚。
改名是最容易埋雷的。一个服务从 user-api 改成 account-api,运维改了 Ingress 的 host 字段,控制台里旧的 A 记录却还在。旧记录不会报错,也不会有人记得删,它就一直挂在 zone 里,直到某天安全扫描把它当成暴露面报出来,或者某台机器回收后地址被复用,旧记录悄悄指向了别人的业务。
扩容的场景更隐蔽。业务从单集群扩到双集群做容灾,理论上同一批主机名要在两个入口同时生效,人工操作的做法是复制一遍记录。但实际执行时总会有几条漏掉,漏掉的那几条在生产上表现为「部分地区打不开」——因为不同地区的递归 DNS 缓存命中了不同的记录集,排查起来非常费劲。
迁移和回滚是同一枚硬币的两面。集群整体迁到新的物理机池,入口地址全变了,需要在控制台批量改指向;改到一半发现有问题要回滚,就得再批量改回去。这两次批量操作之间如果有人手工改过别的记录,回滚就会连带把无辜的记录一起改坏。人工维护 DNS 的根本缺陷就在这里:它没有「期望状态」的概念,只有一堆彼此孤立的历史操作。而 ExternalDNS 的全部价值,正是把「历史操作」换成「期望状态」。
理解 ExternalDNS,先要理解它的三段式结构:source 负责回答「期望状态是什么」,registry 负责回答「这些记录归谁管」,provider 负责回答「怎么落到具体的 DNS 服务上」。三段缺一段,整套机制就会出现不同形态的失控。
source 是数据的入口。常见的 source 类型有 service、ingress、node、istio 网关以及 CRD(典型是 DNSEndpoint)。用 service 作为 source 时,ExternalDNS 去读 Service 对象上的注解或 LoadBalancer 状态里分配到的外部地址;用 ingress 作为 source 时,它去读 Ingress 规则里的 host 字段以及对应的入口地址。node source 则把节点地址作为记录目标,常见于没有负载均衡器、直接把域名指到节点的自建集群。istio 网关 source 面向的是以 Gateway 资源声明入口的体系。CRD source 是兜底手段:当上面几种资源都表达不了你要的记录时,直接用 DNSEndpoint 这类自定义资源手写期望记录。
选择 source 时有一条务实原则:以团队实际声明入口的方式为准,不要为了接 ExternalDNS 反过来改造业务 YAML。如果业务已经统一用 Ingress 声明主机名,就只开 ingress source,不要同时开 service 和 ingress。多开一个 source 意味着多一条产生期望状态的路径,两条路径对同一个主机名给出不同目标地址时,就会出现后面要讲的多值合并——这不是报错,而是一个静默的、很难被察觉的行为变化。
还需要注意 source 只负责「读」,它不校验主机名是否真的能被访问,也不关心目标地址是否健康。即使后端服务全部 CrashLoopBackOff,只要 Service 或 Ingress 对象还在,期望状态里就有这条记录。ExternalDNS 不是健康检查工具,把「服务不可用」的摘除逻辑寄托在它身上是错误的方向,那应该是负载均衡层或者入口网关层的事情。
如果只能记住 ExternalDNS 的一个概念,那应该是 registry。ExternalDNS 面对的是一个关键难题:一个 zone 里除了它创建的记录,还有人工创建的、别的系统创建的记录,它凭什么判断哪些记录是「自己该管的」、哪些是「不能碰的」?答案就是 registry 里的所有权标记。
默认的 txt registry 工作方式很朴素:每创建一条记录,就额外写一条同名的 TXT 记录,里面带上若干标记。典型标记有三类:heritage=external-dns 表明这条记录由 ExternalDNS 创建;external-dns/owner=<owner-id> 表明具体归属哪个实例,owner-id 由 --txt-owner-id 指定;external-dns/resource=<资源标识> 记录它对应集群里的哪个资源。下一次同步时,ExternalDNS 先读回 zone 里的全部记录,用这些 TXT 标记把记录分成「我的」「别人的」两堆,只对自己那堆做增删改。
这三个标记各有分工,缺一不可。heritage 是粗筛,把 ExternalDNS 体系和人工体系分开;owner 是细筛,把同一体系内的不同实例分开;resource 是追溯,出问题时能反查到是哪个命名空间下的哪个对象产生了这条记录。可以理解为:heritage 决定「要不要管」,owner 决定「轮不轮得到我管」,resource 决定「管的是谁」。
把 registry 讲清楚之后,一个常见误解也就自然消解了:ExternalDNS 不是「接管整个 zone」。它只接管带了自己标记的记录。这也意味着,如果你一开始没设 owner-id,或者两套实例用了同一个 owner-id,那么「我的」这堆记录的范围就会失真——这正是记录漂移最主要的成因。
记录漂移这个词,指的是 zone 里的记录出现非预期的反复增删:一会儿被删掉、一会儿又被建回来,或者稳定地被两个实例轮流改写。它的成因有好几种,但排在第一位、也最容易踩的是 owner-id 撞车。
现场是这样的:同一个 zone 里跑了两套 ExternalDNS——可能是生产集群和预发集群共用一个域名后缀,也可能是集群迁移期间新旧两套并存。如果这两套的 --txt-owner-id 设成了同一个值,或者其中一套干脆没设 owner-id,那么它们各自读回 zone 记录时,都会把对方创建的记录算进「我的」这一堆。接下来就是互相伤害:A 实例计算出「期望状态里没有对方那些记录」,于是判定它们是多余记录并删除;B 实例在下一个周期发现自己的记录没了,于是重新创建;A 再删,B 再建。外部看到的现象就是记录在「存在」和「不存在」之间反复横跳,业务侧表现为间歇性解析失败。
判断是不是 owner-id 撞车的方法很直接:去看 zone 里那些被反复增删的记录对应的 TXT 记录,比对里面的 external-dns/owner 值。如果同一个主机名对应的 TXT 记录内容在两套值之间来回变化,或者 TXT 记录上根本没有 owner 标记,基本就可以锁定。另一个线索是看 ExternalDNS 的日志,删除动作密集成对出现、且删除和创建交替,就是典型症状。
处置办法是强制区分 owner-id,并且把它作为硬性规范写进部署清单:每套 ExternalDNS 实例的 owner-id 必须全局唯一,命名上建议带上集群标识和环境标识,让人一眼能看出是哪一套。owner-id 一旦设定就不要随意更改——更改 owner-id 等价于宣布「之前那些记录我不再认领」,它们会立刻变成无主记录,如果此时 policy 是 sync,就会被判定为多余而删除。这个操作在没有预案的情况下做,等同于一次大规模删记录。
policy 决定 ExternalDNS 被允许对记录做什么动作,一共三种:sync、upsert-only、create-only。选错 policy 是记录事故的第二大成因,而且经常和 owner-id 问题叠加出现。
sync 是默认策略,增删改全做。它会创建期望状态里有、实际没有的记录;更新目标地址变化的记录;删除实际有、期望状态里没有的记录。这是「完全对齐」的语义,也是最终应该达到的状态,但它的破坏力最大——任何一处期望状态算错(比如 source 配错、过滤条件写错、上游资源被误删),错误都会被直接放大成删除动作,且不可逆。
upsert-only 只做增和改,不做删。它保证期望状态里的记录一定存在且正确,但绝不主动清理任何东西。这是迁移期和灰度期应该用的策略:新集群起来了,先把记录建出来并指向新地址,即便这一过程中有算错的地方,最坏结果也只是多出几条记录或者指向暂时不对,而不会把正在服务的记录删掉。代价是 zone 会持续积累孤儿记录,所以需要人工清理窗口。
create-only 更进一步,只建,不改也不删。它适合最保守的观察阶段:你完全不确定这套配置会算出什么样的期望状态,那就先让它只把缺失的记录补上,已经存在的记录一律不动。上线初期的第一个同步周期用 create-only 跑一遍,把将要产生的变更导出来人工审一遍,是性价比很高的做法。三种策略的推进顺序应该是 create-only 观察、upsert-only 过渡、sync 收敛,而不是直接上 sync。
ExternalDNS 的删除语义必须单独强调,因为很多人第一次踩坑都栽在这里:Service 或 Ingress 对象一旦被删除,或者对象上的主机名注解被移除,对应的 DNS 记录会在下一个同步周期被清掉,没有回收站,没有软删除,没有二次确认。
这意味着一次误操作 kubectl delete、一次 Helm 卸载、一次命名空间整体清理,都会连带把生产域名上的记录抹掉。更麻烦的是,同步周期默认是 1 分钟(由 --interval 控制,常见配置为 1m),也就是说删除动作的生效非常快,运维往往来不及在生效前拦住它。而且由于 ExternalDNS 是轮询而非事件驱动,删除发生在下一次轮询时,不是对象删除的瞬间,这给故障定位增加了时间上的模糊性。
加护栏的办法是分层防御,而不是依赖单一开关。第一层是 policy 分层:新接入一律 create-only,观察一段时间确认期望状态正确后再切 upsert-only,最后才切 sync。第二层是 process 分层:把域名记录的 Delete 权限从 ExternalDNS 使用的 API 凭据中剥离——很多 DNS 服务商支持细粒度授权,只给创建和更新权限、不给删除权限,是比任何配置都硬的护栏。第三层是审计分层:开启 DNS 服务商侧的变更日志,任何记录变动都能追溯到时间和来源,出问题能快速回滚。
TTL 也要一起考虑。记录的 TTL 可以用注解 external-dns.alpha.kubernetes.io/ttl 指定,不同 provider 有各自的默认值和最小值限制,不是你想设多短就能设多短。TTL 设得长,一旦记录被误删或误改,递归 DNS 缓存里的旧值会继续生效,这既可能是缓冲(用户暂时无感,你还有时间修),也可能是拖累(修好了也要等缓存过期)。在做迁移这类高风险动作前,先把相关记录的 TTL 调低,让改动能够快速生效;迁移完成稳定后再调回去,是被反复验证过的做法。
把前面分散提到的风险收敛成一张表,方便排查时对照。这张表的用法是:先按「现象」定位行,再按「判定方法」确认,最后执行「处置」。多数线上 DNS 抖动都能在这五行里找到对应项。
| 成因 | 现场现象 | 判定方法 | 处置 |
|---|---|---|---|
| 两套实例 owner-id 相同或未设置 | 记录反复被删又重建,业务间歇性解析失败 | 查同名 TXT 记录中的 owner 标记是否一致或缺失 | 为每套实例设全局唯一 owner-id,先停一套再改 |
| 迁移期直接使用 sync 策略 | 旧集群记录被新实例判定为多余而清空 | 日志中出现成批删除,且删除对象属于另一集群 | 改用 upsert-only 或 create-only,确认期望状态后再切 sync |
| ACME DNS-01 校验 TXT 与 registry TXT 混淆 | 证书续签失败,或校验记录被提前清理 | 查 _acme-challenge 记录是否带有 heritage 标记 |
用 TXT 名称前缀后缀分离,或让一方排除校验域名段 |
| source 配置超出实际使用范围 | 同一主机名被合并成多值记录,指向意外地址 | 比对 TXT 中 resource 标记数量与预期资源数 | 收紧 source 类型,只为实际声明入口的方式开一种 |
| owner-id 中途变更 | 历史记录集体变成无主记录并被删除 | 变更前后的 owner 值不同,删除集中在改名之后 | 提前改 policy 为 upsert-only,改完人工核对后再恢复 |
这张表里每一行都不是理论推演,而是真实会在生产上出现的形态。值得补充的是,这些成因经常叠加:一个团队同时踩了「owner-id 没设」和「迁移期用 sync」,现象会比单行描述复杂得多,排查时不要只找一个原因就收手。
多套集群共用一个域名 zone 是现实中最常见的部署形态:生产和预发用同一个后缀、多个地域集群做容灾、迁移期新旧并存。这时有两种组织方式,代价完全不同。
第一种是拆 owner-id、共用实例配置。做法是每套集群各跑一个 ExternalDNS,指向同一个 zone,但 owner-id 各不相同。好处是配置简单、无需拆分 zone;代价是所有实例都要有这个 zone 的写权限,任何一个实例配置出错都可能影响整个 zone。此外,多套实例同时轮询同一个 zone,会放大对 provider API 的调用量,在记录条数较多时更容易撞上速率限制。
第二种是拆 zone。给每套集群划独立的子域,比如预发用独立的子域后缀,各自管各自的 zone。好处是故障域彻底隔离,预发集群怎么折腾都不会碰到生产记录;代价是域名规划要提前做,业务代码里的主机名也要跟着变,且跨环境复用同一套配置模板时会更麻烦。
拆实例(即拆 owner-id)适合「记录本身就应该共存于同一 zone」的场景,比如双集群容灾下同一个主机名要有两个入口地址。拆 zone 适合「记录本就不该互相看见」的场景,比如生产和预发。判断标准其实很简单:如果两套集群创建的记录允许合并成同一条多值记录,就共 zone 拆 owner-id;如果不允许,就拆 zone。不要用共 zone 来满足隔离需求,那是拿配置正确性去赌。
ExternalDNS 提供了 --domain-filter 和 --zone-id-filter 来限定它只管哪些 zone。这两个参数解决的是「一个实例不要越界」的问题,但它们解决不了「一套凭据不能跨账号」的问题。
具体地说,如果一个实例需要同时写两个归属不同云账号的 zone,那么它必须同时持有两套凭据。而 ExternalDNS 的凭据配置通常是实例级别的,一套实例对应一套凭据。这时正确做法是拆实例:每个账号一个 ExternalDNS 实例,各自配各自的 domain-filter 和凭据,互不干扰。强行在一个实例里塞多套凭据,只会让权限模型变得难以审计,一旦出问题无法定位是哪个账号的变更。
过滤参数还有个容易忽略的细节:过滤是在「读」和「写」两端同时生效的。配置了 domain-filter 之后,不在过滤范围内的 zone 不会被读取,因此也不会被纳入期望状态计算。这既是保护,也意味着一旦过滤条件写错(比如漏了某个后缀),那些 zone 里的记录就会被视为「不存在」,需要创建的记录会被重新创建,可能导致重复创建失败或者与已有记录冲突。改过滤条件属于高风险变更,同样应该在 upsert-only 下先观察。
证书自动签发和 DNS 自动注册,是两件都会往同一个 zone 里写 TXT 记录的事,撞车是必然的,问题只在于怎么隔离。ACME 的 DNS-01 校验方式要求在 zone 里写入名为 _acme-challenge 的 TXT 记录来完成域名所有权验证;ExternalDNS 的 txt registry 也会写 TXT 记录来标记归属。两者都写 TXT,但用途、生命周期和删除条件完全不同。
冲突的表现有两种。一种是证书签发侧失败:ExternalDNS 把 _acme-challenge 记录当成无主的多余记录删掉,导致校验在读取阶段拿不到值。另一种是 ExternalDNS 侧混乱:证书工具写下的 TXT 被误判为 registry 记录,影响归属判断。更麻烦的是,如果证书工具也用了类似 heritage=external-dns 的标记格式(部分实现确实会带类似标记),误判概率会显著上升。
隔离的办法有两类。第一类是名称隔离:给 ExternalDNS 的 TXT registry 记录配置名称前缀或后缀,让它写出的 TXT 记录名与 _acme-challenge 明显区分开;或者反过来,让证书签发侧使用带前缀的校验记录名(部分 ACME 客户端支持通过子域委派实现)。第二类是范围隔离:让其中一方明确排除校验域名段——比如把证书的 DNS-01 校验通过 CNAME 委派到一个独立的校验专用 zone,ExternalDNS 的 domain-filter 不包含那个 zone,双方物理上就见不到面。
从工程角度看,范围隔离比名称隔离更可靠。名称隔离依赖双方都正确配置了前缀,且未来不会有人改回默认;范围隔离则是靠 zone 边界硬隔开,即便某天配置被改错,影响范围也被限制在委派出去的那个校验 zone 里。如果业务体量允许,优先做子域委派。
有一类「ExternalDNS 配好了但就是不生效」的问题,根源不在 ExternalDNS,而在 DNS 协议本身。zone 的顶点,也就是域名本身(例如 example.com 而不是 www.example.com),不能配置 CNAME 记录。这是协议层面的约束,不是某个服务商的限制。
原因在于顶点必然同时存在 SOA 和 NS 记录,而 CNAME 的语义是「这个名称的所有记录类型都别名到另一个名称」,一旦在顶点配置 CNAME,SOA 和 NS 也会被别名掉,整个 zone 的授权体系就崩了。所以在顶点上要指向某个地址,只能使用 A 记录(直接写 IP),或者使用服务商提供的 ALIAS、ANAME 这类非标准但被广泛实现的扁平化记录类型——它们对外表现为 A 记录的应答,内部则由服务商动态解析目标地址。
Ingress 类型的资源在 ExternalDNS 里通常写成 A 记录或 ALIAS 记录,指向负载均衡地址;是否支持 ALIAS/ANAME 取决于具体的 provider 实现,不是所有 provider 都提供。如果你的业务一定要把域名顶点托管给 ExternalDNS,那么在选型阶段就要确认 provider 对顶点记录的支持方式,否则会出现「子域都好好的、就主域名同步不了」的怪现象。
另一个相关限制是多值记录。当多个 Service 或 Ingress 声明了同一个主机名时,ExternalDNS 不会互相覆盖,而是把它们的目标地址合并成一条多值记录。这在双集群容灾场景下是想要的行为,但在「两个团队误用了同一个主机名」的场景下就是静默的事故——没有人报错,两个服务的流量被混在同一个记录里。定期巡检 zone 里是否存在意外的多值记录,应该成为例行工作。
对生效时间的误解,是团队对 ExternalDNS 失望的主要原因之一。很多人以为改完 Ingress 记录就立刻变了,实际不是。延迟由三段叠加而成,每一段都不为零。
第一段是轮询延迟。ExternalDNS 默认同步周期是 1 分钟,由 --interval 控制,常见配置为 1m。它不是事件驱动的,不会因为对象变化立刻触发同步,而是每隔一个周期整体重算一次期望状态。所以对象改完,最坏情况下要等接近一个周期才被读到。
第二段是写入延迟。provider 的 API 有速率限制,ExternalDNS 提供了批处理相关的参数,比如批量变更条数上限、批次之间的间隔、API 重试次数等。当一次同步涉及的变更条数较多时,会被拆成多个批次提交,批次之间还有间隔,大批量记录入库的耗时会明显拉长。这也是为什么在记录规模较大的 zone 里,一次大规模变更的收敛时间远不止一个周期。
第三段是 DNS 传播延迟,这段与 ExternalDNS 无关但必须算进去:权威侧改完之后,递归 DNS 要等记录 TTL 过期才会重新拉取。这三段叠加的结果就是:不要把它当成「改完立刻全网生效」的工具。任何依赖 DNS 切换的动作(切流、容灾切换、回滚),都必须为这三段延迟留出时间窗口,并且提前把 TTL 调低。
规模问题上,有几个口径是接入前就应该知道的,它们决定了你的方案能撑到多大。第一是单条 TXT 记录值的长度:字符串超长时会按 255 字节分块存储。这意味着当 registry 记录的 resource 标识很长(比如命名空间加资源名本身就很长)时,一条 TXT 可能被拆成多块,读取时需要正确拼接。实现上是兼容的,但它提醒了一件事:命名不要太长。
第二是单个 DNS 标签最长 63 字符。这是协议限制,与服务商无关。集群里如果按命名空间和资源名自动生成主机名,很容易在某些长名字上撞到这个上限,表现为记录创建失败或者被截断。自动生成主机名的模板应该在生成侧就做长度校验,而不是等到 DNS 层报错。
第三是云厂商单个托管 zone 的记录条数上限,量级在万条。注意 txt registry 会让记录条数翻倍——一条业务记录对应一条 TXT 归属记录。所以按业务记录数估算容量时要乘二。当记录规模逼近上限时,需要提前拆 zone,而不是等到创建失败再处理。拆 zone 会涉及域名规划调整,属于应该提前做而不是事后补的工作。
把这三条合起来看,规模化的关键动作其实很朴素:控制命名长度、按业务线拆 zone、按环境拆 owner-id、控制单个实例管辖的记录总数。这些都不是调优技巧,而是架构决策,越早做成本越低。
这一节讲的是一个常被混淆的概念,也是很多「ExternalDNS 没生效」工单的真正答案。DNS 系统里有两个角色:权威 DNS 负责「 authoritative 地回答某个域名的值是什么」,递归 DNS 负责「替客户端去问,并把答案缓存一段时间」。
ExternalDNS 写的是权威侧。它把记录提交到 DNS 服务商的权威服务器,从这一刻起,任何一次全新的、没有缓存的查询,都会拿到新值。但客户端通常不直接查权威服务器,而是通过本地递归 DNS(运营商的 LocalDNS、公司内网递归、公共递归等)查询,而这些递归服务器会按记录 TTL 缓存答案。TTL 没过期之前,它们不会重新去问,客户端拿到的就还是旧值。
因此出现「权威侧查是新值、本地 dig 是旧值」时,不是同步失败,而是缓存未过期。判断方法是用 dig 直接指定权威服务器查询,对比结果;两者一致说明同步没问题,只需要等缓存过期。反过来,如果连权威侧都是旧值,那才是 ExternalDNS 侧的问题,要去看轮询周期、批处理队列和 provider 的 API 返回。
这个分工也解释了一个运维上的现实:把权威 DNS 放在哪里,决定了你的变更能力边界。如果权威 DNS 与业务跑在同一个故障域(比如同一批物理机、同一个机房、同一条上行),那么一旦这个故障域出问题,你不仅业务挂了,连通过改 DNS 把流量切走的能力也一起没了。这一点在自建集群的场景下尤其值得警惕。
在租用的物理机上自建集群的团队,有不少同时也在自建权威 DNS,典型是 BIND。ExternalDNS 对这类自建权威 DNS 的支持路径是 RFC2136 动态更新,配合 TSIG 做鉴权。这条路径能用,但有几个前提必须先确认。
第一是动态更新必须开启,且要限定允许的来源地址。开放式的动态更新等于允许任何人改你的 zone,这是不可接受的。第二是 TSIG 密钥要按实例分发,不同集群、不同环境用不同密钥,这样即使某个密钥泄露,影响范围也限于它对应的那部分记录,同时在服务端日志里也能区分变更来源。第三是 BIND 侧的更新日志要落盘并保留,否则出现记录漂移时无从追溯。
TSIG 之外,还要考虑自建权威 DNS 本身的可用性。ExternalDNS 依赖 provider API 的持续可用,如果 BIND 实例是单点,那么它一挂,所有记录变更能力就中断——已有的记录还能解析(取决于从服务器的部署),但新的服务上线就注册不了域名。自建权威 DNS 至少要有主从部署,并且主从之间的同步延迟要在你的变更时间窗口内。
如果团队没有运维自建权威 DNS 的经验,也可以考虑使用云厂商的权威 DNS 作为 provider,同时业务跑在租用的物理机上。这两者不冲突:业务服务器在哪里,和权威 DNS 由谁提供,是两个独立的决策。混合部署在自建集群场景里非常常见。
ExternalDNS 解决的问题是「记录多且变化频繁」,如果你的场景不符合这个前提,它带来的复杂度大于收益。明确不建议上的有下面几类。
第一类:就三五条记录的静态站点。记录总数少、一年改不了一次,手工维护成本几乎为零,而引入 ExternalDNS 意味着要维护一个常驻控制器、一套 API 凭据、一套归属标记体系,还要为它准备审计和回滚机制。这种情况下,控制台手工管理反而更稳妥。
第二类:域名在不受支持的小注册商手里。ExternalDNS 要工作,前提是 DNS 服务商提供可被程序调用的 API,或者有 RFC2136 这类标准更新接口。如果域名所在的注册商只提供网页控制台、没有 API、也不支持把权威 DNS 托管出去,那么 ExternalDNS 无从下手。这种情况下的可行路径是先把权威 DNS 迁移到支持 API 的服务商,而不是硬凑。
第三类:团队没有 DNS 变更审计制度。ExternalDNS 会持续、自动地修改生产域名记录,如果团队连「谁在什么时候改了什么」都没有记录能力,那么出问题时既无法定位也无法回滚。这类团队应该先建立 DNS 变更的审计和回滚流程(哪怕最初是手工的),再考虑自动化。自动化不会让混乱变有序,只会让混乱变快。
还有一类处于灰色地带、需要单独判断的场景:业务对 DNS 变更的时效性要求极高,比如依赖域名切换做秒级容灾。前面算过延迟的三段构成,轮询加批处理加缓存,决定了它不是一个低延迟的调度通道。这类需求更合适的做法是让入口层(负载均衡或网关)来做快速切换,DNS 只承担粗粒度的、不频繁的指向变更。把高频切换压在 DNS 上,无论是否用 ExternalDNS 都会碰壁。
第四类:域名记录中存在大量非集群管理的历史遗留记录,且短期内无法梳理清楚。虽然 txt registry 理论上能保护无标记记录,但如果 policy 被误配成 sync 且 owner-id 未正确设置,风险敞口依然存在。先把 zone 治理清楚,再引入自动同步。
回到服务器租用和自建集群的落地语境,接入 ExternalDNS 之前,有几件事是必须逐项确认的,它们决定了这套机制在你们环境里是减负还是埋雷。
第一,DNS 服务商的 API 是否支持,以及支持的记录类型是否覆盖你的需求(尤其是顶点记录是否支持 ALIAS/ANAME)。这一项决定了选型是否成立,应该在动手之前确认,而不是写到一半发现不支持。第二,API 密钥的权限范围:优先使用最小权限,只授予必要 zone 的写权限;如果服务商支持细粒度授权,可以考虑在灰度阶段剥离删除权限,作为硬护栏。第三,变更审计日志必须开启并保留足够长时间——DNS 事故的追溯高度依赖历史记录,没有日志等于放弃定位能力。
第四,回滚预案。DNS 回滚的本质是「把记录改回上一个已知正确状态」,所以预案的核心是:是否有记录变更的历史版本可查、是否有手工覆盖的通道、覆盖后会不会被下一个同步周期再次改写。这里有个容易忽略的点:如果 ExternalDNS 还在 sync 模式下运行,你手工改回的记录会在下一个周期被它按期望状态再次覆盖。因此回滚动作必须包含「暂停 ExternalDNS 或将 policy 临时切到 upsert-only」这一步。
第五,权威 DNS 是否与业务处于同一故障域。这一点前面提过,但值得单独列为检查项:如果权威 DNS 也跑在同一批租用的物理机上、同一个机房内,那么机房级故障会同时夺走业务和调度能力。把权威 DNS 放在独立的故障域,是成本不高但收益显著的一项安排。
DNS 自动化看起来是纯软件问题,但它对底层资源的消耗是实实在在的。ExternalDNS 的每个同步周期都要从 provider 拉回全量记录做比对,记录条数越多、实例数量越多,对 API 的调用量就越大;多个实例共用一个 zone 时,调用量是相乘的关系。规模上来之后,控制器本身的 CPU 和内存占用、对 API 的网络往返、以及批处理队列的等待时间,都会变成可观测的开销。
所以做资源规划时,建议先把容量账算清楚:预计托管多少个主机名、多少个 zone、几套集群共用、单套集群的记录峰值是多少、同步周期打算设多长。用这几个数字估算 API 调用频次和控制器负载,再倒推需要什么样的机器规格和网络出口质量。在租用物理机的模式下,这种按实际负载倒推的做法比凭经验选配置要可靠得多,也更容易在后续扩容时说清楚要加什么。
把前面的内容收敛成一份上线检查清单:owner-id 是否全局唯一且已写入部署规范、policy 是否处于 create-only 或 upsert-only 而非默认 sync、source 类型是否只开了实际使用的那一种、domain-filter 是否覆盖了全部且仅覆盖应有的 zone、API 凭据是否为最小权限、变更审计日志是否已开启、证书签发的 DNS-01 校验记录是否与 registry 记录完成隔离、顶点记录是否确认了 provider 的支持方式、以及权威 DNS 是否位于独立的故障域。
清单之外还有一条属于组织层面的约定:谁来负责 zone 的最终治理。ExternalDNS 把变更动作自动化了,但「哪些域名允许被自动管理」「谁有权改 domain-filter」「谁能把 policy 从 upsert-only 切到 sync」这些问题仍然需要人做决定。建议在接入时就指定明确的责任人,并把这些高风险配置的变更纳入和代码发布同等严格度的评审。DNS 事故的特点是影响面大、定位慢、恢复依赖外部缓存过期,因此把决策权收窄比把流程放宽更安全。
另外一个值得提前演练的场景是整 zone 误删的恢复路径。虽然 sync 策略下大规模删除理论上只影响带归属标记的记录,但如果 owner-id 曾经变更过、或者历史上有过人工补写的记录没有标记,删除范围就会超出预期。演练的内容应包括:从 DNS 服务商的历史变更日志中导出被删记录清单、按清单批量重建、以及在此期间暂停 ExternalDNS 避免二次覆盖。演练一次的成本远低于真实事故时的慌乱成本。
这份清单里的每一项,对应的是一类真实会发生的故障,而不是形式化的流程。逐项确认完之后再放开 sync,是接入 ExternalDNS 最稳妥的顺序。对于在租用服务器上自建 K8s 的团队来说,DNS 自动化是能显著降低日常变更负担的一步,但它同时也是一把没有安全扣的工具——归属机制理解清楚了,它是减负;理解不清楚,它制造的故障会比手工维护时更隐蔽。一万网络深耕 IDC 行业 19 年(成立于 2007 年),在华南、华东、华北及中国香港、海外多地整合自营节点与 BGP 多线资源,提供服务器租用、机柜托管与带宽接入的一体化交付;具体机型、带宽与报价以官网实时信息或咨询确认为准。
最后提醒一句:本文讨论的是技术接入层面的通用实践,具体的参数命名、provider 支持能力和配额限制会随版本变化,落地时应以官方文档和你所选 DNS 服务商的当前说明为准。涉及机型、带宽与费用的部分,以官网实时信息或咨询确认为准。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品