1. 为什么在 K8S 上部署 Nacos 集群值得认真对待
Nacos 在微服务架构里扮演的角色,用过的人都知道——注册中心和配置中心二合一,服务发现、动态配置、健康检查一把抓。但真到了生产环境,单机版 Nacos 就是个玩具,一旦挂掉整个服务治理链路直接瘫痪。所以高可用集群是必选项,没有商量余地。
问题在于,Nacos 集群的传统部署方式对运维太不友好。早期大家习惯用 Docker Compose 或者直接物理机部署,手动分配节点、配置 MySQL 共享存储、维护集群成员列表,每次扩缩容都像拆炸弹。Kubernetes 的 StatefulSet 恰好解决了这类有状态服务的痛点:稳定的网络标识、有序的部署和扩缩、持久化存储卷,这些特性跟 Nacos 集群的需求高度吻合。
我这次要聊的,就是基于 Nacos 2.0 版本,用 StatefulSet 在 K8S 上搭一套真正能扛生产流量的高可用集群。Nacos 2.0 跟 1.x 在通信模型上有本质区别——2.0 引入了 gRPC 长连接,性能大幅提升,但端口和网络配置也跟着变了,很多人踩坑就踩在这里。这篇文章会从架构设计、存储选型、StatefulSet 编排、Nacos 集群配置、MySQL 高可用对接,一直到实际部署和问题排查,把整个链路拆开讲透。
适合谁看?如果你已经对 K8S 的基本概念(Pod、Service、PVC)有了解,对 Nacos 的使用也不陌生,但还没在生产环境完整部署过 Nacos 集群,那这篇内容就是给你准备的。我会尽量把每个决策背后的“为什么”讲清楚,而不是甩一堆 YAML 让你自己猜。
2. 整体架构设计与核心思路拆解
2.1 为什么选 StatefulSet 而不是 Deployment
Deployment 管的是无状态服务,Pod 名字随机、网络标识不固定、启动顺序不可控。Nacos 集群恰恰需要这三样东西的反面:
- 稳定的网络标识:Nacos 集群节点之间需要互相通信,每个节点要能通过固定地址找到其他节点。StatefulSet 给每个 Pod 分配一个稳定的 DNS 名称,格式是
<pod-name>.<service-name>.<namespace>.svc.cluster.local,这个名称在 Pod 重建后依然不变。 - 有序的启动和停止:Nacos 集群启动时需要先确认其他节点的状态,如果所有节点同时启动,容易出现选主混乱。StatefulSet 默认按序号从 0 到 N-1 依次启动,停止时逆序执行,这个顺序保证对集群初始化非常关键。
- 持久化存储:Nacos 的 data 目录需要持久化,虽然配置数据存在 MySQL 里,但本地缓存和日志对排查问题很重要。StatefulSet 的 volumeClaimTemplates 能为每个 Pod 自动创建独立的 PVC,Pod 漂移后数据不丢。
注意:Nacos 2.0 之后,集群节点间的通信同时依赖 HTTP 和 gRPC,StatefulSet 的稳定网络标识对 gRPC 长连接的建立至关重要。如果用 Deployment,Pod 重建后 IP 变化,其他节点需要重新建立连接,会造成短暂的通信中断。
2.2 Nacos 2.0 的端口变化与网络规划
这是很多人从 1.x 升级到 2.0 时最容易翻车的地方。Nacos 1.x 只需要暴露一个 8848 端口(HTTP),但 2.0 新增了 gRPC 通信,需要额外开放两个端口:
| 端口 | 用途 | 说明 |
|---|---|---|
| 8848 | HTTP API | 控制台、OpenAPI、服务注册发现 |
| 9848 | gRPC 客户端 | 客户端与 Nacos 服务端通信,偏移量 = 8848 + 1000 |
| 9849 | gRPC 服务端 | 集群节点间通信,偏移量 = 8848 + 1001 |
这三个端口必须在 Service 和容器端口映射中全部暴露。我见过不少人只配了 8848,结果客户端注册服务时报连接超时,排查半天才发现是 gRPC 端口没开。
另外,Nacos 2.0 的 gRPC 通信默认使用nacos.server.ip或者 Pod IP 进行节点间通信。在 K8S 环境下,Pod IP 是动态分配的,所以必须显式配置nacos.inetutils.ip-address或者使用 Headless Service 的 DNS 解析。我的做法是直接用 Headless Service 的完整域名,避免 IP 变化带来的问题。
2.3 存储选型:MySQL 还是内置 Derby
Nacos 支持两种存储模式:内置 Derby 和外部 MySQL。单机模式下 Derby 够用,但集群模式必须用外部 MySQL,因为 Derby 不支持多节点共享数据。
MySQL 的高可用方案这里不展开讲,但至少要保证 Nacos 连接的 MySQL 是主从架构或者 MGR 集群,否则 MySQL 单点故障照样会导致 Nacos 集群不可用。我这次部署假设你已经有一个可用的 MySQL 高可用环境,后面会讲怎么对接。
提示:Nacos 2.0 的数据库表结构跟 1.x 有差异,部署前务必用 2.0 版本自带的
mysql-schema.sql初始化数据库,不要直接复用 1.x 的表结构。
3. 核心细节解析与实操要点
3.1 Headless Service 的配置要点
StatefulSet 必须配合 Headless Service 使用,这是铁律。Headless Service 的特点是clusterIP: None,它不分配 ClusterIP,而是直接返回 Pod 的 DNS 记录。
apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: nacos labels: app: nacos spec: clusterIP: None selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc-client port: 9848 targetPort: 9848 - name: grpc-server port: 9849 targetPort: 9849这个 Headless Service 的作用是让每个 Nacos Pod 都能通过<pod-name>.nacos-headless.nacos.svc.cluster.local这样的域名互相访问。比如nacos-0.nacos-headless.nacos.svc.cluster.local就指向第一个 Pod。
3.2 StatefulSet 的 volumeClaimTemplates 设计
Nacos 需要持久化的目录主要是/home/nacos/data和/home/nacos/logs。data 目录存放本地缓存和协议数据,logs 目录存放运行日志。
volumeClaimTemplates: - metadata: name: nacos-data spec: accessModes: ["ReadWriteOnce"] storageClassName: "nfs-storage" resources: requests: storage: 10Gi这里有几个细节值得说:
- storageClassName要根据你的 K8S 集群实际存储方案来填。如果用 NFS,需要提前装好 NFS Provisioner;如果用云厂商的块存储,填对应的 StorageClass 名称。
- accessModes选
ReadWriteOnce就够了,因为每个 Pod 用自己的 PVC,不需要共享。 - 存储大小建议至少 10Gi,data 目录虽然不大,但日志增长可能比较快,留足余量。
3.3 Nacos 集群配置的关键参数
Nacos 集群模式的核心配置在application.properties和cluster.conf两个文件里。在 K8S 环境下,我推荐用环境变量注入配置,而不是挂载配置文件,这样更灵活。
关键环境变量:
| 变量名 | 值 | 说明 |
|---|---|---|
| MODE | cluster | 必须设为 cluster,否则启动单机模式 |
| NACOS_SERVERS | nacos-0.nacos-headless...:8848 nacos-1...:8848 nacos-2...:8848 | 集群节点列表 |
| SPRING_DATASOURCE_PLATFORM | mysql | 使用 MySQL 存储 |
| MYSQL_SERVICE_HOST | mysql-host | MySQL 地址 |
| MYSQL_SERVICE_DB_NAME | nacos_config | 数据库名 |
| MYSQL_SERVICE_USER | nacos | 数据库用户 |
| MYSQL_SERVICE_PASSWORD | password | 数据库密码 |
| NACOS_SERVER_PORT | 8848 | 服务端口 |
注意:
NACOS_SERVERS里的地址必须用完整的 Headless Service 域名,不能用 Pod IP。因为 Pod 重建后 IP 会变,但域名不变。
3.4 健康检查与就绪探针
Nacos 2.0 的健康检查端点跟 1.x 不同。1.x 用/nacos/actuator/health,2.0 虽然也保留了这个端点,但 gRPC 端口的健康检查需要额外处理。
livenessProbe: httpGet: path: /nacos/actuator/health port: 8848 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: path: /nacos/actuator/health port: 8848 initialDelaySeconds: 20 periodSeconds: 5 failureThreshold: 3initialDelaySeconds设 30 秒是因为 Nacos 启动需要连接 MySQL、初始化集群状态,太快检查会误判。failureThreshold设 3 是给集群选主留出缓冲时间。
4. 完整实操过程与核心环节实现
4.1 环境准备与前置条件
开始之前,确认以下条件已满足:
- K8S 集群版本 1.20 以上,支持 StatefulSet 的稳定特性。
- 集群已配置默认 StorageClass,或者你明确知道要用哪个 StorageClass。
- MySQL 高可用环境已就绪,版本 5.7 或 8.0 均可。
- 已创建独立的 namespace,比如
nacos。
kubectl create namespace nacos4.2 初始化 MySQL 数据库
从 Nacos 2.0 的发布包中取出mysql-schema.sql,在 MySQL 中执行:
mysql -h mysql-host -u root -pCREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE nacos_config; SOURCE /path/to/mysql-schema.sql;创建专用用户并授权:
CREATE USER 'nacos'@'%' IDENTIFIED BY 'YourStrongPassword'; GRANT ALL PRIVILEGES ON nacos_config.* TO 'nacos'@'%'; FLUSH PRIVILEGES;提示:生产环境不要用 root 用户连接 Nacos,权限最小化原则。密码要足够复杂,避免被暴力破解。
4.3 编写 StatefulSet YAML
下面是完整的 StatefulSet 配置,我加了详细注释:
apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: nacos spec: serviceName: nacos-headless replicas: 3 podManagementPolicy: OrderedReady selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - nacos topologyKey: kubernetes.io/hostname containers: - name: nacos image: nacos/nacos-server:v2.0.4 ports: - containerPort: 8848 name: http - containerPort: 9848 name: grpc-client - containerPort: 9849 name: grpc-server env: - name: MODE value: "cluster" - name: NACOS_SERVERS value: "nacos-0.nacos-headless.nacos.svc.cluster.local:8848 nacos-1.nacos-headless.nacos.svc.cluster.local:8848 nacos-2.nacos-headless.nacos.svc.cluster.local:8848" - name: SPRING_DATASOURCE_PLATFORM value: "mysql" - name: MYSQL_SERVICE_HOST value: "mysql-host" - name: MYSQL_SERVICE_PORT value: "3306" - name: MYSQL_SERVICE_DB_NAME value: "nacos_config" - name: MYSQL_SERVICE_USER value: "nacos" - name: MYSQL_SERVICE_PASSWORD value: "YourStrongPassword" - name: NACOS_SERVER_PORT value: "8848" - name: JVM_XMS value: "2g" - name: JVM_XMX value: "2g" - name: JVM_XMN value: "1g" volumeMounts: - name: nacos-data mountPath: /home/nacos/data - name: nacos-logs mountPath: /home/nacos/logs livenessProbe: httpGet: path: /nacos/actuator/health port: 8848 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /nacos/actuator/health port: 8848 initialDelaySeconds: 20 periodSeconds: 5 resources: requests: memory: "4Gi" cpu: "2" limits: memory: "4Gi" cpu: "2" volumeClaimTemplates: - metadata: name: nacos-data spec: accessModes: ["ReadWriteOnce"] storageClassName: "nfs-storage" resources: requests: storage: 10Gi - metadata: name: nacos-logs spec: accessModes: ["ReadWriteOnce"] storageClassName: "nfs-storage" resources: requests: storage: 10Gi几个关键点解释:
- podAntiAffinity:尽量让三个 Nacos Pod 调度到不同节点上,避免单节点故障导致集群不可用。用
preferred而不是required,是因为如果节点不够,至少还能调度成功。 - JVM 参数:Nacos 默认 JVM 堆内存较小,生产环境建议调到 2G 以上。
JVM_XMN是新生代大小,设为堆内存的一半比较合理。 - resources:requests 和 limits 设成一样,保证 QoS 等级为 Guaranteed,避免被其他 Pod 挤占资源。
4.4 部署与验证
应用 YAML:
kubectl apply -f nacos-headless-service.yaml kubectl apply -f nacos-statefulset.yaml观察 Pod 启动顺序:
kubectl get pods -n nacos -w你会看到nacos-0先启动,等它 Ready 之后nacos-1才开始启动,最后是nacos-2。这是 StatefulSet 的 OrderedReady 策略在起作用。
验证集群状态:
kubectl exec -it nacos-0 -n nacos -- curl http://localhost:8848/nacos/v1/ns/operator/servers返回的 JSON 里应该包含三个节点的信息,state字段为UP表示节点健康。
4.5 暴露控制台访问
创建一个 NodePort 或 LoadBalancer 类型的 Service 来暴露控制台:
apiVersion: v1 kind: Service metadata: name: nacos-console namespace: nacos spec: type: NodePort selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 nodePort: 30848浏览器访问http://<任意节点IP>:30848/nacos,默认用户名密码都是nacos。登录后进入“集群管理”页面,应该能看到三个节点,且都是健康状态。
注意:生产环境建议用 Ingress 加 TLS 证书暴露控制台,不要直接用 NodePort,安全性太差。而且控制台默认密码必须改掉。
5. 常见问题与排查技巧实录
5.1 节点启动后无法组成集群
现象:三个 Pod 都 Running,但集群管理页面只显示一个节点,或者节点状态是DOWN。
排查思路:
- 检查
NACOS_SERVERS环境变量是否正确注入:kubectl exec -it nacos-0 -n nacos -- env | grep NACOS_SERVERS - 检查 Pod 之间网络是否互通:
kubectl exec -it nacos-0 -n nacos -- ping nacos-1.nacos-headless.nacos.svc.cluster.local - 检查 gRPC 端口是否监听:
kubectl exec -it nacos-0 -n nacos -- netstat -tlnp | grep 984
最常见的原因是NACOS_SERVERS里用了 Pod IP 而不是域名,或者 Headless Service 的 selector 跟 StatefulSet 的 labels 不匹配。
5.2 客户端注册服务超时
现象:应用启动时连接 Nacos 报Client not connected或Server is DOWN。
排查思路:
- 确认客户端配置的地址是 Nacos 的 Service 地址,而不是 Pod IP。
- 确认 9848 端口在 Service 中已暴露。Nacos 2.0 客户端会先连 8848 获取 gRPC 端口,然后连 9848 建立长连接。
- 检查网络策略是否限制了 9848 端口的流量。
提示:如果你用的是 Spring Cloud Alibaba,确保版本跟 Nacos 2.0 兼容。Spring Cloud Alibaba 2021.0.1.0 以上版本才完整支持 Nacos 2.0 的 gRPC 通信。
5.3 MySQL 连接池耗尽
现象:Nacos 运行一段时间后报Communications link failure或Too many connections。
排查思路:
- 检查 Nacos 的数据库连接池配置,默认最大连接数是 20,三个节点就是 60。如果 MySQL 的
max_connections设得比较小,容易耗尽。 - 调整 Nacos 的
db.pool.config.maxActive参数,或者调大 MySQL 的max_connections。 - 检查是否有慢查询导致连接被长时间占用。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| Pod 启动后 CrashLoopBackOff | MySQL 连接失败 | 检查数据库地址、用户名密码、网络连通性 |
| 集群节点显示不全 | NACOS_SERVERS 配置错误 | 使用 Headless Service 完整域名 |
| 客户端连接超时 | gRPC 端口未暴露 | 确保 9848、9849 端口在 Service 中开放 |
| 控制台无法访问 | Service 类型或端口错误 | 检查 NodePort 或 Ingress 配置 |
| 数据不一致 | 多个 Nacos 连了不同的 MySQL | 确认所有节点使用同一个数据库 |
| 启动极慢 | JVM 内存不足或 MySQL 响应慢 | 调大 JVM 堆内存,优化 MySQL 性能 |
5.5 几个我踩过的坑
第一个坑:一开始我用 Deployment 部署,Pod 重建后 IP 变化,集群节点列表里出现大量僵尸节点。换成 StatefulSet 后问题消失。
第二个坑:Nacos 2.0 刚发布时,我用的客户端版本还是 1.x 的,结果 gRPC 端口连不上。升级客户端到 2.0 兼容版本后解决。
第三个坑:MySQL 用的是单节点,Nacos 集群倒是高可用了,但 MySQL 一挂全完。后来换成 MGR 三节点集群才算真正高可用。
第四个坑:没配 podAntiAffinity,三个 Pod 全调度到同一个节点上,节点宕机后集群直接瘫痪。加上反亲和性配置后,Pod 分散到不同节点,安全性大幅提升。
6. 生产环境加固建议与扩展思路
6.1 资源限制与 JVM 调优
Nacos 默认的 JVM 参数是-Xms2g -Xmx2g -Xmn1g,在容器环境下需要根据实际资源调整。如果 Pod 的 memory limit 是 4Gi,JVM 堆设 2G 比较合理,留 2G 给堆外内存和系统使用。
env: - name: JVM_XMS value: "2g" - name: JVM_XMX value: "2g" - name: JVM_XMN value: "1g" - name: JVM_MS value: "128m" - name: JVM_MMS value: "320m"JVM_MS和JVM_MMS是 Metaspace 的初始大小和最大大小,Nacos 加载的类比较多,Metaspace 设 320M 比较稳妥。
6.2 数据备份与恢复策略
Nacos 的配置数据存在 MySQL 里,所以备份 MySQL 就是备份 Nacos 数据。建议每天做一次全量备份,binlog 保留 7 天以上。
本地 data 目录的缓存数据不需要备份,Pod 重建后会自动从 MySQL 重新加载。但 logs 目录建议保留,排查问题时需要历史日志。
6.3 监控告警接入
Nacos 暴露了 Prometheus 格式的指标,可以通过/nacos/actuator/prometheus端点采集。关键指标包括:
nacos_monitor{name="configCount"}:配置数量nacos_monitor{name="serviceCount"}:服务数量nacos_monitor{name="raftNotifyTaskCount"}:Raft 通知任务数- JVM 相关指标:堆内存使用、GC 次数、线程数
建议对以下情况设置告警:
- 集群节点数少于预期
- 服务注册数突降
- JVM 老年代使用率持续超过 80%
- MySQL 连接池活跃连接数接近上限
6.4 扩缩容注意事项
Nacos 集群扩容比较简单,直接调大replicas就行。StatefulSet 会按顺序创建新 Pod,新节点会自动加入集群。
缩容要小心,必须先确认要删除的节点上没有承载关键服务。Nacos 的负载均衡是客户端做的,缩容后客户端需要刷新服务列表。建议缩容前先下线节点,等客户端感知后再删除 Pod。
提示:Nacos 集群的节点数建议用奇数,3 个或 5 个。偶数节点在选主时容易出现平票,虽然 Nacos 用的是 Raft 协议,但奇数节点在容错性上更有优势。
6.5 后续扩展方向
这套部署方案可以进一步扩展:
- 多集群联邦:如果业务跨多个 K8S 集群,可以用 Nacos 的同步机制做配置同步。
- 灰度发布:结合 Nacos 的命名空间和分组功能,实现配置的灰度推送。
- 服务网格集成:Nacos 可以作为 Istio 的服务注册中心,替代默认的 K8S Service 发现。
我个人在实际操作中的体会是,Nacos 集群部署最难的不是 YAML 怎么写,而是对 Nacos 2.0 通信模型的理解。很多人卡在 gRPC 端口和集群节点通信上,本质上是因为没搞清楚 2.0 跟 1.x 的架构差异。把这一点吃透,剩下的就是按部就班配置的事。另外,MySQL 的高可用千万别忽视,Nacos 集群做得再好,数据库单点照样让你半夜爬起来处理故障。