Kubernetes服务发现与CoreDNS核心机制详解
1. 从“服务发现”说起:为什么Kubernetes离不开DNS
如果你在Kubernetes里跑过几个Pod,尝试过让一个Pod去访问另一个Pod,你大概率不会去记那个又长又难记的Pod IP,比如
10.244.1.5
。你更希望像在互联网上访问
www.google.com
一样,用一个好记的名字,比如
my-frontend-service
。这个“把名字变成IP地址”的过程,就是服务发现,而DNS(域名系统)是实现服务发现最自然、最通用的方式。Kubernetes将这套机制深度集成,让集群内的应用可以像访问外部网站一样,通过域名来访问内部服务、Pod甚至外部端点,极大地简化了微服务间的通信配置。
在Kubernetes的早期版本中,集群DNS功能由
kube-dns
组件提供。但从1.11版本开始,CoreDNS作为其继任者,成为了默认的DNS服务器。这个转变并非偶然。CoreDNS采用Go语言编写,以其模块化、灵活性和高性能著称。它不像传统的BIND那样拥有一个庞大而复杂的配置文件,而是通过一个名为
Corefile
的简洁配置文件,以插件链(Plugin Chain)的方式组织功能。这种设计使得CoreDNS能够轻松适应Kubernetes动态、声明式的环境,高效地响应服务、Pod和节点的频繁变化。
简单来说,没有DNS的Kubernetes集群,就像一座没有路标和门牌号的城市。服务之间只能通过原始的IP地址进行“盲找”,一旦Pod重启或服务扩缩容导致IP变更,整个通信链路就会断裂。CoreDNS就是这个城市里的“动态地址簿”和“智能查询中心”,它持续监听Kubernetes API Server,实时更新服务与IP的映射关系,确保无论后端如何变化,前端应用始终能用同一个稳定的域名找到它。
2. CoreDNS在Kubernetes中的核心工作机制
要理解CoreDNS如何工作,我们需要深入到它的部署和查询流程中。当你部署一个标准的Kubernetes集群(例如使用
kubeadm
)时,CoreDNS通常会以Deployment的形式运行在
kube-system
命名空间下,背后有一个Service为其提供稳定的集群IP。
2.1 默认的域名解析逻辑
每个Pod在创建时,其
/etc/resolv.conf
文件会被Kubernetes自动注入DNS配置。这个文件决定了Pod如何进行域名解析。一个典型的内容如下:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
我们来拆解这几个关键配置:
-
nameserver 10.96.0.10: 这是CoreDNS Service的集群IP。Pod发出的所有DNS查询,默认都会发送到这个地址。 -
search域 : 这是一个域名后缀列表。当Pod尝试解析一个不完全合格域名(例如,只写了服务名redis)时,系统会依次拼接这些后缀进行查询。查询顺序是:redis.default.svc.cluster.local->redis.svc.cluster.local->redis.cluster.local。这允许你在Pod内用短名称(如redis)访问同一命名空间的服务,用<service>.<namespace>(如redis.prod)访问其他命名空间的服务。 -
options ndots:5: 这是一个性能优化(有时也是坑点)相关的选项。它规定,如果一个域名中的点(.)数量 大于等于5 ,则会被视为绝对域名(FQDN),直接查询, 不 会走search域拼接。如果点数少于5,则会先尝试拼接search域查询,失败后再尝试直接查询。理解这一点对排查某些解析超时问题至关重要。
2.2 CoreDNS的配置核心:Corefile
CoreDNS的行为由一个名为
Corefile
的配置文件驱动。在Kubernetes中,这个配置通常以一个ConfigMap的形式存在,名为
coredns
,位于
kube-system
命名空间。让我们看一个最简化的默认配置:
kubectl get configmap coredns -n kube-system -o yaml
其
Corefile
部分可能如下:
.:53 {
errors # 将错误日志输出到标准输出
health { # 健康检查端点,默认监听8080端口
lameduck 5s
}
ready # 就绪检查端点,用于Kubernetes的Readiness Probe
kubernetes cluster.local in-addr.arpa ip6.arpa { # 核心插件:处理Kubernetes域名的解析
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153 # 暴露Prometheus指标
forward . /etc/resolv.conf # 将非集群域名的查询转发到上游DNS
cache 30 # 缓存
loop # 检测DNS循环查询并终止
reload # 允许自动重载配置(修改ConfigMap后,CoreDNS Pod会自动加载新配置)
loadbalance # DNS负载均衡,对同一域名的多条A记录进行轮询
}
这个配置文件定义了一个监听在53端口的服务器块。其中,
kubernetes
插件是灵魂所在。它告诉CoreDNS:负责解析
cluster.local
及其子域(如
svc.cluster.local
)的请求。当收到一个对
my-svc.default.svc.cluster.local
的查询时,这个插件会去查询Kubernetes API,获取名为
my-svc
、位于
default
命名空间的Service的ClusterIP,并返回给客户端。
forward . /etc/resolv.conf
这一行则指明了“非Kubernetes域名”的去向。例如,Pod内查询
www.baidu.com
,由于该域名不匹配
cluster.local
,CoreDNS会将查询转发到Pod自身的
/etc/resolv.conf
中定义的上游DNS(通常是宿主机的DNS或外部公共DNS),从而实现内外网域名的统一解析。
2.3 解析流程全景图
假设一个Pod(Pod A)想要访问同一个命名空间下的服务
backend
,整个DNS解析流程如下:
-
Pod A内的应用程序发起对
backend的DNS查询。 -
查询请求被发送到本地
/etc/resolv.conf中定义的CoreDNS Service IP(10.96.0.10)。 - 请求到达某个CoreDNS Pod。
-
CoreDNS根据
Corefile配置,首先检查域名。backend的点数少于5(ndots:5),且不是FQDN,因此CoreDNS会先为其拼接search域。 -
它首先尝试查询
backend.default.svc.cluster.local。这个域名匹配kubernetes插件负责的cluster.local域。 -
kubernetes插件拦截该查询,向Kubernetes API Server询问:在default命名空间下是否存在一个名为backend的Service? -
API Server返回该Service的ClusterIP,例如
10.103.156.88。 - CoreDNS将这个IP地址作为DNS响应返回给Pod A。
-
Pod A的应用程序拿到IP,发起实际的TCP/UDP连接到
10.103.156.88:port,由kube-proxy或CNI(如果使用IPVS模式或某些服务网格)完成到后端Pod的负载均衡和流量转发。
这个过程对应用完全透明,开发者无需关心后端Pod的具体位置和IP,实现了彻底的解耦。
3. 深入解析:CoreDNS如何映射Kubernetes资源
CoreDNS的
kubernetes
插件不仅仅能解析Service。通过配置,它可以处理多种Kubernetes资源,形成一套完整的集群内域名体系。
3.1 标准域名格式
-
Service : 这是最常用的形式。
-
格式:
<service-name>.<namespace-name>.svc.cluster.local -
解析结果:对应Service的ClusterIP。如果是Headless Service(
clusterIP: None),则返回该Service后端所有Pod的IP地址列表。 -
示例:
backend.default.svc.cluster.local->10.103.156.88
-
格式:
-
Pod : 虽然不推荐直接通过Pod IP通信,但CoreDNS也支持通过DNS直接定位Pod。
-
格式:
<pod-ip>.<namespace>.pod.cluster.local -
注意,这里的
<pod-ip>需要将点(.)替换为横线(-)。例如,Pod IP10.244.1.5对应的域名是10-244-1-5.default.pod.cluster.local。 - 这主要用于一些特殊场景,如StatefulSet中Pod需要有稳定的网络标识。
-
格式:
-
Service端口 : 可以解析SRV记录,获取服务的端口和协议信息。
-
格式:
_<port-name>._<protocol>.<service>.<ns>.svc.cluster.local - 这在某些需要动态发现端口的客户端中会用到。
-
格式:
3.2 Headless Service与StatefulSet的特别之处
Headless Service(无头服务)是一个特例。当你创建一个
clusterIP: None
的Service时,它不会被分配ClusterIP。此时,对它进行DNS查询,行为会有所不同:
- 如果该Service没有关联任何Selector,则DNS查询会返回该Service配置的所有Endpoints(手动指定的地址列表)。
- 如果该Service有关联Selector(最常见的是与StatefulSet配合),DNS查询会返回该Selector匹配的 所有Pod IP地址的列表 。
StatefulSet与Headless Service是黄金搭档。StatefulSet创建的每个Pod都有稳定的名称:
<statefulset-name>-<ordinal>
。当为StatefulSet创建一个同名的Headless Service后,每个Pod会获得一个稳定的DNS域名:
-
格式:
<pod-name>.<headless-svc-name>.<namespace>.svc.cluster.local -
示例:对于一个名为
web的StatefulSet和一个名为nginx的Headless Service,第一个Pod的域名是web-0.nginx.default.svc.cluster.local。这个域名直接解析到该Pod的IP,并且在该Pod的生命周期内是稳定的,即使Pod重启、重建,只要名字不变,域名解析就不变。这对于构建有状态应用集群(如ZooKeeper, Elasticsearch)至关重要,集群成员可以通过这些稳定的域名互相发现。
3.3 自定义上游与存根域(Stub Domains)
在企业环境中,我们经常需要让集群内的Pod能够解析内部的私有域名,比如
company.internal
。这可以通过CoreDNS的
forward
插件或
hosts
插件来实现,但更Kubernetes原生、更灵活的方式是配置
存根域
和
上游域名服务器
。
你可以在CoreDNS的ConfigMap中修改
Corefile
,添加针对特定域名的转发规则:
.:53 {
...
kubernetes cluster.local in-addr.arpa ip6.arpa {
...
}
# 将 company.internal 域的所有查询转发到指定的内部DNS服务器
forward company.internal 10.100.10.1 10.100.10.2
# 将所有其他非集群域名查询转发到宿主机/etc/resolv.conf定义的上游
forward . /etc/resolv.conf
...
}
这样,当Pod查询
db.company.internal
时,CoreDNS会将其转发到
10.100.10.1
或
10.100.10.2
,而不是公共DNS。这种配置使得混合云或需要访问企业内网服务的场景变得非常简单。
4. 实战:CoreDNS的配置、调优与故障排查
了解了原理,我们来看看如何管理和维护CoreDNS。
4.1 扩缩容与资源限制
默认的CoreDNS Deployment通常只有2个副本。在生产环境中,这可能需要根据集群规模进行调整。你可以直接修改其Deployment的副本数:
kubectl scale deployment coredns --replicas=3 -n kube-system
同样,默认的资源请求和限制可能偏低。高负载下,CoreDNS可能因内存不足(OOM)被杀。你需要编辑其Deployment,调整资源配额。一个中型集群的参考配置如下:
# kubectl edit deployment coredns -n kube-system
resources:
limits:
memory: "256Mi"
cpu: "500m"
requests:
memory: "128Mi"
cpu: "100m"
注意 : CPU请求不宜设得过低,否则在节点CPU竞争激烈时,CoreDNS处理查询的延迟会显著增加,表现为应用DNS解析超时。内存限制需要监控实际使用量来设定,如果CoreDNS频繁OOM,除了增加限制,还应检查是否存在异常的高频查询或环路。
4.2 性能调优:缓存与负载均衡
-
cache插件 : 默认配置中的cache 30表示缓存TTL为30秒。对于解析结果几乎不变的内部服务,可以适当增加缓存时间(如300秒),以减少对API Server的查询压力。对于外部域名,需谨慎,因为过长的缓存可能导致IP变更不及时。你可以为不同区域配置不同的缓存策略。 -
loadbalance插件 : 当Service对应多个Endpoint(Pod IP)时,此插件会对返回的A记录进行轮询(Round Robin)。这能在DNS层面实现简单的客户端负载均衡。大多数情况下应该开启。
一个调优后的
Corefile
片段示例:
.:53 {
...
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30 # 可以适当增加,比如300,减少API查询
}
cache {
success 300 # 成功响应缓存5分钟
denial 30 # NXDOMAIN响应缓存30秒
}
loadbalance round_robin
...
}
4.3 监控与日志
-
监控
: CoreDNS默认集成了
prometheus插件,在9153端口暴露指标。你可以通过Prometheus收集这些指标,关键指标包括:-
coredns_dns_request_count_total: 总请求数,按类型、区域统计。 -
coredns_dns_request_duration_seconds: 请求延迟分布。 -
coredns_dns_response_rcode_count_total: 响应码统计(NOERROR, NXDOMAIN, SERVFAIL等),帮助发现解析失败问题。 -
coredns_panic_count_total: CoreDNS崩溃次数。
-
-
日志
:
errors插件会将错误日志打印到标准输出。你可以通过kubectl logs查看。对于更详细的调试,可以启用log插件,但它会产生大量日志,仅建议在排查问题时临时开启。
4.4 常见故障排查场景与命令
当遇到“服务无法解析”或“解析超时”问题时,可以按照以下链路排查:
场景一:Pod内无法解析任何域名
-
检查CoreDNS Pod状态
:
确保所有Pod都是kubectl get pods -n kube-system -l k8s-app=kube-dnsRunning且READY为2/2(一个容器是CoreDNS,另一个通常是用于节点本地缓存的node-cache,如果部署了的话)。 -
检查CoreDNS Service
:
确认Service存在且有ClusterIP(通常是kubectl get svc kube-dns -n kube-system10.96.0.10)。 -
进入问题Pod,检查DNS配置
:
确认kubectl exec -it <problem-pod> -- cat /etc/resolv.confnameserver指向的是正确的CoreDNS Service IP。 -
从Pod内测试DNS查询
:
如果失败,尝试直接查询CoreDNS Pod IP(绕过Service):kubectl exec -it <problem-pod> -- nslookup kubernetes.default # 或者使用 busybox 镜像 kubectl run -it --rm --restart=Never debug --image=busybox:1.28 -- nslookup kubernetes.default
如果直接访问Pod IP成功,但通过Service IP失败,问题可能出在kube-proxy或网络插件上(Service网络不通)。# 获取一个CoreDNS Pod的IP COREDNS_POD_IP=$(kubectl get pods -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].status.podIP}') kubectl exec -it <problem-pod> -- nslookup kubernetes.default $COREDNS_POD_IP
场景二:可以解析外部域名,但无法解析内部服务
-
检查CoreDNS日志
:
查看是否有关于kubectl logs -n kube-system deployment/coredns --tail=50kubernetes插件的错误,例如连接API Server失败。 -
检查RBAC权限
: CoreDNS需要相应的RBAC权限来访问API Server。通常这些配置在安装时已设置好,但如果被误修改,会导致无法查询服务信息。检查ClusterRole
system:coredns的权限。 -
检查
Corefile配置 : 确认kubernetes插件配置的域(cluster.local)是否正确,是否覆盖了你需要解析的域名。
场景三:DNS解析间歇性超时或延迟很高
- 检查资源限制 : 如前所述,检查CoreDNS Pod的CPU请求是否过低,是否被其他Pod挤压。
-
检查
ndots配置 : 这是最常见的坑之一。假设Pod内频繁查询一个包含4个点的外部域名(如my.app.prod.company.com)。由于ndots:5,点数4<5,系统会先尝试拼接search域,产生一系列无用的查询(如my.app.prod.company.com.default.svc.cluster.local),全部失败后才会发起原始查询,导致显著延迟。-
解决方案A(应用侧)
: 在应用的连接字符串中,使用完全合格域名(FQDN),即以点结尾,如
my.app.prod.company.com.。这样系统会直接查询,不走search域。 -
解决方案B(集群侧)
: 修改Pod或Namespace级别的DNS策略。可以为Pod Spec添加
dnsConfig,降低ndots值或减少search域。但这需要修改应用部署配置。
spec: dnsConfig: options: - name: ndots value: "2" # 降低ndots值 dnsPolicy: None # 必须设置为None才能使用dnsConfig -
解决方案A(应用侧)
: 在应用的连接字符串中,使用完全合格域名(FQDN),即以点结尾,如
- 检查网络插件 : 某些网络插件(如Calico、Cilium)的配置可能会影响Pod与CoreDNS Service之间的网络连通性或性能。
场景四:自定义上游域名解析失败
-
检查
Corefile中的forward规则 : 确认IP地址和端口是否正确,上游DNS服务器是否可达。 -
从CoreDNS Pod内测试上游
:
kubectl exec -it -n kube-system <coredns-pod> -- nslookup <your-internal-domain> <upstream-dns-ip> - 检查网络策略 : 如果集群启用了NetworkPolicy,请确认是否有策略允许CoreDNS Pod访问上游DNS服务器的53端口。
通过以上结构化的排查步骤,大部分CoreDNS相关的问题都能被定位和解决。记住,在Kubernetes中,DNS问题常常表现为网络连通性问题,掌握其工作原理和排查工具是运维人员的必备技能。
更多推荐
所有评论(0)