CoreDNS v1.8.0 源码拆包、插件裁剪与 k8s DNS 替换排错
简介面向 Kubernetes 集群运维与容器网络调试场景这份 CoreDNS v1.8.0 离线镜像包解决的是集群内 DNS 服务组件在无外网或受限网络环境下的部署与升级问题适用于正在搭建 k8s v1.21.2 集群、需要固定组件版本的开发与运维人员。压缩包共 8 个文件体积约 40.62MB由 4 个 json 清单与元数据文件、2 个 tar 层数据包和 2 个 version 版本标识文件构成属于标准的镜像分层导出结构可通过 docker load 或导入私有镜像仓库的方式还原为可运行的 CoreDNS 镜像省去自行拉取与重新打包的环节。目前已有 408 人学习下载。借助它读者能快速补齐集群 DNS 组件的镜像来源配合 kubeadm 部署流程完成 coredns 服务验证也可作为版本比对与离线环境镜像归档的参考素材。1. 为什么一个 tar.gz 值得拆CoreDNS v1.8.0 在 k8s 里到底管什么手里这个 coredns_v1.8.0.tar.gz 不是一份现成配置合集而是 CoreDNS v1.8.0 的源码包放进 k8s 场景最直接的用途是替换或重建集群 DNS。很多人拿到包先找 Corefile编译出来的二进制却和集群里跑的版本对不上解析一抖就怀疑网络。这个包适合想搞清楚插件链、需要裁剪插件、或者要固定二进制版本的一线维护者。只改 ConfigMap 的读者也能从源码里看清插件顺序和参数边界避免把“配置写对了”当成“执行顺序也对”。拆它的价值不在背命令而在遇到 DNS 玄学时能定位到是 Corefile、插件顺序还是上游回环。下面按拆包、编译、替换、排查、回归的顺序走能复现也能看到坑在哪。2. 拆包先看三处go.mod、plugin.cfg 与 coremain 的启动链拆包第一步不是改配置而是确认源码根目录和构建入口。tar.gz 解压后如果多一层同名目录后面的make、go generate都会在错误路径执行。先列顶层再解压能避免把文件散在当前目录。# 先列出压缩包顶层目录避免解压后多一层同名目录 tar -tzf coredns_v1.8.0.tar.gz | head -n 5 # 解压并进入源码根目录 tar -xzf coredns_v1.8.0.tar.gz SRC_DIR$(tar -tzf coredns_v1.8.0.tar.gz | head -n 1 | cut -d/ -f1) cd $SRC_DIR # 核对关键入口文件 ls -1 coredns.go coremain plugin.cfg go.mod Makefile这段命令先用tar -tzf只读列表不落盘head -n 1取第一项cut -d/ -f1取顶层目录名。-z表示 gzip-x解压-f指定文件。ls -1是逐行确认入口文件是否存在如果plugin.cfg或coremain缺失说明压缩包层级和预期不一致先别往下编译。2.1 解压后先核对目录与 go.modgo.mod决定模块路径、语言版本和依赖锁定。CoreDNS 的插件 import 路径与 module 名绑定模块名不对后面go generate生成的注册文件就会引用错包。先看前几十行不要急着go build。# 看 module、go 指令和直接依赖 sed -n 1,35p go.mod # 下载依赖并校验本地缓存 go mod download go mod verifysed -n 1,35p只读前 35 行避免被长依赖列表淹没。go mod download把依赖拉到本地模块缓存go mod verify校验缓存内容是否被改动。参数上不需要额外加-x除非要排查下载了哪些模块。失败时先看报错里的模块路径和版本再确认当前目录确实是源码根目录而不是解压出来的子目录。注意依赖下载失败时不要先怀疑源码包损坏先确认go env GOMOD指向的go.mod是不是这份源码的。路径错了所有模块解析都会看起来像“缺包”。2.2 plugin.cfg 决定插件顺序不是装饰CoreDNS 的插件执行顺序由编译时的plugin.cfg决定不是由 Corefile 里写的先后决定。很多人以为 Corefile 里把kubernetes写在forward前面就生效实际请求还是按插件链走。自定义编译时改错顺序集群域名可能先被转发出去表现就是集群内短域名解析不稳定。# 备份后只做阅读不改 cp plugin.cfg plugin.cfg.bak nl -ba plugin.cfg | sed -n 1,60pcp先留一份可回滚的备份nl -ba显示行号方便对比。plugin.cfg每行大致是“插件名:包路径”以#开头的是注释。阅读时重点看kubernetes与forward的相对位置以及cache、loop、reload这些会影响请求路径的插件。不要凭记忆排顺序以包内文件为准。如果后面要裁剪插件改完plugin.cfg必须重新生成注册文件。旧注册文件残留会让二进制继续带旧插件./coredns -plugins看起来正常实际 Corefile 里写新插件却报找不到。这个坑在第 3 章的裁剪步骤里会再落一次命令。2.3 coremain 的启动链与最小可跑 Corefile启动入口在coredns.go它调用coremain里的运行逻辑解析命令行参数、读取 Corefile、按插件链装配 server然后监听端口。理解这条链的好处是排错时知道该看启动日志、Corefile 语法还是插件注册而不是一上来就重启 Pod。# 用非 53 端口跑一个最小实例避免和本机已有 DNS 冲突 cat Corefile.min EOF .:53 { whoami log errors } EOF ./coredns -conf Corefile.min -dns.port 1053.:53是 server block表示默认zonewhoami返回客户端地址用来确认请求确实到了 CoreDNSlog打查询日志errors打错误。-conf指定配置文件-dns.port 1053把监听端口改掉避免和本机 53 端口冲突。启动后另开终端验证# 指定端口查询确认最小实例能响应 dig 127.0.0.1 -p 1053 example.org A shortdig -p指定端口short只输出答案。返回里能看到whoami给出的客户端信息说明请求链路通了。失败时先看ss -lunpt | grep 1053是否被占用再看 Corefile 是否多了不支持的插件。./coredns -plugins可以列出编译进去的插件但不要用它判断执行顺序顺序还是看plugin.cfg。3. 从源码到可执行文件make、插件裁剪与交叉编译参数编译这一步最容易翻车的地方是把go build当成万能命令。CoreDNS 的插件注册文件是生成出来的直接go build可能编出一个“看起来能跑、实际缺插件”的二进制。项目自带Makefile的默认目标会先做生成再构建先看它做了什么再决定要不要手写go build。3.1 make 与 go build 的差别make不是简单包一层go build。它通常会执行go generate生成插件注册文件再编译二进制。直接go build适合已经生成过注册文件、只改业务代码的场景第一次拆包或改过plugin.cfg优先走make。# 先看 Makefile 里默认目标做了什么 grep -nE ^(all|build|coredns|generate|clean): Makefile | head -n 20 # 走项目自带构建链 make ./coredns -version ./coredns -plugins | sort | sed -n 1,20pgrep -nE找目标名和行号head -n 20防止输出太长。make走默认目标./coredns -version看版本字符串./coredns -plugins看编译进去的插件列表。sort和sed只是让输出稳定方便对比裁剪前后。失败时先看make报的是go:generate错误还是编译错误前者多半是生成文件路径或依赖问题后者再看具体包。构建方式是否生成插件注册适用场景主要风险make是首次拆包、改过plugin.cfg依赖 Makefile 目标完整go build否已生成过注册文件、只改局部代码插件缺失或旧注册残留go generatego build是交叉编译、要自定义参数步骤多生成失败不易发现这张表不是让你背而是排错时判断“我是不是少跑了生成”。改过插件配置却直接go build二进制里可能还是旧插件链现象比编译报错更难查。3.2 裁剪插件改 plugin.cfg 后必须重新生成注册文件裁剪插件的目的通常是减小体积或去掉不需要的调试入口。以whoami为例做一次可回滚裁剪先备份再注释对应行清掉旧产物重新构建。不要只改plugin.cfg就复用旧二进制。# 以调试插件 whoami 为例做一次可回滚裁剪 cp plugin.cfg plugin.cfg.bak sed -i /^whoami:/s/^/# / plugin.cfg grep -n ^whoami plugin.cfg || true # 清掉旧注册文件和旧二进制再重新生成 make clean make ./coredns -plugins | grep -w whoami || echo whoami 已不在插件列表sed -i在 Linux 上直接改文件/^whoami:/定位行首的插件名s/^/# /在前面加注释符。grep -n ^whoami用来确认没有未注释的残留|| true只是让命令在没匹配时不中断脚本。make clean是关键旧注册文件不清后面可能继续链接旧插件。grep -w做整词匹配避免whoami被其他字符串误命中。裁剪后如果 Corefile 里还写着whoami启动会报插件找不到。这不是编译失败而是配置和二进制不匹配。解决方法是同步删掉 Corefile 里对应指令或者保留插件不裁剪。生产环境裁剪前先列清楚哪些插件在用log、errors、health、ready这类排错和探活插件不要为了体积随手去掉。3.3 交叉编译与产物校验集群节点通常是 Linux开发机可能是别的系统。交叉编译要显式设置GOOS、GOARCH并关掉 CGO 让产物更接近静态链接减少基础镜像依赖。改过plugin.cfg时先go generate再编译不要跳过生成。# 先生成注册文件再做静态交叉编译 go generate ./... CGO_ENABLED0 GOOSlinux GOARCHamd64 go build \ -trimpath \ -ldflags-s -w \ -o coredns-linux-amd64 . file coredns-linux-amd64 sha256sum coredns-linux-amd64CGO_ENABLED0关闭 CGO减少动态库依赖GOOSlinux、GOARCHamd64指定目标平台-trimpath去掉构建机本地路径-ldflags-s -w去掉符号表和调试信息体积更小但线上崩溃时排错信息也少。file确认产物是目标平台的可执行文件sha256sum留指纹方便多节点比对是不是同一份二进制。要编 ARM64 就把GOARCH改成arm64要 Windows 就改GOOSwindows输出文件名也跟着改。失败时先看go generate是否报错再看go build的包路径。不要用-a强行全量重编来掩盖生成问题那样只会让构建更慢根因还在。4. 在 k8s 里替换 DNSCorefile、ConfigMap 与滚动验证把源码编成二进制只是第一步真正落地是在 k8s 里替换 DNS。替换前先画清请求路径Pod 的/etc/resolv.conf里 nameserver 指向 kube-dns Service 的 ClusterIPService 再把 53 端口转到 CoreDNS Pod。任何一段 selector、端口或 Corefile 写错表现都是“集群内域名解析失败”。4.1 先画清楚请求路径Pod 的 resolv.conf 到 CoreDNS Service先确认 Service、Endpoints 和 Pod 三者对得上。Service 没有 Endpoints 时Pod 里的 DNS 请求会超时但 CoreDNS 日志可能一条都没有。不要一上来就改 Corefile先看请求有没有到。kubectl -n kube-system get svc kube-dns -o wide kubectl -n kube-system get endpoints kube-dns kubectl -n kube-system get pod -l k8s-appkube-dns -o wideget svc看 ClusterIP 和端口get endpoints看后端 Pod IP 有没有注册get pod -l按标签找 CoreDNS Pod。参数-o wide只是多显示节点和 IP。失败时重点看 Endpoints 是否为空为空就查 Service 的 selector 和 Pod labels 是否一致。不同集群的标签可能不同以实际 Deployment 为准不要硬套。提示在 Pod 里看到的 nameserver 是 Service ClusterIP不是 CoreDNS Pod IP。用 Pod IP 去测 DNS 只能验证单个实例不能验证 Service 链路。4.2 Corefile 的 kubernetes 插件参数怎么设Corefile 不是越长越好关键是让kubernetes处理集群域名让forward处理外部域名中间用cache控制缓存。下面是一份可落地的片段集群域名、反查 zone 和上游按实际环境改。apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance }errors把错误打到标准输出health提供健康检查lameduck 5s让进程退出前留出排水时间。ready提供就绪探针kubernetes处理cluster.local和反查 zonepods insecure表示 Pod IP 反查不做 TLS 校验适合集群内可信网络多租户环境再考虑更严格的模式。fallthrough让未命中的反查继续往后走ttl 30控制记录缓存时间。prometheus :9153暴露指标forward . /etc/resolv.conf把外部域名交给节点上游max_concurrent 1000限制并发转发防止上游被打满。cache 30缓存正反查结果loop检测回环reload支持 Corefile 热加载loadbalance对多条 A 记录做轮转。参数不是越大越好ttl太大变更慢太小会增加 apiserver 压力cache和ttl要一起看。参数作用常见设置注意点pods insecurePod IP 反查不校验集群内可信网络多租户要评估fallthrough未命中继续往后走反查 zonezone 要和kubernetes一致ttl记录缓存时间30 秒太小压 apiserver太大变更慢cacheCoreDNS 侧缓存30 秒和ttl一起调max_concurrent限制转发并发1000按上游能力调4.3 ConfigMap 更新与滚动验证命令改 Corefile 前先备份 ConfigMap改完滚动重启并等就绪最后从集群内验证集群域名和外部域名。不要只在本机dig本机走得通不代表 Pod 里走得通。# 1. 先备份现有 ConfigMap改坏了能回滚 kubectl -n kube-system get configmap coredns -o yaml coredns-configmap.bak.yaml # 2. 应用新的 Corefile假设已写入 coredns-configmap.yaml kubectl apply -f coredns-configmap.yaml # 3. 滚动重启并等待就绪 kubectl -n kube-system rollout restart deployment coredns kubectl -n kube-system rollout status deployment coredns --timeout120s # 4. 从集群内验证集群域名和外部域名 kubectl run dns-test --imagebusybox:1.28 --restartNever --rm -it -- \ sh -c nslookup kubernetes.default.svc.cluster.local nslookup example.org-o yaml备份完整对象回滚时可以直接kubectl apply -f。apply是幂等的重复执行不会叠对象。rollout restart触发 Pod 重建rollout status等到就绪或超时。kubectl run起一个临时 Pod在集群网络里执行nslookup验证的是 Pod 到 Service 再到 CoreDNS 的完整链路。失败时先看kubectl -n kube-system logs deploy/corednsCorefile 语法错误通常会在启动日志里带Corefile:字样。再看 Pod 是否 Readyready插件没通过时 Service 可能没有 Endpoints。回滚不要等直接应用备份的 ConfigMap 再重启。5. 避坑与排查loop、forward 回环、插件顺序和缓存DNS 问题最像黑匣子日志少、现象散。下面四条是拆 CoreDNS 源码包和替换 k8s DNS 时最容易遇到的坑每条按“现象、原因、解决”写命令能直接跑。血泪经验是先确认请求有没有到 CoreDNS再谈解析结果。5.1 loop 插件报 loop detected现象是 CoreDNS 日志里反复出现 plugin本文还有配套的精品资源点击获取