news 2026/9/25 4:39:08

ExternalDNS 对接 Pi-hole 自定义 DNS:从部署到验证的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ExternalDNS 对接 Pi-hole 自定义 DNS:从部署到验证的完整指南
  • 云原生

【免费下载链接】external-dns

Configure external DNS servers dynamically from Kubernetes resources

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

导读

本指南基于 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 完成。

整体工作流如下:

  1. ExternalDNS 从 Kubernetes 的 Service、Ingress 等资源中收集期望的 DNS 记录(Endpoints);
  2. 通过Records()读取 Pi-hole 当前已有的 A/AAAA/CNAME 记录;
  3. 在 plan 中对比期望状态与现状,生成 Create / Update / Delete 变更;
  4. 通过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-serverEXTERNAL_DNS_PIHOLE_SERVERPi-hole Web 服务器的基地址(--provider=pihole时必填)
--pihole-passwordEXTERNAL_DNS_PIHOLE_PASSWORD管理后台密码(若已开启认证)
--pihole-tls-skip-verifyEXTERNAL_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() 的执行顺序很有讲究:

  1. 先处理纯删除(changes.Delete),直接调用 DELETE;
  2. 将UpdateNew按「域名 + 记录类型」合并去重(同一域名多个新目标会 append 并去重),避免重复写入;
  3. 对UpdateOld,若新旧目标集合完全一致(排序后比较)则跳过删除,否则先删旧记录;
  4. 最后统一创建新记录。

另外,创建时若收到 "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

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

相关推荐

上一篇:regl高级特性探索:多重渲染目标和实例化渲染
下一篇:Thanos协议缓冲:数据序列化方案

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 4:38:54

单机记忆翻牌游戏开发实战:状态机、洗牌算法与移动端优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 4:38:21

Codex 重大更新:AGENTS.md 与 Skills 智能体工作流实战指南

1. 从"焚决"这个词说起:Codex 这次到底更新了什么"焚决"这个词最近在开发者圈子里传得挺凶,第一次看到的时候我还以为是哪个玄幻小说的功法名。后来才搞明白,这是社区里对 Codex 一次重大能力升级的戏称——大概意思是&q…

作者头像 李华
网站建设 2026/9/25 4:37:57

用树莓派开源方案DIY CarPlay车机:从编译到点亮屏幕全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 4:37:34

中文错别字自动纠正实战:轻量级机器学习方案

简介:本资源是一套基于机器学习的中文错别字智能检索与自动纠正系统完整实现,面向人工智能、计算机科学及相关专业(如通信工程、自动化、电子信息等)的在校学生、教师及初级开发者,解决中文文本中常见形近、音近错别字…

作者头像 李华
网站建设 2026/9/25 4:37:20

运维转网络安全实战:6个月升级路线与经验复用指南

运维这行干久了,谁没在凌晨两点接过磁盘告警电话?又有谁没在重大活动保障前一遍遍检查服务器状态,结果还是被一个隐蔽的配置问题搞得焦头烂额?这些场景我太熟悉了,也正是因为这些经历,让我后来转向网络安全…

作者头像 李华
网站建设 2026/9/25 4:36:28

瓦瑟斯坦距离:生成式AI与分布比较的核心度量

1. 这不是数学考试,而是你每天都在用的距离感“瓦瑟斯坦距离”这五个字刚冒出来,很多人第一反应是:又一个拗口的数学名词,大概率和我无关。但事实恰恰相反——你刷短视频时平台推荐的下一条内容,自动驾驶汽车判断前方障…

作者头像 李华