Agent服务通常需要动态扩缩容,Pod的IP随时可能变化,如果靠配置文件写死地址,维护成本会非常高。Consul提供了成熟的服务注册与健康检查机制,而Kubernetes自身也有基于CoreDNS的服务发现能力,两者如何打通是很多团队落地时会遇到的实际问题。本文将围绕Consul与K8s DNS的集成展开,覆盖架构原理、部署配置和方案对比。

一、为什么需要Consul与K8s DNS集成
Kubernetes原生的服务发现依赖Service和DNS,Pod通过环境变量或域名访问服务,这在纯K8s环境里工作得很好。但现实架构往往更复杂:Agent服务可能同时部署在虚拟机、裸金属和多个K8s集群上,这些节点之间需要互相发现。Consul天然支持多数据中心和异构环境,只要进程能连上Consul Agent,就能完成注册与查询。
问题在于,K8s内的应用习惯用统一DNS后缀(如cluster.local)解析一切服务。如果不做集成,应用访问Consul中的服务就必须改代码或者引入Consul SDK,改造成本高,也失去了K8s DNS的透明性。集成的核心目标就是让Pod能够直接通过类似agent-svc.service.consul这样的域名解析到Consul中注册的服务,而集群原有Service的解析不受影响。
常见的集成思路有两种:一是让CoreDNS把consul后缀的请求转发给Consul DNS,二是通过consul-k8s组件把K8s Service同步到Consul目录。前者偏向查询侧打通,后者偏向注册侧打通,两者也可以组合使用。
二、CoreDNS转发配置详解
Consul Agent本身内置一个DNS服务器,默认监听8600端口,支持UDP和TCP。只要把CoreDNS中对.consul后缀的查询转发到Consul的DNS端口,Pod内的域名解析就能自动落到Consul上。这是侵入性最小的方案,应用代码完全不需要改动。
先确认CoreDNS的ConfigMap,添加一个forward插件配置块:
kubectl edit configmap coredns -n kube-system
在Corefile中加入如下配置:
consul:53 {
errors
cache 30
forward . 10.96.0.20:8600
}
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
forward . /etc/resolv.conf
cache 30
}
这里的10.96.0.20应替换为Consul Server所在Service的ClusterIP,也可以直接使用Consul的Service域名。注意转发地址不能写成127.0.0.1,因为CoreDNS Pod与Consul并不在同一个网络命名空间。配置保存后CoreDNS会自动重载,接下来注册一个测试服务验证:
# 在Consul中注册一个Agent服务
curl -X PUT -d '{
"Name": "agent-svc",
"Address": "10.244.1.15",
"Port": 8080,
"Check": {
"HTTP": "http://10.244.1.15:8080/health",
"Interval": "10s"
}
}' http://consul-server:8500/v1/agent/service/register
# 在任意Pod内验证解析
nslookup agent-svc.service.consul
如果解析返回了10.244.1.15,说明转发链路已经打通。还要注意一点:Consul DNS默认只返回健康实例的地址,健康检查失败的服务会直接解析不到,这在故障排查时容易误判为DNS问题,实际是健康检查没通过。
三、使用consul-k8s组件双向同步服务
转发方案解决了查询问题,但如果希望K8s里的Service自动出现在Consul目录中,让集群外的虚拟机Agent也能发现它们,就需要consul-k8s组件。它是HashiCorp官方提供的Helm Chart,会在集群内安装Sync组件、注入Webhook和Consul Client DaemonSet。
安装方式如下:
helm repo add hashicorp https://helm.releases.hashicorp.com helm install consul hashicorp/consul \ --namespace consul --create-namespace \ --set global.name=consul \ --set connectInject.enabled=true \ --set syncCatalog.enabled=true \ --set dns.enabled=false
关键参数说明:syncCatalog.enabled开启后,Sync进程会监听K8s的Service资源变化,把带有特定注解的Service同步到Consul;connectInject.enabled用于Sidecar注入,配合Service Mesh使用;dns.enabled=false是因为我们已经用CoreDNS做转发,避免Consul再抢DNS职责产生冲突。
默认情况下,Sync只同步带有consul.hashicorp.com/service-sync=true注解的Service,这个行为可以按需调整:
apiVersion: v1
kind: Service
metadata:
name: agent-api
annotations:
consul.hashicorp.com/service-sync: "true"
spec:
selector:
app: agent-api
ports:
- port: 8080
targetPort: 8080
同步成功后,在Consul UI或通过API就能看到agent-api服务,集群外的Agent节点查询agent-api.service.consul即可拿到K8s Pod地址。反向同步(Consul服务写回K8s)在旧版本中支持有限,一般还是推荐集群外节点直接部署Consul Client去查询。
四、方案对比与选型建议
两种方案各有适用场景,可以从运维成本、故障域和功能需求三个维度评估:
| 维度 | CoreDNS转发 | consul-k8s同步 |
|---|---|---|
| 部署复杂度 | 低,改一处Corefile | 高,需安装整套组件 |
| 服务可见方向 | K8s内查Consul服务 | Consul侧可见K8s服务 |
| 对集群侵入 | 几乎无 | 需注入Sidecar与DaemonSet |
| 健康检查 | 沿用Consul原生检查 | 结合K8s探针状态 |
| 适用场景 | 混合环境统一查询 | 多集群联邦、Service Mesh |
实践中的建议是:如果只是让K8s内的应用能调用Consul里注册的传统Agent服务,CoreDNS转发完全够用,配置简单且故障面小;如果存在多个K8s集群互相发现,或者计划使用Consul Connect做流量治理,那么投入成本部署consul-k8s更划算,它的Catalog同步和注入能力是转发方案给不了的。
还有几个容易踩的坑需要提醒。第一,Consul DNS对UDP报文有512字节限制,实例较多时会截断,客户端需要支持TCP回退,dig命令可加+tcp验证。第二,CoreDNS的cache插件会缓存Consul的健康状态,故障摘除会有最多缓存时长的延迟,生产环境建议把缓存时间控制在30秒以内。第三,跨数据中心场景下要开启Consul的WAN Gossip,否则转发生效后也可能解析不到远端数据中心的服务。
总的来说,Consul与K8s DNS集成并不复杂,关键是想清楚服务发现的流量方向:是集群内消费外部服务,还是外部消费集群内服务,或者双向都要。方向确定后再选择转发或同步方案,配合健康检查与缓存策略调优,就能构建一套对业务透明的Agent服务发现体系。