- 云原生
【免费下载链接】external-dns
Configure external DNS servers dynamically from Kubernetes resources
本指南完整讲解 ExternalDNS 如何通过ambassador-host源读取 Emissary-ingress(原 Ambassador)的Host.getambassador.io资源,并为其声明的域名自动生成 DNS 记录。文章从工作原理、annotation 约定、RBAC 权限,到 kind 本地端到端演练和真实云环境接入,逐一给出可复制的配置与命令,并深入对应源码与测试用例,帮助读者理解"Host 如何映射到 DNS 记录"的底层机制,快速落地 Ambassador/Emissary-ingress 场景的动态 DNS 管理。
概述:为什么需要 ambassador-host 源
ExternalDNS 支持多种"源(source)"来发现 Kubernetes 中的 DNS 记录,每个源监听特定类型的资源,从中提取域名与目标地址(完整源列表)。ambassador-host源属于 Ingress Controllers 类别,专门监听Host.getambassador.io资源——即 Emissary-ingress(前身 Ambassador)用于声明域名与 TLS 配置的 CRD。
在 Emissary-ingress 的架构中,Host资源描述了一个站点的主机名(spec.hostname),而真正的流量入口是集群内的LoadBalancer类型 Service。ambassador-host源把这两者关联起来:读取每个Host的spec.hostname作为 DNS 名称,把对应的 Emissary-ingressLoadBalancerService 的外部地址(IP 或主机名)作为记录目标,从而让 DNS 记录始终跟随 Service 地址变化,无需人工维护。
ExternalDNS 使用getambassador.io/v3alpha1版本的 CRD,这要求 Emissary-ingress 3.x(datawire/ambassadorv2 CRD 已不再随 3.10 quickstart 安装)。如果仍然运行只提供 v2 CRD 的旧版本 Emissary,请继续使用 ExternalDNS v0.21.0 或更早版本。
工作原理
ambassador-host源的核心逻辑位于 source/ambassador_host.go,可总结为三步:
- 发现 Host:通过 Kubernetes dynamic informer 监听
getambassador.io/v3alpha1的hosts资源,将Unstructured对象转换为ambassador.Host类型(Endpoints方法,source/ambassador_host.go)。 - 检查 annotation:每个
Host必须带有external-dns.ambassador-serviceannotation,其值指向 Emissary-ingress 的LoadBalancerService。没有该 annotation 的Host会被直接忽略(日志输出Host %s ignored: no annotation %q found,source/ambassador_host.go)。 - 确定目标并生成记录:优先使用
external-dns.kubernetes.io/targetannotation 覆盖目标;否则解析external-dns.ambassador-service指向的 Service,取其外部地址作为目标(targetsFromAmbassadorLoadBalancer,source/ambassador_host.go)。最终通过endpointsFromHost生成 A / CNAME 记录(source/ambassador_host.go)。
external-dns.ambassador-service 的值格式
该 annotation 支持三种写法,解析逻辑见parseAmbLoadBalancerService(source/ambassador_host.go):
| 写法 | 含义 | 示例 |
|---|---|---|
name | 与Host同命名空间下的 Service | emissary-ingress |
namespace/name | 显式指定命名空间 | emissary/emissary-ingress |
name.namespace | Ambassador 历史跨命名空间语法 | emissary-ingress.emissary |
实现上先按/切分,若无/再按.切分(仅一次,因此svc.foo.bar会被解释为命名空间foo.bar中的 Servicesvc);若两者都不是,则视为同命名空间 Service。测试向量完整覆盖了这些边界情况(source/ambassador_host_test.go),例如ns/svc/foo/bar会被判定为非法格式并返回错误。
目标(Target)的解析优先级
目标地址的确定遵循以下顺序(对应源码中Endpoints的 targets 处理):
external-dns.kubernetes.io/targetannotation 显式指定的目标(TargetsFromTargetAnnotation,见 source/annotations/processors.go);- 无该 annotation 时,解析
external-dns.ambassador-service指向的 Service 地址(extractLoadBalancerTargets,source/service.go):- 优先使用
spec.externalIPs; - 其次使用
status.loadBalancer.ingress[].ip(生成 A 记录); - 再次使用
status.loadBalancer.ingress[].hostname(生成 CNAME 记录)。
- 优先使用
测试用例对上述场景均有覆盖:LoadBalancer IP 生成 A 记录、LoadBalancer hostname 生成 CNAME 记录、externalIPs优先级高于status.loadBalancer、target annotation 覆盖 Service 地址等(source/ambassador_host_test.go)。
支持的 annotation
根据 docs/annotations/annotations.md 的矩阵,ambassador-host源支持:
| Annotation | 作用 |
|---|---|
external-dns.ambassador-service | 必需。指向 Emissary-ingressLoadBalancerService,决定目标地址来源 |
external-dns.kubernetes.io/target | 可选。覆盖记录目标 |
external-dns.kubernetes.io/ttl | 可选。设置记录 TTL |
external-dns.kubernetes.io/<provider>-... | 可选。provider 专属 annotation(如 Cloudflare 的cloudflare-proxied) |
TTL 与 provider 专属 annotation 分别由TTLFromAnnotations(source/annotations/processors.go)与ProviderSpecificAnnotations(source/annotations/provider_specific.go)解析。测试确认:external-dns.kubernetes.io/ttl: "180"会生成RecordTTL: 180的记录(source/ambassador_host_test.go),external-dns.kubernetes.io/cloudflare-proxied: "true"会附带对应的 provider 属性(source/ambassador_host_test.go)。
过滤机制
源还支持两类过滤(见ambassadorHostSource结构体中的annotationFilter与labelSelector,source/ambassador_host.go):
- annotation 过滤:通过
--annotation-filter参数指定(如kubernetes.io/ingress.class in (external-ingress)),不匹配的 Host 被过滤(annotations.Filter,source/annotations/filter.go); - label 过滤:通过
--label-filter参数指定。
在 source/ambassador_host_test.go 中,多组测试验证了 annotation 过滤与 label 过滤单独或组合使用时"匹配则生成记录、不匹配则忽略"的行为。此外,源通过--namespace参数支持"所有命名空间"或"单个命名空间"两种监听范围。
在本地 kind 集群中端到端运行
下面用 kind 在本地完整跑通ambassador-host源,使用inmemoryprovider,无需任何云凭证——ExternalDNS 本来要执行的 DNS 变更会直接打印到日志中。
1. 创建集群
kind create cluster --name external-dns-ambassador2. 安装 Emissary-ingress 3.10
kubectl apply -f https://app.getambassador.io/yaml/emissary/3.10.0/emissary-crds.yaml kubectl wait --timeout=90s --for=condition=available deployment emissary-apiext -n emissary-system kubectl create namespace emissary kubectl apply -f https://app.getambassador.io/yaml/emissary/3.10.0/emissary-emissaryns.yaml kubectl -n emissary rollout status deployment/emissary-ingress第一步安装 CRD 后,Host.getambassador.io(v3alpha1)才会在集群中可写。
3. 部署 ExternalDNS
以下清单包含 ServiceAccount、ClusterRole、ClusterRoleBinding 与 Deployment。注意--source=ambassador-host必须配合--policy=sync才能观察到更新与删除;--inmemory-zone=example.com让inmemoryprovider 持久化记录,否则每次协调循环都会重复发出CREATE,看不到UPDATE/DELETE。
apiVersion: v1 kind: ServiceAccount metadata: name: external-dns namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: [""] resources: ["services","endpoints","pods"] verbs: ["get","watch","list"] - apiGroups: ["getambassador.io"] resources: ["hosts"] verbs: ["get","watch","list"] --- 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: default --- apiVersion: apps/v1 kind: Deployment metadata: name: external-dns namespace: default spec: strategy: type: Recreate selector: matchLabels: app: external-dns template: metadata: labels: app: external-dns spec: serviceAccountName: external-dns containers: - name: external-dns image: registry.k8s.io/external-dns/external-dns:v0.23.0 args: - --source=ambassador-host - --policy=sync # full synchronization so updates/deletes are applied; set --policy=upsert-only to prevent deletions - --provider=inmemory - --inmemory-zone=example.com # persist records so updates/deletes are observable - --log-level=debug # show the records that would be created # for a real provider, replace the two lines above, e.g.: # - --provider=xxx # - --domain-filter=example.com # - --registry=txt # - --txt-owner-id=my-identifier应用清单:
kubectl apply -f external-dns.yaml从源码在宿主机上运行(适合测试对源本身的本地改动):此时使用当前 kubeconfig 上下文(即 kind 集群),不需要集群内 RBAC:
go run main.go \ --source=ambassador-host \ --provider=inmemory \ --policy=sync \ --inmemory-zone=example.com \ --interval=10s \ --log-level=debug参数说明:
--inmemory-zone=example.com:给inmemoryprovider 一个存放记录的 zone。没有它,provider 在两次协调循环之间不保留任何状态,每轮都会重复发出CREATE,永远看不到UPDATE或DELETE;--interval=10s:缩短两次协调循环之间的等待时间(默认一分钟);--log-level=debug:显示本应创建的记录。
ambassador-host源在 source/store.go 的BuildWithConfig工厂中被注册(types.AmbassadorHost分支),启动时通过 dynamic informer 监听Host资源,并通过 source/informers 层注册事件处理器与缓存同步逻辑(source/ambassador_host.go)。
4. 创建一个 Host
inmemoryprovider 没有云 LoadBalancer,因此要用external-dns.kubernetes.io/targetannotation 显式指定目标。但external-dns.ambassador-serviceannotation 依然必须存在,否则该Host不会被处理:
kubectl apply -f - <<EOF apiVersion: getambassador.io/v3alpha1 kind: Host metadata: name: my-host namespace: default annotations: external-dns.ambassador-service: emissary/emissary-ingress external-dns.kubernetes.io/target: 203.0.113.10 spec: hostname: my-host.example.com acmeProvider: authority: none EOF5. 验证
kubectl logs -l app=external-dns -f应看到 ExternalDNS 拾取该Host,并为my-host.example.com创建指向203.0.113.10的 A 记录:
... level=debug msg="Endpoints generated from Host: default/my-host: [my-host.example.com 0 IN A 203.0.113.10 []]" ... level=info msg="CREATE: my-host.example.com 0 IN A 203.0.113.10 []"这段日志与源码中的调试输出一一对应(Endpoints generated from Host: %s: %v,source/ambassador_host.go),也与 source/ambassador_host_test.go 中"真实 API Server 形态的 v3alpha1 Host 对象(含 conversion webhook 注入的ambassador_id与acmeProvider字段)"测试所验证的产物一致。
清理
kind delete cluster --name external-dns-ambassador接入真实云 DNS Provider
本地验证通过后,把 Deployment 中的--provider=inmemory与--inmemory-zone=example.com替换为真实 provider 参数,例如:
args: - --source=ambassador-host - --policy=sync - --provider=aws - --domain-filter=example.com - --registry=txt - --txt-owner-id=my-identifier同时:
- 去掉 target annotation:真实环境中不再需要
external-dns.kubernetes.io/target,让源自动解析external-dns.ambassador-service指向的 Emissary-ingressLoadBalancerService 地址; - 指向正确 Service:把
external-dns.ambassador-service设置为实际运行的 Emissary-ingress Service(上述清单中为emissary/emissary-ingress)。
这样,当云平台为 Emissary-ingress 的LoadBalancerService 分配或更换外部 IP/主机名时,ExternalDNS 会在下一次协调循环中自动更新对应的 DNS 记录,实现 DNS 与入口的动态对齐。
常见问题与排错建议
- Host 被忽略:日志出现
Host xxx ignored: no annotation "external-dns.ambassador-service" found,说明缺少必需 annotation(source/ambassador_host.go)。 - 找不到目标:日志出现
Could not find targets for service ...,说明 annotation 指向的 Service 不存在、格式非法,或该 Service 既没有externalIPs也没有status.loadBalancer地址(source/ambassador_host.go)。 - 总是 CREATE、没有 UPDATE/DELETE:
inmemoryprovider 未配置--inmemory-zone时不保留状态;真实 provider 场景请确认--policy=sync且 registry 配置正确。 - 版本不匹配:ExternalDNS 使用 v3alpha1 CRD,需要 Emissary-ingress 3.x;旧版仅提供 v2 CRD 的环境请使用 ExternalDNS v0.21.0 或更早版本。
相关阅读
- 支持的 Sources 总览:了解
ambassador-host在全部源中的定位与能力矩阵(过滤器、命名空间、FQDN 模板、事件、provider 专属支持) - Annotations 参考:
target、ttl及 provider 专属 annotation 的完整说明 - ambassador-host 源实现:核心实现
- ambassador-host 源测试:覆盖各种 annotation、过滤与目标解析场景
- ExternalDNS 部署清单:生产环境部署参考
- 云原生
【免费下载链接】external-dns
Configure external DNS servers dynamically from Kubernetes resources
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考