1. 问题现象与背景分析
最近在RKE2集群中部署NodeLocalDNS并启用网络策略后,不少用户反馈出现间歇性DNS解析失败的情况。具体表现为部分Pod无法解析集群内服务名称,偶尔连外部域名(如api.twitter.com)也会超时。这个问题在Kubernetes 1.23+版本中尤为常见,特别是在同时满足以下条件时:
- 使用RKE2默认的CoreDNS配置
- 启用了NodeLocalDNS缓存
- 配置了限制DNS流量的NetworkPolicy
通过抓包分析发现,当Pod尝试解析域名时,DNS查询包被正确路由到了NodeLocalDNS(169.254.20.10),但响应包在返回途中被网络策略拦截。这导致DNS查询在等待5秒后超时,然后自动重试到CoreDNS服务IP,形成一种"先失败后成功"的异常现象。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心组件交互原理
2.1 NodeLocalDNS工作流程
NodeLocalDNS作为DaemonSet部署后,会在每个节点上:
- 监听169.254.20.10:53和节点IP:53
- 通过iptables规则将Pod的DNS请求劫持到本地缓存
- 缓存未命中时向上游CoreDNS发起查询
关键iptables规则示例:
bash复制-A OUTPUT -d 10.43.0.10/32 -p udp -m udp --dport 53 -j DNAT --to-destination 169.254.20.10:53
-A OUTPUT -d 10.43.0.10/32 -p tcp -m tcp --dport 53 -j DNAT --to-destination 169.254.20.10:53
2.2 网络策略的影响机制
当启用如下网络策略时:
yaml复制apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
spec:
egress:
- ports:
- port: 53
protocol: UDP
实际上限制了Pod到任何53端口的出站流量,包括到NodeLocalDNS的通信。虽然NodeLocalDNS在同一节点,但Kubernetes网络插件(如Calico)仍会应用该策略。
3. 解决方案与配置调整
3.1 修正网络策略
需要明确放行到NodeLocalDNS的流量:
yaml复制egress:
- to:
- ipBlock:
cidr: 169.254.20.10/32
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
3.2 NodeLocalDNS配置优化
在RKE2的helm values中增加:
yaml复制localDNS:
ipAddress: 169.254.20.10
prometheus:
enabled: true # 开启监控便于排查
forceTcpToUpstream: true # 强制TCP协议提升稳定性
3.3 CoreDNS调优
修改RKE2的CoreDNS配置(/var/lib/rancher/rke2/server/manifests/rke2-coredns-config.yaml):
yaml复制template: |
.:53 {
errors
ready
health {
lameduck 5s
}
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
forward . /etc/resolv.conf {
prefer_udp # 关键修改点
expire 10s
}
cache 30
loop
reload
loadbalance
}
4. 验证与排查指南
4.1 基础检查步骤
- 确认NodeLocalDNS Pod正常运行:
bash复制kubectl -n kube-system get pods -l k8s-app=node-local-dns
- 检查iptables规则是否生效:
bash复制iptables -t nat -L | grep 169.254.20.10
- 测试DNS解析时延:
bash复制# 从业务Pod内执行
time nslookup kubernetes.default.svc.cluster.local
time nslookup example.com
4.2 高级诊断工具
使用dig命令分析查询路径:
bash复制dig +tcp @169.254.20.10 kubernetes.default.svc.cluster.local
dig +trace @169.254.20.10 example.com
抓包分析(在NodeLocalDNS Pod所在节点):
bash复制tcpdump -i any port 53 -w dns.pcap
5. 性能优化建议
- 缓存调优:
yaml复制localDNS:
cache:
maxSize: 10000 # 默认是5000
positiveTTL: 30s
negativeTTL: 5s
- 并发控制:
yaml复制prometheus:
enabled: true
port: 9253
metrics:
maxRequests: 100
concurrentRequests: 10
- 资源限制:
yaml复制resources:
limits:
memory: 100Mi
cpu: 200m
requests:
memory: 50Mi
cpu: 50m
6. 典型问题案例
案例1:周期性解析失败
现象:每5分钟出现持续10秒的DNS超时
原因:CoreDNS默认每5分钟重载配置,期间短暂不可用
解决:调整CoreDNS的reload周期为30分钟
yaml复制reload 30s
案例2:IPv6解析异常
现象:AAAA记录查询超时
解决:在NodeLocalDNS配置中禁用IPv6
yaml复制localDNS:
disableIPv6: true
案例3:特定域名解析缓慢
现象:部分云服务API域名解析耗时>2s
解决:在CoreDNS配置中添加特定域名缓存
yaml复制cache {
success 9984 30
denial 9984 5
prefetch 10 60s 10%
}
7. 安全加固措施
- 限制NodeLocalDNS的访问范围:
yaml复制networkPolicy:
enabled: true
ingress:
- from:
- podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
- 启用DNSSEC验证:
yaml复制localDNS:
dnssec:
enabled: true
validate: true
- 审计日志配置:
yaml复制logging:
level: info
format: json
audit: true
8. 监控与告警配置
Prometheus监控指标示例:
yaml复制- name: node_local_dns
rules:
- alert: NodeLocalDNSHighFailureRate
expr: rate(node_local_dns_request_failures_total[1m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "NodeLocalDNS high failure rate on {{ $labels.instance }}"
description: "DNS failure rate is {{ printf \"%.2f\" $value }}"
Grafana仪表板关键指标:
- 请求成功率(1 - failures/requests)
- 缓存命中率(cache_hits/requests)
- 上游查询时延(upstream_latency_seconds)
9. 版本兼容性说明
不同RKE2版本的注意事项:
| RKE2版本 | 核心变化 |
|---|---|
| v1.21.x | 需手动部署NodeLocalDNS |
| v1.22.x | 内置CoreDNS 1.8.3,需调整forward策略 |
| v1.23.x | 默认启用DNS查询跟踪 |
| v1.24+ | 支持自动重载TLS证书 |
10. 替代方案对比
当NodeLocalDNS表现不稳定时,可考虑:
- CoreDNS本地缓存模式
yaml复制template: |
.:53 {
cache {
prefetch 10 60s 10%
}
}
-
使用ExternalDNS+云厂商PrivateZone
优势:降低集群负载
劣势:增加云服务成本 -
DNSMasq Sidecar模式
适合场景:
- 需要精细控制缓存策略
- 对TCP协议有特殊要求
配置示例:
yaml复制containers:
- name: dnsmasq
image: andyshinn/dnsmasq:2.82
args:
- --log-queries
- --cache-size=10000
- --server=/cluster.local/10.43.0.10
- --server=/in-addr.arpa/10.43.0.10
- --server=/ip6.arpa/10.43.0.10
