ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

K8s集群CoreDNS v1.8.0离线部署与排障实战

K8s集群CoreDNS v1.8.0离线部署与排障实战 简介coredns v1.8.0离线镜像包面向Kubernetes集群运维与离线部署场景适用于k8s v1.21.2环境解决内网或无法直接拉取镜像时集群核心DNS组件缺失的问题。压缩包共8个文件以json清单、tar镜像层和version版本文件为主json用于镜像结构与配置描述tar存储实际镜像层version记录组件版本整体大小40.62MB便于传输与导入。包内遵循标准docker镜像导出结构包含manifest.json、layer.tar、VERSION等文件可直接用于离线环境导入。已有405人学习下载适合需要快速搭建或恢复coredns服务的集群管理员、DevOps工程师及k8s学习者。用户可跳过镜像构建环节快速获得与k8s v1.21.2匹配的coredns组件既适用于单机测试环境也可批量导入生产集群降低内网部署门槛同时借助标准镜像结构减少手动封装带来的出错风险。1. 为什么还在用 CoreDNS v1.8.0K8s 集群 DNS 的稳定件与 tar.gz 的价值在 K8s 集群里CoreDNS 几乎是逃不开的组件它做的是服务发现和域名解析的底层活。v1.8.0 是 2021 年前后 K8s 1.19、1.20 时代最常见的配套版本到现在仍有不少企业集群跑在这个版本上。coredns_v1.8.0.tar.gz 这个包直接解压后并不会得到一个可执行的 CoreDNS它更多是源码与构建原料的集合体。很多刚接触 K8s 的同事拿到 tar.gz 就以为“解压、拷贝、运行”三步走结果卡在编译依赖、CGO、镜像打包上。这篇文章把这包里该看的东西拆开从校验、编译、镜像、部署到踩坑照着走完你至少能把一个离线环境里的 CoreDNS v1.8.0 跑起来并且知道它为什么「看起来简单实际坑不少」。2. 拿到 coredns_v1.8.0.tar.gz 之后校验、解包与镜像准备的三个选择题2.1 校验哈希与解包先识别包里是源码还是二进制用源码包的人少但官方 GitHub Release 给出的v1.8.0.tar.gz确实就是源码归档跟coredns_1.8.0_linux_amd64.tgz那种直接含二进制的发布包不是一回事。所以第一步不是急着编译而是先确认手里这个 tar.gz 到底是什么形态。我一般先用sha256sum校验下载完整性再列包内目录。sha256sum coredns_v1.8.0.tar.gz tar -tzf coredns_v1.8.0.tar.gz | head -40哈希值要以你实际下载来源的官方或者可信镜像站给出的为准。如果来源页只给了文件名没给哈希我会再用gzip -t验证压缩包本身有没有损坏。tar -tzf列目录能很快区分源码包和二进制包源码包里会出现coremain/、plugin/、go.mod、Makefile这些文件二进制包里通常是coredns、LICENSE、README和少量.sym符号文件。如果你是离线环境建议把go.mod和plugin.cfg两个文件的版本先读一遍。grep -E kubernetes|etcd|forward plugin.cfg | head -10plugin.cfg决定了 CoreDNS 编译时会包含哪些插件v1.8.0 默认启用了kubernetes、etcd、forward、cache、hosts这些常用项也保留了template、transfer等进阶插件。这个文件在之后自定义编译时是要改的。改plugin.cfg前必须确认你选的基础插件没有互相依赖冲突比如同时启用kubernetes和etcd时两者都注册了cluster.local这种自带的 zone 处理逻辑容易在 Corefile 里出现顺序歧义。压缩包解出来之后先不要急着go build把目录结构完整看一遍确认没有缺失子模块。2.2 构建与镜像本地编译 CoreDNS 的常见做法源码包编译 CoreDNS v1.8.0 需要 Go 1.16 或更高版本这个版本约束在go.mod里能直接看到。我习惯用 Go 1.17 作为编译环境和 v1.8.0 的依赖兼容性最好用太新的 Go 1.21 反而可能因为标准库变更引发某些插件的编译警告。export GO111MODULEon export CGO_ENABLED0 export GOOSlinux export GOARCHamd64 cd coredns_v1.8.0 make corednsCGO_ENABLED0是关键。CoreDNS 默认情况下很多插件需要 cgo 来解析主机文件或者调用系统库但我们要把它跑在容器里依赖 glibc 的二进制进容器后要么体积大要么在 distroless 镜像里直接段错误。CGO_ENABLED0会强制静态链接代价是hosts插件读/etc/hosts、reload插件监控文件变化这些行为都要依赖容器内的纯 Go 实现效果基本一致。make coredns会先根据plugin.cfg生成core/plugin.cfg.go再编译出coredns二进制。编译完成以后可以用file coredns确认它是 statically linked而不是 dynamically linked。file coredns ./coredns -version如果file输出里带statically linked说明可以省去很多麻烦。接下来要做的是把它塞进镜像。我一般不用官方镜像因为离线环境经常拿不到而且官方镜像的基础镜像不一定符合私有仓库的合规要求。我更习惯用多阶段构建把coredns二进制拷进一个干净的基础镜像里。FROM alpine:3.14 RUN apk add --no-cache ca-certificates COPY coredns /usr/bin/coredns COPY Corefile /etc/coredns/Corefile WORKDIR /etc/coredns EXPOSE 53/udp 53/tcp 9153/tcp ENTRYPOINT [/usr/bin/coredns]这里必须装ca-certificates。CoreDNS v1.8.0 的forward插件默认走 DNS over UDP/TCP不验证证书但当你启用tls选项往上游做加密解析时没有 CA 证书根本无法完成 TLS 握手。alpine:3.14是我常用选择体积小、带 shell排障时能进容器执行nslookup和wget比scratch友好很多。2.3 三个基础镜像路线scratch、alpine、distroless 怎么选这不是玄学是实打实的镜像体积和调试能力权衡。官方 CoreDNS 早期版本用过alpine后来才切到distroless。我整理一个简单对照基础镜像体积shellCA 证书适用场景scratch最小无需自备极简离线部署不调试alpine:3.14约 5MB有可安装日常离线环境推荐distroless约 20MB无内置生产安全要求高不做交互调试选scratch就意味着出问题只能看宿主机日志exec进容器啥工具都没有排查 DNS 解析问题极为痛苦。选distroless更接近生产标准但离线导入时你得自带gcr.io/distroless/static-debian10镜像反而多一层依赖。我实际维护离线集群时优先alpine不仅是体积小还因为apk可以临时安装bind-tools进入 CoreDNS 容器直接跑dig验证。docker build -t registry.local/core/coredns:v1.8.0 . docker save registry.local/core/coredns:v1.8.0 | gzip coredns-v1.8.0-image.tar.gzdocker save是导出离线镜像最直接的方式。如果你用的是 containerd 而不是 docker需要换成ctr images import或者nerdctl load。这里有个容易翻车的地方保存体积大的镜像时如果管道被中断生成的 tar.gz 是完全无法用的所以保存后我建议再执行一次tar -tzf抽查镜像层是否存在。3. 部署到 K8s从 manifest 到 Corefile 的自定义3.1 准备 deployment 和 service关键参数说明CoreDNS 在 K8s 里的部署形态是 Deployment默认两个副本通过 ClusterIP 提供服务。v1.8.0 的 Deployment 清单和官方 manifest 差别不大但有几个参数值得单独调。首先replicas不能盲目跟官方设 2如果你的集群节点少两个副本会调度到同一个节点一旦节点故障DNS 直接不可用。我一般会在podAntiAffinity里加一条强制不同节点调度规则。apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system spec: replicas: 2 selector: matchLabels: k8s-app: kube-dns template: metadata: labels: k8s-app: kube-dns spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: k8s-app: kube-dns topologyKey: kubernetes.io/hostname containers: - name: coredns image: registry.local/core/coredns:v1.8.0 args: [-conf, /etc/coredns/Corefile] ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics protocol: TCP resources: limits: cpu: 500m memory: 256Mi requests: cpu: 100m memory: 128Miargs里指定的-conf路径必须和镜像里Corefile的拷贝路径一致否则启动日志会报找不到配置。ports里 53 端口同时声明 UDP 和 TCP是因为 CoreDNS 默认同时监听两种协议TCP 主要用于区域传输和大响应Service 暴露时要同时映射。Service 部分我一般直接用kube-dns这个命名因为集群内其它组件的resolv.conf默认指向的 DNS 服务名就是kube-dns.kube-system改了名字老服务就要跟着改。apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/cluster-service: true kubernetes.io/name: CoreDNS spec: selector: k8s-app: kube-dns clusterIP: 10.96.0.10 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCPclusterIP: 10.96.0.10是很多 K8s 安装器默认的 DNS 地址你在kubelet的--cluster-dns参数里配了哪个Service 的clusterIP就必须是哪个不然集群 DNS 就是个空壳。这里如果配错了Pod 内部解析任意服务名都只会得到connection refused。3.2 Corefile 自定义kubernetes、forward、hosts、rewrite 的优先级Corefile 是 CoreDNS 的核心配置文件v1.8.0 的默认Corefile长这样用Kubernetes插件处理集群内域名用forward把集群外部域名转发给上游。.:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance }这个配置里有几个参数值得解释。kubernetes后面的cluster.local是集群内部域名后缀后面的in-addr.arpa和ip6.arpa用于反向解析pods insecure表示 Pod 的 IP 可以从端点信息里取不额外校验 Pod 名称如果你的集群启用了 Pod 安全策略可以改成pods verified但性能会略有下降。forward . /etc/resolv.conf的写法是把所有不在集群后缀内的域名转发给节点上的 resolv.conf 里配置的 DNS通常是宿主机 DNS 或上游公共 DNS。max_concurrent 1000是 v1.8.0 新增的限制防止上游响应慢导致 goroutine 飙升。自定义时最常踩的是rewrite插件的位置。假如你有个内部域名old.local要映射到新域名new.local把rewrite写在kubernetes之前和之后效果完全不同.:53 { rewrite name exact old.local new.local kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure } forward . /etc/resolv.conf }插件执行顺序严格按 Corefile 里书写顺序rewrite在前时进来的查询先被改写再进入kubernetes或forward流程写在后面则完全不起作用。所以任何需要优先处理的规则必须放在kubernetes之前。3.3 配置 metrics 与日志从标准输出抓错CoreDNS v1.8.0 默认把日志打到标准输出错误日志带plugin/forward:或plugin/kubernetes:前缀。很多新手一发现 DNS 解析失败就到处抓包其实先看日志是最快的。kubectl -n kube-system logs -f deployment/coredns --tail50日志里如果出现SERVFAIL说明上游解析失败如果出现NXDOMAIN说明记录确实不存在或者你的 Corefile zone 写错了。metrics插件打开后可以通过/metrics拉取 Prometheus 格式指标关注coredns_dns_request_duration_seconds和coredns_dns_responses_total这两个指标。后者按rcode标签区分如果NXDOMAIN占比异常高多半是集群后缀配置范围不对。要在Corefile里启用 metrics需要在 server 块里加一行prometheus :9153注意监听地址用:9153不要写成127.0.0.1:9153否则 Pod 外的Service抓取会失败。.:53 { errors health prometheus :9153 kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure } forward . /etc/resolv.conf }加完配置后必须重启 CoreDNS 让新Corefile生效。我一般不会直接kubectl deletepod而是稍微保守一点kubectl -n kube-system rollout restart deployment/coredns kubectl -n kube-system rollout status deployment/coredns4. 避坑与常见问题v1.8.0 的已知边界与资源限制4.1 现象集群 DNS 间歇性超时这是一个很经典的问题集群内服务间调用偶尔报域名解析超时但过一会儿又自动恢复。先排查 CoreDNS Pod 是否频繁重启再看日志有没有SERVFAIL。实际原因通常是 conntrack 对 DNS 的长连接处理不友好。CoreDNS 收到来自同一个 Pod 的大量 UDP DNS 请求时会复用同一连接而 conntrack 可能把后续的 UDP 包当成新流导致响应丢失。解决办法有几个常用方向一是调大cache的 TTL减少重复查询二是在forward插件的上游配置里增加prefer_udp或force_tcp强制用 TCP 避免 UDP 流表问题三是修改 kube-proxy 的conntrack参数。我一般先做第一和第二个因为它们不用动节点。v1.8.0 的forward插件支持prefer_udp在forward . 10.0.0.2后面加这个参数即可。forward . 10.0.0.2 10.0.0.3 { prefer_udp }prefer_udp会让 CoreDNS 优先用 UDP但在某些情况下反而加重 conntrack 压力。如果你的集群规模大直接改成force_tcp往往更稳但上游 DNS 必须支持 TCP 查询。这个变更要在压测环境下验证不能直接上生产。4.2 现象NodeLocal DNSCache 与 v1.8.0 冲突很多集群做了 NodeLocal DNSCache用 DaemonSet 在每个节点上跑一个node-local-dns把集群 DNS 流量留在节点内减少 conntrack 压力。但 v1.8.0 的 CoreDNS 和某些版本的 NodeLocal DNSCache 配合时会出现部分域名解析时好时坏。原因是 NodeLocal DNSCache 默认把上游 DNS 设置为kube-dns的 ClusterIP而 CoreDNS v1.8.0 的 Service 如果被修改过比如clusterIP和 kubelet 配置不一致缓存组件会把请求发给一个不可达的地址。解决方法是检查 NodeLocal DNSCache 的 ConfigMap 里的upstream地址确保它指向 CoreDNS 当前 Service 的 ClusterIP。还有一种情况是 CoreDNS 的 ServicesessionAffinity被设置了导致同一 Pod 的请求总落在同一个 CoreDNS 副本上单副本过载后表现出超时。排查命令如下。kubectl -n kube-system get svc kube-dns -o yaml | grep -E clusterIP|sessionAffinity kubectl -n kube-system get cm node-local-dns -o yaml | grep -A5 upstream如果确认是会话亲和问题把sessionAffinity清掉就行。4.3 现象升级后应用解析外部域名失败从 CoreDNS 1.7 升级到 1.8.0 后部分应用突然解析不了外部域名但集群内部服务解析正常。这种问题多半是 v1.8.0 对forward插件的默认行为做了调整当forward的目标是/etc/resolv.conf时resolv.conf里的search域名和ndots会直接影响解析顺序。K8s 应用的/etc/resolv.conf里通常有search default.svc.cluster.local svc.cluster.local cluster.localndots:5。查询一个短域名时会先尝试把它拼上这些 search 域去请求 CoreDNSCoreDNS 在kubernetes插件里找不到对应的 Service然后 fallthrough 到forward转发给上游。如果Corefile里forward . /etc/resolv.conf后面没有except排除集群后缀那么这些带后缀的拼接查询也会被发给上游上游 DNS 肯定会返回 NXDOMAIN最终应用只能拿到失败结果。解决方法是尽量减少无谓的 search 域拼接并把forward的目标改为一个明确的上游 IP而不依赖宿主机 resolv.conf。forward . 10.0.0.2 { except cluster.local }except cluster.local告诉 CoreDNS 不要把cluster.local后缀的查询转发给上游而是继续走kubernetes插件的逻辑。注意如果你的集群还有别的内部域名比如internal.example.com也要加进except列表。4.4 现象Corefile 语法没报错但规则不生效Corefile 的配置项写错位置或名字时CoreDNS 启动时经常不报语法错误只是某个插件悄悄不工作。比如rewrite插件规则中的正则写法在 v1.8.0 里要求用name regex而不是name exact很多文档没写清楚导致替换规则完全不生效。另一个高频问题是cache插件的success容量写得太小比如只有 1那基本等于没缓存。还有一个点位是插件loop和forward的相互影响如果你的集群节点上的/etc/resolv.conf指向了 CoreDNS 本身而Corefile里forward . /etc/resolv.conf形成了一个环loop插件会发现并终止请求但日志只会打一条Loop警告业务侧看到的是解析被中断。解决方法是把forward的上游改成一个明确的 IP而不是/etc/resolv.conf。这属于配置边界不亲眼看到日志很难定位。kubectl -n kube-system logs -l k8s-appkube-dns | grep -i loop如果有Loop输出立即检查节点resolv.conf里的 nameserver 是不是指向了 CoreDNS 的 ClusterIP。是的话要在节点上改/etc/resolv.conf或调整kubelet的--resolv-conf参数。4.5 现象内存占用持续上涨CoreDNS v1.8.0 在较大集群里内存占用慢慢上涨直到 OOM。先看是不是cache缓存了太多外部域名。默认cache 30表示对成功响应缓存 30 秒这个容量是动态的但持续高 QPS 下会占据大量内存。我建议给cache设置success容量上限比如cache 30 { success 10240 30 denial 1024 5 }success 10240表示最多缓存 10240 条成功记录denial 1024表示最多缓存 1024 条 NXDOMAIN 记录。设置后如果内存还在涨那就是kubernetes插件维护的对象缓存过大。v1.8.0 的kubernetes插件从 K8s API Server watch 所有 Service、Endpoint 和 Pod集群规模上万时内存占用会到几百 MB。可以在kubernetes插件里加resyncperiod和upstream参数或者减少不需要的pods模式。生产环境建议给 CoreDNS 的 memory limit 留出至少 20% 余量不要卡死在 128Mi。5. 验证与压测用 queryperf 给 DNS 做个稳定基线5.1 安装 queryperf 并生成负载DNS 压测工具比较多dnsperf和queryperf是目前最常用的两个。queryperf是 BIND 自带的工具可以在编译 BIND 时一块构建也可以单独拿源码包编译。我一般用它生成一个域名列表然后从集群外打到 CoreDNS 的 Service 上观察丢包率和响应时间分布。./queryperf -s 10.96.0.10 -p 53 -d queryfile.txt -l 10 -c 5-s指定目标 IP-p指定端口-d指定查询文件。查询文件每行一个域名比如www.example.com A。-l 10表示压测持续 10 秒-c 5表示并发 5 个查询流。压测时要注意观察queryperf输出里的Average packet size和Timeout count如果超时比例超过 1%说明 CoreDNS 的cache命中率太低或者上游响应太慢。压测前最好先清空 CoreDNS 的缓存不然你测的是缓存命中性能。v1.8.0 的cache插件没有暴露清空接口只能重启 Pod或者用一个没被查询过的随机域名做测试。我更倾向于重启一次再测保证基线干净。5.2 抓包定位超时tcpdump 的典型思路压测只能告诉你好不好抓包才能告诉你为什么。当查询耗时波动大时在 CoreDNS 节点上抓 UDP 53 端口的数据包先看有没有响应再看响应延迟在哪个环节。tcpdump -i eth0 udp port 53 -c 100 -nn -tt-tt输出精确到微秒的时间戳方便对比请求和响应的间隔。如果能看到请求包但很久才有响应且响应包来自 CoreDNS Pod 对应节点的 IP说明处理在 CoreDNS 内部慢。如果完全没有响应多半是报文被 conntrack 丢弃或 Service 转发问题。还有一种情况是 UDP 响应包太大被分片超过 1500 字节后部分网络插件丢分片客户端只能等到超时。这种情况可以把forward的缓冲区调大或者在 Corefile 里加上bufsize 4096。.:53 { bufsize 4096 ... }bufsize 4096会在响应中带上 EDNS0 UDP 最大载荷 4096减少大的 DNS 响应被分片概率。但这个改动对客户端也有要求老版本的glibc解析器可能忽略这个值所以不能完全依赖它。从那以后我每次调整 CoreDNS 参数都会先压测 10 秒看超时数再抓包 100 条确认请求与响应的时间戳至少反复三轮才敢把变更留在集群里。这样一套流程走下来至少能让你的 CoreDNS 处在可预期、可复现的状态。希望帮到你。本文还有配套的精品资源点击获取
返回列表