☰
K8s 部署 Nacos 集群实战:StatefulSet 编排与避坑指南
2026/10/20 3:46:26 网站建设 项目流程

简介:这份资源面向需要在 Kubernetes 上部署 Nacos 集群的开发与运维人员,尤其适合刚接触 K8s 编排、希望跳过繁琐配置直接跑通集群的入门者。它把 Nacos 集群部署拆解为按执行顺序排列的极简 YAML 脚本,覆盖数据库配置、Headless Service、StatefulSet、Service 以及 Ingress 等关键环节,省去大量手工拼装配置的麻烦,按顺序执行即可完成搭建。压缩包共 6 个文件,以 5 个 yaml 清单为主,分别承担数据库连接、无头服务、有状态副本集、服务暴露与外部访问入口等职责,另附 1 个 txt 说明文件,整体仅约 3KB,轻量易读。目前已有 765 人学习下载,说明该方案在同类场景中具备一定参考价值。读者可借此快速获得一套可直接套用的 Nacos on K8s 部署模板,理解各资源对象的先后依赖与作用,并在此基础上按自身环境调整镜像、存储与域名等参数,减少从零摸索的排错成本。

1. 从一次 StatefulSet 滚动更新卡死说起:k8s 里跑 Nacos 集群到底难在哪

很多人第一次在 k8s 上部署 Nacos 集群,都是被“集群”两个字骗进来的。单机模式standalone一条命令就起来了,于是想当然地以为把副本数改成 3 就完事。结果 Pod 是 Running 了,日志里却反复刷server is DOWN,控制台节点列表只认一个自己,配置一改就丢,服务注册列表忽多忽少。更玄学的是,滚动更新时新 Pod 起不来,旧 Pod 又不敢删,整个 StatefulSet 卡在Pending或者CrashLoopBackOff,业务侧注册中心直接不可用。

这套环境的难点不在 Nacos 本身,而在 k8s 的有状态编排和 Nacos 集群寻址、持久化、探针三件事的配合。它适合两类人:一类是手上已经有 k8s 集群、想把注册配置中心从虚机迁进来的运维;另一类是照着网上零散 yaml 拼凑、跑起来但不敢上生产的开发者。这篇笔记按“装什么版本 → 怎么编排 → 怎么配参数 → 坏了怎么救 → 怎么验证真跑通”的顺序走一遍,命令和 yaml 都能直接抄,参数我会说清为什么这么填。装之前先把环境列清楚,比装完再返工省事得多。

2. 装前把版本和环境钉死:Nacos 2.x 与 k8s 的匹配清单

2.1 版本选择:为什么我锁 Nacos 2.2.x 而不是最新版

Nacos 1.x 和 2.x 的通信模型差别很大。1.x 用 HTTP 长轮询,2.x 引入了 gRPC 长连接,端口从原来的单 8848 变成 8848(HTTP)+ 9848(gRPC 客户端)+ 9849(gRPC 服务端)。这个变化直接决定了 k8s 里 Service 和探针怎么写,也是很多人“Pod 起来了但客户端连不上”的根因。

我一般锁nacos/nacos-server:v2.2.3这个镜像。原因有三个:2.2.x 对 k8s 的 headless service 寻址支持比较稳;2.2.3 修了 2.2.0 早期版本里集群节点互相踢的 bug;再新的 2.3.x 改动了鉴权默认行为,老项目迁移容易踩坑。如果你是新项目,2.3.x 也可以,但下面的参数要按官方 release note 对一遍。

k8s 侧要求:1.24 以上,因为要用到StatefulSet的podManagementPolicy和稳定网络标识。容器运行时无所谓,containerd 和 docker 都行。存储类必须有,Nacos 的data目录和logs目录都要持久化,否则重启即失忆。

组件推荐版本说明
Nacosv2.2.32.x gRPC 模型,集群寻址稳定
k8s1.24+支持 StatefulSet 稳定标识
MySQL8.0 / 5.7外置库,生产必须
存储类任意支持 RWO每副本独立 PVC

2.2 命名空间与前置资源:先把地基打好

不要把所有东西塞 default 命名空间。我习惯单独建一个nacos命名空间,方便后面 RBAC 和资源配额隔离。

# 创建独立命名空间,避免和业务 Pod 混在一起 kubectl create namespace nacos # 确认存储类可用,后面 PVC 要靠它动态供给 kubectl get storageclass

逻辑说明:kubectl create namespace是幂等操作之外的基础动作,重复执行会报 AlreadyExists,可以忽略。get storageclass是为了确认集群里有默认存储类,如果没有,PVC 会一直 Pending,这是后面排查章节的第一个高频坑。参数上,命名空间名nacos要和后面所有 yaml 里的namespace字段一致,改了就全改,别只改一处。

2.3 外置 MySQL:Nacos 集群的数据底座

生产环境不要用 Nacos 内置的 Derby。三个副本各写各的 Derby,配置根本对不上。必须先建库建表。

-- 建库,字符集必须是 utf8mb4,否则中文配置项会乱码 CREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 建专用账号,不要用 root 连 Nacos CREATE USER 'nacos'@'%' IDENTIFIED BY 'YourStrongPass123'; GRANT ALL PRIVILEGES ON nacos_config.* TO 'nacos'@'%'; FLUSH PRIVILEGES;

逻辑说明:建库时字符集写utf8mb4,这是血泪经验,用默认 latin1 后面配置里带中文就变问号。账号用%通配是因为 Pod IP 不固定,没法提前指定。建完库还要导入 Nacos 官方mysql-schema.sql,这个文件在 Nacos 发行包的conf目录里,版本要对上,2.2.3 的表结构和 1.x 不一样,别拿旧脚本导。

提示:MySQL 连接串后面要写进 ConfigMap,密码建议用 Secret 存,不要明文写在 yaml 里提交到仓库。

3. 编排 StatefulSet:headless service 与三个关键端口

3.1 headless service:集群节点互相认识的唯一方式

Nacos 集群模式下,每个节点要告诉其他节点“我是谁、你在哪”。在 k8s 里,这靠 headless service(clusterIP: None)给每个 Pod 一个稳定的 DNS 名,格式是<pod-name>.<service-name>.<namespace>.svc.cluster.local。

apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: nacos labels: app: nacos spec: clusterIP: None # headless,不做负载均衡,直接解析到 Pod IP selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc-client port: 9848 targetPort: 9848 - name: grpc-server port: 9849 targetPort: 9849

逻辑说明:clusterIP: None是核心,有了它,nacos-0.nacos-headless.nacos.svc.cluster.local才能解析到具体 Pod。三个端口必须都开:8848 是控制台和 OpenAPI,9848 是客户端 gRPC 长连接,9849 是服务端之间同步用的 gRPC。少开 9848,客户端能连上控制台但注册失败,报错很隐晦。targetPort要和容器实际监听端口一致,Nacos 2.x 默认就是这三个。

3.2 StatefulSet 主体:副本数、探针、环境变量

StatefulSet 而不是 Deployment,是因为 Nacos 集群需要稳定的网络标识和独立的存储。副本数我一般设 3,奇数便于后续如果引入一致性协议时投票,也够用。

apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: nacos spec: serviceName: nacos-headless # 必须绑定上面的 headless service replicas: 3 podManagementPolicy: Parallel # 并行启动,加快集群形成 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: containers: - name: nacos image: nacos/nacos-server:v2.2.3 ports: - containerPort: 8848 - containerPort: 9848 - containerPort: 9849 env: - name: MODE value: "cluster" # 集群模式,别写 standalone - 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.nacos.svc.cluster.local" - name: MYSQL_SERVICE_DB_NAME value: "nacos_config" - name: MYSQL_SERVICE_USER value: "nacos" - name: MYSQL_SERVICE_PASSWORD valueFrom: secretKeyRef: name: nacos-mysql-secret key: password volumeMounts: - name: nacos-data mountPath: /home/nacos/data - name: nacos-logs mountPath: /home/nacos/logs volumeClaimTemplates: - metadata: name: nacos-data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi - metadata: name: nacos-logs spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 5Gi

逻辑说明:serviceName必须指向 headless service,否则 Pod 拿不到稳定 DNS。podManagementPolicy: Parallel让三个 Pod 同时起,比默认的 OrderedReady 快很多,集群形成也快。NACOS_SERVERS里每个地址都要写全 FQDN,端口 8848,这是节点互相寻址的清单,写错一个节点就少一个。数据库密码走secretKeyRef,不落明文。volumeClaimTemplates给每个 Pod 生成独立 PVC,data 和 logs 分开挂,日志写满不会撑爆数据盘。

3.3 探针配置:别让 liveness 把正常节点杀掉

Nacos 启动慢,尤其是第一次连数据库建表。探针配太激进,Pod 还没起来就被 liveness 杀掉,循环重启,永远起不来。

startupProbe: # 启动探针,给足冷启动时间 httpGet: path: /nacos/v1/console/health/readiness port: 8848 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 30 # 最多容忍 300 秒启动 livenessProbe: httpGet: path: /nacos/v1/console/health/liveness port: 8848 initialDelaySeconds: 60 periodSeconds: 20 failureThreshold: 5 readinessProbe: httpGet: path: /nacos/v1/console/health/readiness port: 8848 initialDelaySeconds: 30 periodSeconds: 10

逻辑说明:startupProbe是给慢启动兜底的,failureThreshold: 30乘以periodSeconds: 10等于 300 秒,足够 Nacos 冷启动。startup 成功前,liveness 和 readiness 都不生效,避免误杀。liveness 用/liveness端点,readiness 用/readiness,两个端点语义不同,别混用。参数上initialDelaySeconds别设太小,我见过设 5 秒的,Pod 起来就被杀。

4. 配置参数逐项拆:环境变量、JVM 与持久化的取舍

4.1 环境变量对照表:哪些必须改,哪些保持默认

Nacos 官方镜像支持一堆环境变量,但常用的就那几个。下面这张表是我实际部署时对过的,标“必改”的漏一个集群就起不来。

变量名建议值是否必改作用
MODEcluster必改集群模式
NACOS_SERVERS三节点 FQDN 列表必改节点寻址
SPRING_DATASOURCE_PLATFORMmysql必改用外置库
MYSQL_SERVICE_HOSTMySQL 地址必改数据库连接
MYSQL_SERVICE_DB_NAMEnacos_config必改库名
JVM_XMS / JVM_XMX2g / 2g建议堆内存
NACOS_AUTH_ENABLEtrue建议开启鉴权

逻辑说明:MODE写错成 standalone,三个 Pod 各玩各的,控制台永远只有一个节点。NACOS_SERVERS是集群的“通讯录”,必须包含所有副本的 FQDN。JVM 参数在容器里默认可能偏小,2g 堆对中小规模够用,规模大再往上加,但别超过容器 limit,否则 OOMKilled。

4.2 JVM 与资源 limit:给多少内存才不 OOM

Nacos 是 Java 应用,堆内存和容器 limit 要匹配。我一般给容器 limit 4Gi,堆设 2g,留一半给堆外和系统。

resources: requests: memory: "2Gi" cpu: "500m" limits: memory: "4Gi" cpu: "2" env: - name: JVM_XMS value: "2g" - name: JVM_XMX value: "2g" - name: JVM_XMN value: "1g"

逻辑说明:JVM_XMS和JVM_XMX设成一样,避免堆动态伸缩带来的抖动。JVM_XMN是新生代,设堆的一半。容器 limit 要比堆大,因为 JVM 还有元空间、线程栈、直接内存。如果 limit 设成和堆一样大,稍微一波动就 OOMKilled,Pod 反复重启,日志里看不到 Java 异常,只有 k8s 的退出码 137,这是典型的“黑匣子”现场。

4.3 持久化目录:data 和 logs 为什么必须分开

Nacos 的data目录存的是本地缓存和协议数据,logs存日志。两个目录的写入频率和增长曲线完全不同。日志一天可能几个 G,数据相对稳定。挂同一个 PVC,日志写满会把数据盘撑爆,Nacos 直接写不进去。

volumeMounts: - name: nacos-data mountPath: /home/nacos/data - name: nacos-logs mountPath: /home/nacos/logs

逻辑说明:两个volumeClaimTemplates分别生成 PVC,互不影响。存储大小按规模估,data 10Gi 起步,logs 5Gi 并配日志轮转。如果集群有统一的日志采集,logs 可以挂 emptyDir 再采集走,但生产我倾向持久化,出问题能回溯。

注意:PVC 一旦创建,扩容要看存储类是否支持。不支持在线扩容的,只能重建,数据会丢,所以初始大小别抠。

5. 避坑与排查:五个让集群起不来的高频问题

5.1 现象:Pod 一直 Pending,事件里报 no persistent volumes available

原因:集群没有默认存储类,或者存储类不支持动态供给,PVC 卡在 Pending,Pod 调度不上去。

解决:kubectl get storageclass确认有默认存储类,没有就手动指定storageClassName。如果是本地存储,确认节点上有可用 PV。这个坑在新集群上第一次部署必踩。

5.2 现象:Pod Running 但日志刷 server is DOWN,控制台只有一个节点

原因:NACOS_SERVERS写错,或者 headless service 没建、serviceName没绑定,Pod 之间 DNS 解析不了。

解决:进 Pod 里nslookup nacos-0.nacos-headless.nacos.svc.cluster.local验证解析。解析不了就回头查 headless service 的clusterIP: None和serviceName字段。解析通了但列表还少,检查NACOS_SERVERS里端口是不是 8848,FQDN 有没有写全。

5.3 现象:客户端能打开控制台,但服务注册失败,报连接超时

原因:9848 端口没在 Service 里暴露,或者 NetworkPolicy 挡了 gRPC 流量。Nacos 2.x 的注册走 gRPC,不是 HTTP。

解决:确认 headless service 的 ports 里有 9848 和 9849。有 NetworkPolicy 的,放行同命名空间内这三个端口的流量。这个坑最隐蔽,因为控制台是 HTTP 的 8848,能打开会让人误以为网络没问题。

5.4 现象:滚动更新时新 Pod 起不来,旧 Pod 被删,集群短暂不可用

原因:podManagementPolicy是默认的 OrderedReady,加上探针太激进,新 Pod 没 ready 旧的不让动,或者反过来旧的一删新的还没好。

解决:设podManagementPolicy: Parallel,配好startupProbe给足启动时间。更新策略用RollingUpdate并设maxUnavailable: 1,保证至少两个节点在线。生产环境更新前先在测试命名空间走一遍。

5.5 现象:Pod 反复重启,退出码 137,日志无 Java 异常

原因:容器内存 limit 太小,JVM 堆加堆外超过 limit,被内核 OOMKilled。

解决:kubectl describe pod看Last State的Reason是不是 OOMKilled。是就把 limit 调大,保证 limit 比JVM_XMX大至少 1Gi。别只看堆,堆外内存和线程栈也要算进去。

6. 装完怎么验证真跑通:从节点列表到一次配置发布

6.1 三层验证:Pod、集群、客户端

装完别急着交付,按三层验证走一遍,每层都有明确的通过标准。

第一层,Pod 和 PVC 状态。kubectl get pods -n nacos三个 Pod 都 Running 且 READY 是 1/1,kubectl get pvc -n nacos六个 PVC 都 Bound。这一步不过,后面不用看。

第二层,集群节点列表。进任意一个 Pod 执行健康检查,或者直接看控制台。

# 进 Pod 查集群健康状态,返回的 status 应该是 UP kubectl exec -n nacos nacos-0 -- curl -s http://localhost:8848/nacos/v1/console/health/readiness # 查节点列表,三个节点的 state 都应该是 UP kubectl exec -n nacos nacos-0 -- curl -s "http://localhost:8848/nacos/v1/ns/operator/servers"

逻辑说明:第一个命令返回{"status":"UP"}说明本节点就绪。第二个命令返回的 JSON 里servers数组应该有三个元素,每个state是UP。如果只有一两个,回第 5 章查NACOS_SERVERS和 DNS。

第三层,客户端实际读写。用 Nacos 的 OpenAPI 发一个配置,再读回来,确认数据落库且三节点可见。

# 发布一个配置 curl -X POST "http://<任一节点IP>:8848/nacos/v1/cs/configs" \ -d "dataId=test.yaml&group=DEFAULT_GROUP&content=key: value" # 从另一个节点读,验证集群同步 curl "http://<另一节点IP>:8848/nacos/v1/cs/configs?dataId=test.yaml&group=DEFAULT_GROUP"

逻辑说明:写一个节点、从另一个节点读,能读到说明集群同步正常,数据也落了 MySQL。这一步过了,才算真跑通。参数上dataId和group要成对,读的时候和写的时候一致。

6.2 一个我每次都会做的收尾动作

验证通过后,我会做一件事:把三个 Pod 依次删掉,观察它们能不能自动重建并重新加入集群。这是模拟节点故障,比看状态更有说服力。

# 删一个 Pod,StatefulSet 会自动重建同名 Pod kubectl delete pod nacos-1 -n nacos # 观察重建过程和重新加入集群 kubectl get pods -n nacos -w

逻辑说明:StatefulSet 保证 Pod 名不变,重建后 PVC 还是原来那个,数据不丢。重建完再查一次节点列表,三个都 UP,说明自愈能力没问题。这个动作能提前暴露存储、探针、DNS 的隐患,比等到生产出事强。

从那以后我每次部署 Nacos 集群,都强制走一遍“删 Pod 自愈”验证,不看到三个节点重新 UP 不交付。这套 yaml 和参数在几个环境里跑过,坑基本都在这几个地方。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询