如果你已经在 Kubernetes 里跑业务,迟早会遇到服务注册和发现的问题。我的选择一直是 Consul,不是因为它花哨,而是它把服务发现、健康检查、KV 存储、多数据中心这些事打包得很干净,配合 K8s 的调度能力,一套高可用的 Consul 集群落地并不算复杂。这篇笔记就是基于我实际在 K8s 上部署 Consul 集群的完整过程,把环境选型、配置文件、踩坑记录和排障思路一并整理出来,给正准备动手的人一条能直接照着走的路径。
1. 为什么要在 K8s 上跑 Consul
1.1 它解决的是哪类痛点
K8s 自己有一套内置的服务发现机制,Service 和 EndpointSlice 能在集群内部做好负载均衡。那为什么还要把 Consul 搬进来?我见过最多的场景是混合架构。
比如一部分业务在 Kubernetes 里,另一部分业务还在传统虚拟机或物理机上跑着。K8s 的 Service 只能在集群内生效,跨集群、跨数据中心的调用就成了问题。Consul 可以同时运行在 K8s 内部和外部节点上,注册到同一个 Consul 集群,这样无论服务跑在哪,都能通过同一套注册中心完成发现和路由。再加上 Consul 的 DNS 接口,可以让非 K8s 环境的老应用直接用域名方式访问 K8s 里的新服务,改造成本极低。
另一个痛点是健康检查的视角不同。K8s 的探针是从 Kubelet 视角去探测,Consul 支持更灵活的脚本、HTTP、TCP 检查,并且可以从多个节点、多个数据中心维度去判断服务健康状态。对多活、容灾场景来说,这一层能力是 K8s 原生方案不提供的。
1.2 部署方式的思路取舍
部署 Consul 集群,通常有三条路:直接用二进制跑在虚拟机里、写一堆 YAML 手工部署在 K8s 上、使用官方 Helm Chart 部署。
我个人的经验是:如果只是测试玩一下,手工 YAML 确实能帮你理解组件关系;但如果是生产环境,直接用官方 Helm Chart 是最稳妥的。原因很简单,Consul 在 K8s 上的部署不只是把几个 Pod 拉起来,还涉及 Service 暴露方式、PVC 持久化、ACL 初始化、自动加入集群的配置、Connect 注入器的 Webhook 证书等。这些细节手工写容易漏,漏一个都够排查半天。
Helm Chart 把这些问题都封装好了,默认值虽然不会直接满足生产要求,但至少保证你用默认配置也能跑起来,不会一上来就碰到证书不对、节点无法互相加入这种基础问题。后面我会详细说怎么调整 values.yaml。
2. 环境准备:K8s 版本、Helm 与存储选型
2.1 版本组合怎么定
先说我这次部署用的版本组合:Kubernetes 1.28,Helm 3.14,Consul Helm Chart 1.4.x,Consul 镜像版本 1.18.x。这个组合并不特殊,但经历过多轮验证,稳定性可以放心。
需要注意的是,Helm Chart 版本和 Consul 版本并不是完全绑定的。老 Chart 也能拉新镜像,但不建议这么做,因为 Chart 里的 RBAC、CRD、Webhook 配置可能跟不上新版本的 Consul 特性。更稳妥的做法是打开官方 Chart 的 values.yaml,查看默认镜像 tag,再决定是否升级。
如果 K8s 版本太老,比如还在 1.20 以下,就需要留意 Chart 对 API 版本的要求。新版本 Chart 已经默认使用 apps/v1、policy/v1 这些稳定 API,老集群可能不支持。部署前建议先跑一下helm install --dry-run --debug,它会把渲染出来的资源和 API 版本都打出来,一眼就能看出兼容性问题。
2.2 StorageClass 是持久化的前提
Consul Server 节点保存的是注册数据、KV 配置、ACL token 等信息,这些数据必须落到持久化存储上。在 K8s 里就是 StatefulSet 加 PVC。PVC 能不能正常创建,完全取决于集群里的 StorageClass。
如果你用的是云厂商托管 K8s,一般都有默认的 StorageClass,比如标准的云盘类型。如果是自建 K8s,就需要先确认有没有部署支持动态供应的存储插件,比如 NFS Subdir External Provisioner、Rook Ceph 这类方案。
我这次测试环境用的是高可用存储类,配置了 10Gi 的持久化卷。Consul Server 数据量通常不会特别大,但如果开启了 Connect 和 ACL,并且运行了很久,KV 和 token 数据会缓慢增长,建议至少留 10Gi 以上的空间,并且提前规划扩容流程。
2.3 Helm Chart 还是手工 YAML
很多人会纠结要不要用 Helm Chart,怕它封装太多不好排查。其实完全相反,Helm 渲染后生成的 YAML 是完整的,kubectl get statefulset consul-server -o yaml就能看到最终实际生效的那份配置。
用 Helm 的好处是升级路径清晰。Consul 的架构里,Server 节点升级顺序、Client 节点灰度方式、ACL 迁移流程,Chart 都帮你编排好了。手工 YAML 一旦版本升级,这些流程全部要自己重新梳理,风险高出不少。
还有一个隐藏坑:官方 Chart 默认会创建几个 Service,包括 Consul Server 的头less服务、Client 的 Service、UI 的 Service。手工 YAML 很容易漏掉 Client 的-server访问方式,最后导致 agent 无法找到 Server 节点。
3. 核心概念与配置参数解析
3.1 Consul 集群的角色分工
Consul 集群里有两类核心角色:Server 和 Client。在 K8s 部署中,这两类角色分别以 StatefulSet 和 DaemonSet 的形式运行。
Server 节点负责存储状态、处理查询、参与选举。生产环境建议至少 3 个副本,并且分布在不同的可用区。每个 Server 节点都是一个有状态的 Pod,对应一个 PVC。如果副本数是 1,看起来也能跑,但一旦节点故障,注册数据可能丢失,不满足高可用要求。
Client 节点是无状态的,通常以 DaemonSet 方式在每个 Node 上跑一个。它的作用是代理本地服务的注册和查询,把请求转发给 Server 节点。即使你的 Pod 数量很多,Client 也不会成为瓶颈,因为每个节点只承担本机服务的转发压力。
还有一类角色叫 Controller,是 K8s 部署特有的。它负责协调 Service 注册、Endpoint 更新、Webhook 证书管理。如果关闭 Controller,很多自动化联动功能会失效,建议保持开启。
3.2 持久化、存储与数据安全
Consul Server 的数据目录挂在/consul/data路径下。Helm Chart 通过server.persistence参数控制是否创建 PVC。默认配置下需要显式开启:
server: persistence: enabled: true storageClass: "your-storage-class" size: 10Gi这里有个容易忽视的问题:PersistentVolume 的回收策略和 ReclaimPolicy。如果你的 StorageClass 回收策略是 Delete,删除 PVC 时数据就没了。有些场景下反而不是坏事,因为可以快速重置环境;但对生产环境,建议把重要 Consul 集群的 PV 回收策略改为 Retain,至少给数据找回留一点余地。
ACL token 默认存储在 Server 的数据目录里。如果 PVC 丢失,所有 token 都要重新初始化,服务注册信息也会全部失效。所以持久化不只是“跑起来”的问题,更是“数据安全”的问题。
3.3 网络模型与 DNS 访问方式
Consul 集群内部节点发现依赖两个关键端口:8500 是 HTTP API 和 gRPC 端口,8301 是 Serf LAN 端口。在 K8s 里,Serf LAN 通信需要 Pod 之间网络互通,默认 CNI 插件都满足条件,一般不用额外处理。
Consul 的 DNS 服务默认监听 8600 端口。在 K8s 里,如果你想通过service.consul这种域名解析服务,需要把 Consul DNS 接入 CoreDNS。Helm Chart 里相关配置是dns.enabled和dns.proxy。开启后,CoreDNS 会把consul后缀的域名转发给 Consul Client 的 DNS 服务。
这里要注意,开启 DNS 代理后,CoreDNS 配置文件会被修改,影响面比较大。如果只是测试,可以直接用 Consul Client Pod 的 IP 发起 DNS 查询,先验证功能;确认需要全局解析后,再开启 DNS 代理,避免一开始就改动集群级网络配置。
4. 完整实操:一步步部署 Consul 集群
4.1 安装 Helm 并添加官方仓库
第一步是准备 Helm 环境。Helm 是部署 Consul 集群最省心的工具,它把一堆复杂的 YAML 编排成一套可参数的 Chart,升级回滚也方便。如果你的机器还没有安装 Helm,可以到 Helm 官方文档按对应系统的安装方式装好,macOS 上通常一条命令就能完成。
添加 Consul 官方 Helm 仓库:
helm repo add hashicorp https://helm.releases.hashicorp.com helm repo update然后你可以先检查一下最新可用的 Chart 版本:
helm search repo hashicorp/consul我这次用的是 1.4.2 版本,相比更早的版本,它在 ACL 默认策略和 gRPC 配置上更完善。如果你想了解当前版本的所有参数,执行:
helm show values hashicorp/consul > consul-values.yaml把默认 values 拉下来,后续的配置修改就是在这个基础上进行。
4.2 核心 values.yaml 配置解析
下面是我的最小可用配置,同时兼顾了测试环境的功能完整性和资源成本:
global: name: consul image: "hashicorp/consul:1.18.2" imagePullPolicy: IfNotPresent server: replicas: 1 bootstrapExpect: 1 storage: 10Gi storageClass: "standard" resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "1" client: enabled: true grpc: true ui: enabled: true dns: enabled: true enableRedirection: true controller: enabled: true这个配置里,Server 副本数是 1。我这里这么写是因为测试环境资源有限,但你要清楚,这不是高可用配置。生产至少 3 副本,并且bootstrapExpect要设置为 3,Consul 才会自动选出 Leader 并完成初始化。
dns.enabled开启后,Consul 会尝试修改 CoreDNS。第一次跑的时候建议先只看dns.enabled: true,看看 Pod 日志有没有报错。我在测试中遇到过一个情况:CoreDNS 开启了自动重载插件后,Consul 的 DNS 转发配置和现有配置冲突,导致部分内部域名解析超时。后来是关了dns.enableRedirection才恢复正常。
如果你需要完整的 ACL 和 Connect 支持,再加这些配置:
global: acls: manageSystemACLs: true connect: inject: enabled: true需要提醒你,开启manageSystemACLs以后,consul-k8s controller 会创建和管理 token,部署过程依赖 Job 顺序,如果图表升级过程中中断,令牌可能不一致。所以建议第一次部署时不要急着开启 ACL,先跑通基础功能,再加安全层。
4.3 执行安装并验证集群状态
写好 values.yaml 后,执行安装:
helm install consul hashicorp/consul -f consul-values.yaml -n consul --create-namespace安装完成后,先看 Pod 状态:
kubectl get pods -n consul -o wide正常情况下,你会看到 consul-server 和 consul-client Pod 都处于 Running 状态。Server Pod 只有一个副本时,Consul 也会正常选出 Leader,并且日志里会有agent.server: New leader elected之类的记录。
接着验证集群成员:
kubectl exec -n consul consul-server-0 -- consul members输出里应该能看到 Server 节点状态为 alive,Client 节点也会注册进来。如果只有 Server 节点,说明 Client 没有正确加入集群,一般是网络策略或客户端配置问题。
验证 HTTP API 是否可用:
kubectl exec -n consul consul-server-0 -- consul operator raft list-peers能看到 Raft Peers 列表就说明集群内部通信正常。如果 Client 节点状态是 left 或 failed,优先查看对应节点 Pod 的日志,并确认 8301 端口是否互通。
UI 服务确认:
kubectl get svc -n consul会看到consul-ui这个 Service。测试时简单暴露给本机访问,可以用 port-forward:
kubectl port-forward -n consul svc/consul-ui 8500:80浏览器打开http://localhost:8500就能看到界面。如果是生产环境,不要直接暴露 Service,建议用 Ingress 或配置安全的访问通道。
5. 常见问题与排查实录
5.1 Server Pod 一直处于 Pending 状态
我在第一次部署时,遇到最频繁的问题就是consul-server-0停在 Pending。大部分原因是 PVC 无法动态创建。排查顺序是:
kubectl describe pvc -n consul kubectl get sc kubectl describe pod consul-server-0 -n consulPVC 的 Event 里如果显示waiting for a volume to be created, either by external provisioner or manually created,就说明 StorageClass 没有生效。检查集群里已有的 StorageClass:
kubectl get storageclass如果没有默认 StorageClass,直接把storageClass字段指定为实际存在的类名。
还有一种情况是节点资源不足。Consul Server 有资源 requests 设置,如果 Node 上没有足够内存或 CPU,调度器不会把 Pod 调度上去。看 Pod 的 Scheduling 相关事件就能定位。
5.2 Client 节点状态异常,Server 找不到 Client
Client 以 DaemonSet 方式部署,理论上每个节点都会运行一个。如果consul members看不到 Client,第一步先看 DaemonSet 是否正常:
kubectl get daemonset -n consul如果 DaemonSet 存在但 Pod 没起来,看日志通常会得到线索。比较常见的原因是镜像拉取失败,或者客户端启动参数里retry_join指定的 Server 地址不对。Helm Chart 通常用<release-name>-server这个 Service 名去解析 Server 节点,如果命名冲突,Client 需要单独调整retryJoin参数。
另一个容易被忽略的地方是 Kubernetes 网络策略。如果你在集群里开启了 NetworkPolicy,必须放行 Consul 端口段,包括 8301、8300、8500、8600。测试环境如果不涉及安全合规,可以直接先全放行,排清问题再加策略。
5.3 UI 开启后访问空白或 404
UI 正常部署后,如果端口转发成功但页面空白,先确认后端接口是否返回数据:
curl http://localhost:8500/v1/status/leader curl http://localhost:8500/v1/catalog/services如果 API 正常,UI 空白的常见原因是浏览器缓存了旧的前端资源。强制刷新或换无痕模式通常能解决。
如果 API 也异常,说明 Consul 的 HTTP 端口没有对外监听,检查 Server Pod 日志里是否有bind相关错误。还有一个容易踩的坑:Consul 默认只监听 127.0.0.1 时 UI 无法通过 Service 访问,需要确保配置里client_addr包含了 Pod IP 或0.0.0.0。Helm Chart 会处理好这个,但手工 YAML 场景非常常见。
5.4 ACL 开启后服务注册失败
ACL 是 Consul 部署里最让人头疼的一层。如果你的应用直接调用 Consul API 注册服务,必须带上 token。使用 Consul 官方 SDK 时,通常需要设置环境变量:
export CONSUL_HTTP_TOKEN=<token>K8s 里跑的业务 Pod,如果通过 Consul Client 注册,需要在应用配置里注入 token 文件或环境变量。开启 ACL 后,未授权操作会在日志里留下Permission denied信息。
如果只是测试环境,不建议一开始就开启完整 ACL。先把集群基础功能跑通,再参照官方文档逐步开启,这样可以省下很多排查时间。
6. 生产环境加固与经验建议
6.1 多副本与反亲和配置
生产环境至少有 3 个 Server 副本,并且要配置 Pod 反亲和,让 Server 尽可能分布在不同节点上。下面这段配置可以加到 values.yaml:
server: replicas: 3 bootstrapExpect: 3 affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: consul component: server topologyKey: kubernetes.io/hostname如果不做反亲和,三个副本同时落在一个节点上,这个节点一挂,集群就会因为失去多数派而不可用。这是生产环境最常见的高可用隐患,很多人以为副本多就安全了,其实分布比数量更重要。
PodDisruptionBudget 也要配。给 Consul Server 配置 PDB,保证在任何主动节点维护时,最多只有一个副本不可用:
server: disruptionBudget: enabled: true maxUnavailable: 16.2 备份与恢复流程
Consul 的数据备份应该包含两部分:快照(snapshot)和 ACL token 配置。快照可以用consul snapshot命令完成。在 K8s 里,最常见的备份方式是创建定时任务:
kubectl exec -n consul consul-server-0 -- consul snapshot save /tmp/backup.snap kubectl cp consul/consul-server-0:/tmp/backup.snap ./backup.snap但是这里有个坑:不要只依赖kubectl cp把快照拷出来,一旦 Pod 重建,数据就丢失了。应该把快照上传到对象存储或者挂载的远程卷里。更可靠的办法是部署一个 CronJob,定期执行 snapshot,然后上传到远程存储。
恢复的大致流程是:先停掉集群,或者用consul operator raft remove-peer排除故障节点,再在单节点上恢复快照,最后重新拉起其他节点。恢复前一定要先完整备份当前状态,避免二次损坏。
6.3 监控与告警要点
Consul 本身会暴露 Prometheus 格式的指标,主要端口是 8500 上的/v1/agent/metrics。在 K8s 里,可以通过 ServiceMonitor 把这些指标接入 Prometheus 生态:
serviceMonitor: enabled: true interval: 30s建议关注的指标包括:consul_raft_leader、consul_raft_peers、consul_health_node_status、consul_catalog_service_deregister等。Leader 切换频繁或 Peer 数量不对,说明集群状态异常,需要及时介入。
我还习惯在告警规则里加上一条:Server 节点重启次数。StatefulSet 的 Pod 如果频繁重启,说明可能出现了 OOM 或磁盘问题。看起来是小问题,但因为 Consul 状态依赖持久化,频繁重启可能导致状态不一致,最终影响服务发现。
6.4 最后再分享两个小技巧
第一个是升级 Consul 版本时,不要一次性跨多个大版本,尤其是带 ACL 的集群。保守的做法是:先备份快照,再升级 Helm Chart,然后手动把镜像 tag 改成目标版本,观察 Server 日志确认 Leader 切换和同步正常后,再升级其他组件。
第二个是变更配置时,养好习惯先把 values.yaml 放到 Git 仓库管理。Consul 集群配置不像普通应用,一个错误的配置可能让整个集群的服务发现瘫痪。有版本记录,回滚就快很多。
我在实际执行中还发现,部署 Consul 集群最大的风险往往不是技术本身,而是“临时改一下”没有记录。很多问题发生在一周后,根本想不起来当初改了哪个参数。所以无论你用什么部署工具,落地文档和配置文件版本管理,一定要从第一天就建立起来。