☰
ExternalDNS 的 Ambassador / Emissary-ingress Host 源(ambassador-host)实战指南
2026/9/25 5:07:50 网站建设 项目流程
  • 云原生

【免费下载链接】external-dns

Configure external DNS servers dynamically from Kubernetes resources

项目地址:https://gitcode.com/gh_mirrors/ex/external-dns
点击查看免费下载

本指南完整讲解 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,可总结为三步:

  1. 发现 Host:通过 Kubernetes dynamic informer 监听getambassador.io/v3alpha1的hosts资源,将Unstructured对象转换为ambassador.Host类型(Endpoints方法,source/ambassador_host.go)。
  2. 检查 annotation:每个Host必须带有external-dns.ambassador-serviceannotation,其值指向 Emissary-ingress 的LoadBalancerService。没有该 annotation 的Host会被直接忽略(日志输出Host %s ignored: no annotation %q found,source/ambassador_host.go)。
  3. 确定目标并生成记录:优先使用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同命名空间下的 Serviceemissary-ingress
namespace/name显式指定命名空间emissary/emissary-ingress
name.namespaceAmbassador 历史跨命名空间语法emissary-ingress.emissary

实现上先按/切分,若无/再按.切分(仅一次,因此svc.foo.bar会被解释为命名空间foo.bar中的 Servicesvc);若两者都不是,则视为同命名空间 Service。测试向量完整覆盖了这些边界情况(source/ambassador_host_test.go),例如ns/svc/foo/bar会被判定为非法格式并返回错误。

目标(Target)的解析优先级

目标地址的确定遵循以下顺序(对应源码中Endpoints的 targets 处理):

  1. external-dns.kubernetes.io/targetannotation 显式指定的目标(TargetsFromTargetAnnotation,见 source/annotations/processors.go);
  2. 无该 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-ambassador

2. 安装 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 EOF

5. 验证

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

同时:

  1. 去掉 target annotation:真实环境中不再需要external-dns.kubernetes.io/target,让源自动解析external-dns.ambassador-service指向的 Emissary-ingressLoadBalancerService 地址;
  2. 指向正确 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

项目地址:https://gitcode.com/gh_mirrors/ex/external-dns
点击查看免费下载
上一篇:如何高效自动化处理B站会员购抢票:开源工具完全指南
下一篇:Undercover在大型Ruby项目中的应用:Rails、Hanami和其他框架的最佳实践

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询