☰
K8S StatefulSet 部署 Nacos 2.0 高可用集群实战
2026/10/21 16:16:13 网站建设 项目流程

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 通信,需要额外开放两个端口:

端口用途说明
8848HTTP API控制台、OpenAPI、服务注册发现
9848gRPC 客户端客户端与 Nacos 服务端通信,偏移量 = 8848 + 1000
9849gRPC 服务端集群节点间通信,偏移量 = 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 环境下,我推荐用环境变量注入配置,而不是挂载配置文件,这样更灵活。

关键环境变量:

变量名值说明
MODEcluster必须设为 cluster,否则启动单机模式
NACOS_SERVERSnacos-0.nacos-headless...:8848 nacos-1...:8848 nacos-2...:8848集群节点列表
SPRING_DATASOURCE_PLATFORMmysql使用 MySQL 存储
MYSQL_SERVICE_HOSTmysql-hostMySQL 地址
MYSQL_SERVICE_DB_NAMEnacos_config数据库名
MYSQL_SERVICE_USERnacos数据库用户
MYSQL_SERVICE_PASSWORDpassword数据库密码
NACOS_SERVER_PORT8848服务端口

注意: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: 3

initialDelaySeconds设 30 秒是因为 Nacos 启动需要连接 MySQL、初始化集群状态,太快检查会误判。failureThreshold设 3 是给集群选主留出缓冲时间。

4. 完整实操过程与核心环节实现

4.1 环境准备与前置条件

开始之前,确认以下条件已满足:

  1. K8S 集群版本 1.20 以上,支持 StatefulSet 的稳定特性。
  2. 集群已配置默认 StorageClass,或者你明确知道要用哪个 StorageClass。
  3. MySQL 高可用环境已就绪,版本 5.7 或 8.0 均可。
  4. 已创建独立的 namespace,比如nacos。
kubectl create namespace nacos

4.2 初始化 MySQL 数据库

从 Nacos 2.0 的发布包中取出mysql-schema.sql,在 MySQL 中执行:

mysql -h mysql-host -u root -p
CREATE 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。

排查思路:

  1. 检查NACOS_SERVERS环境变量是否正确注入:
    kubectl exec -it nacos-0 -n nacos -- env | grep NACOS_SERVERS
  2. 检查 Pod 之间网络是否互通:
    kubectl exec -it nacos-0 -n nacos -- ping nacos-1.nacos-headless.nacos.svc.cluster.local
  3. 检查 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。

排查思路:

  1. 确认客户端配置的地址是 Nacos 的 Service 地址,而不是 Pod IP。
  2. 确认 9848 端口在 Service 中已暴露。Nacos 2.0 客户端会先连 8848 获取 gRPC 端口,然后连 9848 建立长连接。
  3. 检查网络策略是否限制了 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。

排查思路:

  1. 检查 Nacos 的数据库连接池配置,默认最大连接数是 20,三个节点就是 60。如果 MySQL 的max_connections设得比较小,容易耗尽。
  2. 调整 Nacos 的db.pool.config.maxActive参数,或者调大 MySQL 的max_connections。
  3. 检查是否有慢查询导致连接被长时间占用。

5.4 常见问题速查表

问题现象可能原因解决方法
Pod 启动后 CrashLoopBackOffMySQL 连接失败检查数据库地址、用户名密码、网络连通性
集群节点显示不全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 集群做得再好,数据库单点照样让你半夜爬起来处理故障。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询