- 云原生
【免费下载链接】external-dns
Configure external DNS servers dynamically from Kubernetes resources
导读
本指南基于 external-dns 官方教程,讲解如何将 Kubernetes 中 Service 与 Ingress 的 DNS 需求,通过 ExternalDNS 自动同步到 Pi-hole 的 Custom DNS(本地 DNS 记录列表)。读完本文,你将掌握 Pi-hole v6 伪 API 的工作原理、ExternalDNS 的完整部署清单、三个 pihole 专用参数的用法,以及用dig验证记录生效的完整实操流程。同时,本文会结合本仓库中 Pi-hole 提供商实现 与 API 客户端 的源码,深入说明认证、记录读写与变更应用的真实机制。
集成原理:ExternalDNS 如何写记录到 Pi-hole
Pi-hole 不仅是一个广告拦截 DNS,还维护着一张「自定义 DNS」记录表。在解析请求时,Pi-hole 会最后检查这张本地列表,因此它可以承载任意数量的 A、AAAA 或 CNAME 记录。ExternalDNS 正是利用 Pi-hole 暴露的一套伪 API 来管理这些记录,从而把集群内的域名解析任务无缝交给 Pi-hole 完成。
整体工作流如下:
- ExternalDNS 从 Kubernetes 的 Service、Ingress 等资源中收集期望的 DNS 记录(Endpoints);
- 通过
Records()读取 Pi-hole 当前已有的 A/AAAA/CNAME 记录; - 在 plan 中对比期望状态与现状,生成 Create / Update / Delete 变更;
- 通过
ApplyChanges()调用 Pi-hole 伪 API,最终把记录写入或移出 Custom DNS 列表。
版本要求(重要):本教程要求 Pi-hole 版本为6.0 或更高,v5 的旧 API 已不再被支持。若你在 v5 环境上运行,将无法正常工作。
前置条件
- 一台运行中的 Pi-hole 实例(v6.0+);
- 可访问该实例的 Kubernetes 集群与
kubectl; - 了解 Pi-hole 管理后台的访问地址(后续会写入
--pihole-server参数)。
部署 ExternalDNS
第一步:为管理员密码创建 Secret(可选)
如果 Pi-hole 管理后台受密码保护,建议先把密码存入 Kubernetes Secret,再以envFrom方式注入容器。密码也可以直接用--pihole-password参数传入,两者择一即可。
kubectl create secret generic pihole-password \ --from-literal EXTERNAL_DNS_PIHOLE_PASSWORD=supersecret把supersecret替换为你的真实密码。注意这里的键名EXTERNAL_DNS_PIHOLE_PASSWORD必须与下文清单中envFrom: secretRef配合的键名一致,因为envFrom会把 Secret 中的每个键直接映射为容器环境变量,而 ExternalDNS 恰好在启动时读取该环境变量作为密码来源。
第二步:应用 ExternalDNS 部署清单
若你的 Pi-hole 未开启认证、或不想使用 Secret,可跳过第一步直接应用以下清单。若你使用非
default命名空间,请同步修改ClusterRoleBinding中的namespace字段。
--- apiVersion: v1 kind: ServiceAccount metadata: name: external-dns --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: external-dns rules: - apiGroups: [""] resources: ["services","pods"] verbs: ["get","watch","list"] - apiGroups: ["discovery.k8s.io"] resources: ["endpointslices"] 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: default --- apiVersion: apps/v1 kind: Deployment metadata: name: external-dns 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 # 若未开启认证或未创建 Secret,可删除此块 envFrom: - secretRef: # 若 Secret 名称不同请同步修改 name: pihole-password args: - --source=service - --source=ingress # Pi-hole 仅支持 A/AAAA/CNAME 记录,无法跟踪记录所有权 # 该参数可不设置,但不设置时 ExternalDNS 尝试创建 TXT 记录会输出告警日志 - --registry=noop # 重要:如果 Pi-hole 中存在手动维护的记录,务必设置为 upsert-only 以防被删除 - --policy=upsert-only - --provider=pihole # 改成你的 Pi-hole Web 服务器实际地址 - --pihole-server=http://pihole-web.pihole.svc.cluster.local securityContext: fsGroup: 65534 # 使 ExternalDNS 能读取 Kubernetes token 文件清单要点说明:
--source=service与--source=ingress:声明数据来源,ExternalDNS 会监听这两类资源;--registry=noop:Pi-hole 只支持 A/AAAA/CNAME,无法像 TXT 记录那样标记所有权,因此使用 noop 注册表跳过所有权跟踪(不设置则会出现 TXT 记录创建告警);--policy=upsert-only:只新增/更新、不删除。如果你在 Pi-hole 里手动维护着记录,务必开启它,否则 ExternalDNS 发现期望状态中不存在的记录时会把它们删除;securityContext.fsGroup: 65534:让进程以可以读取挂载的 Kubernetes token 文件的组运行。
第三步:Pi-hole 相关参数速查
| 参数 | 环境变量 | 说明 |
|---|---|---|
--pihole-server | EXTERNAL_DNS_PIHOLE_SERVER | Pi-hole Web 服务器的基地址(--provider=pihole时必填) |
--pihole-password | EXTERNAL_DNS_PIHOLE_PASSWORD | 管理后台密码(若已开启认证) |
--pihole-tls-skip-verify | EXTERNAL_DNS_PIHOLE_TLS_SKIP_VERIFY | 跳过对 Pi-hole Web 服务器 TLS 证书的校验 |
这些参数在 配置类型定义 中以 pflag 方式注册,并一一映射到Config结构体的PiholeServer、PiholePassword(标记为secure:"yes",打印日志时会脱敏)、PiholeTLSInsecureSkipVerify字段。也可在 flags 文档 中查看到相同定义。当--provider=pihole且未设置--pihole-server时,客户端初始化会直接返回ErrNoPiholeServer(见 client.go)。
验证 ExternalDNS 是否工作
用 Ingress 验证
创建一个 Ingress 资源,ExternalDNS 会使用其spec.rules[].host字段作为记录名:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: foo spec: ingressClassName: nginx rules: - host: foo.bar.com http: paths: - path: / pathType: Prefix backend: service: name: foo port: number: 80用 Service 验证
对于 Service,ExternalDNS 读取注解external-dns.kubernetes.io/hostname的值作为记录名。下面的示例同时包含了 Service 与其后端 Deployment:
--- apiVersion: v1 kind: Service metadata: name: nginx annotations: external-dns.kubernetes.io/hostname: nginx.external-dns-test.homelab.com spec: type: LoadBalancer ports: - port: 80 name: http targetPort: 80 selector: app: nginx --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx name: nginx ports: - containerPort: 80 name: http查询记录是否创建成功
将192.168.100.2替换为你实际使用的 DNS 服务器地址,然后用dig验证:
$ dig +short @192.168.100.2 nginx.external-dns-test.homelab.com 192.168.100.129返回 LoadBalancer 的外部 IP 即表示记录已写入 Pi-hole 的 Custom DNS 并生效。
源码深读:Pi-hole Provider 的内部实现
支持的记录类型与读写格式
从 client.go 可以看到,Provider 只支持三类记录,分别对应两个 API 路径:
- A / AAAA:
/api/config/dns/hosts,记录格式为目标IP 域名(如192.168.100.129 nginx.external-dns-test.homelab.com); - CNAME:
/api/config/dns/cnameRecords,记录格式为域名,目标[,TTL],TTL 可选。
listRecords()会按 A、AAAA、CNAME 三类分别拉取并聚合成 Endpoint(见 pihole.go),并对同一域名多目标的情况进行合并。
认证机制:获取与续期 Session Token
当配置了密码时,客户端初始化会向POST /api/auth发送{"password": "..."},从响应的session.sid中取得 Token(见 client.go)。之后所有请求都会携带X-FTL-SID请求头。
更贴心的是 Token 的自动续期:当请求返回 401 时,客户端会先通过GET /api/auth检查 Token 有效性,若已过期则重新获取新 Token 并重发请求,最多重试 3 次(见 client.go)。这个机制在长时间运行、Pi-hole 会话过期时能保证同步任务不中断。
变更应用策略:先删后建与多目标合并
ApplyChanges() 的执行顺序很有讲究:
- 先处理纯删除(
changes.Delete),直接调用 DELETE; - 将
UpdateNew按「域名 + 记录类型」合并去重(同一域名多个新目标会 append 并去重),避免重复写入; - 对
UpdateOld,若新旧目标集合完全一致(排序后比较)则跳过删除,否则先删旧记录; - 最后统一创建新记录。
另外,创建时若收到 "Item already present" 错误会被忽略(幂等写入),删除不存在的记录时 404 也会被忽略(见 client.go),因此重复同步是安全的。
两条硬性约束
- 不支持通配符记录:
apply()中若检测到域名包含*,会返回软错误UNSUPPORTED: Pihole DNS names cannot return wildcard(见 client.go); - CNAME 只能有一个目标:多目标 CNAME 同样返回软错误(见 client.go)。
这两条约束与 Pi-hole Custom DNS 的存储格式直接相关,在规划记录时应提前避开。
测试佐证
仓库为 Pi-hole Provider 提供了完整的单元测试:provider 测试 覆盖了创建、删除、更新、多目标合并去重、新旧目标完全一致时跳过删除等场景;客户端测试 则用httptest模拟/api/auth认证成功/失败、无服务器配置时报ErrNoPiholeServer等分支。你可以直接运行go test ./provider/pihole/...复现这些行为。
此外,Provider 已通过 provider 工厂 注册为externaldns.ProviderPihole,即--provider=pihole实际调用的就是pihole.New。
常见问题与最佳实践
- 为什么建议
--registry=noop?Pi-hole 无法存储 TXT 记录,所有权追踪无从谈起。不设置时 ExternalDNS 仍会尝试创建 TXT 记录并输出告警日志,设置后可保持日志干净。 - 手动维护的记录会丢吗?会。只要记录不在 Kubernetes 期望状态中,ExternalDNS 就可能删除它。务必使用
--policy=upsert-only保护手动记录。 - HTTPS 证书不可信怎么办?自签名证书场景下可开启
--pihole-tls-skip-verify,注意这会降低传输安全性,仅建议在可信内网使用。 - 如何排查同步失败?关注 ExternalDNS 容器日志中的
Pihole token has expired, fetching a new one与received ... status code from request两类日志,前者是正常的 Token 续期,后者通常对应认证失败或 API 路径错误。
更多教程与资料可参考 Pi-hole 教程原文 以及 Provider 列表文档。
- 云原生
【免费下载链接】external-dns
Configure external DNS servers dynamically from Kubernetes resources
相关推荐
如何快速实现iOS滑动单元格:SwipeCellKit终极完整指南
如何快速实现iOS滑动单元格:SwipeCellKit终极完整指南 你是否在为iOS应用的列表交互体验而烦恼?想要实现像原生邮件应用那样流畅的滑动操作,却苦于复
云原生ExternalDNS 接入 GoDaddy DNS 完整指南:API Key 配置、Helm/Manifest 部署与记录同步验证
ExternalDNS 接入 GoDaddy DNS 完整指南:API Key 配置、Helm/Manifest 部署与记录同步验证 本教程基于 Kuberne
云原生KubeEdge终极指南:5分钟掌握云原生边缘计算核心架构
KubeEdge终极指南:5分钟掌握云原生边缘计算核心架构 KubeEdge作为CNCF旗下的Kubernetes原生边缘计算框架,彻底改变了传统边缘设备的管理
云原生边缘计算物联网容器编排边缘网关
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考