Kubernetes 部署 Nacos 集群:YAML 核心对象与排障实践
简介:面向在 Kubernetes 环境中部署 Nacos 集群的开发与运维人员,这份脚本化配置包把数据库连接、无头服务、有状态副本集、对外服务与 Ingress 暴露等关键环节,按执行顺序拆分为可直接套用的 YAML 清单,降低多组件配置与依赖梳理的门槛,适合刚接触容器编排部署或希望快速复现极简集群的读者参考。压缩包共 6 个文件,以 5 个 YAML 为主,另附 1 个 txt 说明,整体约 3KB,体积轻量,便于纳入版本库并按序应用。目前已有 766 人学习下载。读者可据此理解 Nacos 在 Kubernetes 中从数据库配置、集群寻址到服务暴露的编排关系,并借助说明文件按顺序落地,减少零散查找与反复试错的成本,较快搭起可验证的基础环境。
1. 一套 YAML 把 Nacos 集群塞进 K8s:先看清它替你把哪些脏活干了
早几年搭一套 nacos 集群,流程基本是:申请三台虚机、各自解压、手写 cluster.conf、装 MySQL、调 JVM 堆、开 8848 和 7848 端口,中途任何一步漏了,节点就停在 STARTING 状态互相看不见,排查靠登机器翻 logs 目录。换成 k8s 之后,一份 nacos 集群部署 yaml 就把这些活儿全收进声明式字段里:StatefulSet 负责固定网络标识和持久卷,Headless Service 负责 DNS,ConfigMap 和 Secret 负责数据库连接,探针负责健康判定,kubectl apply之后三个 Pod 按序号起来,注册中心和配置中心一起就位。
这套东西适合三类人:正在把微服务注册配置中心从虚机搬上 K8s 的团队;已经跑着单机版 Nacos、想扩成三节点集群但不想重写编排的人;以及想拿一份能直接改参数、而不是从零敲 YAML 的开发者。反过来说,如果你只是本地跑个 demo,单机模式一条docker run更省事,别上集群。
2. 集群拓扑与资源清单:一套 YAML 是怎么按对象切开的
2.1 三个 Nacos 节点之间到底靠什么互相看见
Nacos 2.x 的集群不是「起三个 Pod 挂同一个 Service 就行」那么简单。它内部同时跑着 HTTP 和 gRPC 两套通信:8848 端口对外提供 OpenAPI 和控制台请求,9848 是客户端侧 gRPC 端口(固定为 8848+1000),9849 是服务端之间同步数据用的 gRPC 端口(8848+1001),7848 则是老版本留下来给 JRaft 做一致性选举的端口。这四个端口得同时在 Service 和容器上放出来,少一个就会出现「控制台能打开、服务注册不上」这种看起来像玄学的问题。
节点之间的成员发现不依赖 K8s 的 Service 负载均衡,而是每个节点在启动时读环境变量NACOS_SERVERS,拿到一个完整的成员地址列表,然后各自去连。这意味着成员列表是写死在 Pod 的环境变量里的,扩缩容时必须同步改这个列表——这是后面二次开发章节要重点处理的地方,也是很多人第一次部署时最容易忽略的坑。
正因为要「固定标识 + 固定成员」,工作负载只能用 StatefulSet,不能用 Deployment。Deployment 拉起来的 Pod 名字是随机后缀,重启一次主机名就变了,Nacos 成员表里的地址立刻失效。StatefulSet 给的是nacos-0、nacos-1、nacos-2这种有序且重启不变的名字,配合 Headless Service 还能拿到nacos-0.nacos-headless.nacos.svc.cluster.local这种完整域名,这才是成员表能写死的前提。
2.2 一份大 YAML 切成 7 个对象
很多仓库把全部资源塞进一个nacos.yaml,能跑,但改起来很痛苦——改个数据库密码要全文搜索。我一般按对象类型拆开,文件名带编号,kubectl apply -f .时会按字典序执行,顺序天然正确。
| 文件名 | K8s 对象 | 作用 | 部署前是否必改 |
|---|---|---|---|
00-namespace.yaml | Namespace | 独立命名空间,隔离注册中心 | 否 |
01-configmap.yaml | ConfigMap | 覆盖application.properties的定制项 | 是 |
02-secret.yaml | Secret | 数据库账号密码、鉴权 token | 是 |
03-service.yaml | Service(Headless) | 给 StatefulSet 提供固定 DNS | 否 |
03-service-console.yaml | Service(NodePort/LB) | 控制台和 OpenAPI 对外入口 | 视环境 |
04-statefulset.yaml | StatefulSet | 三个 Nacos 节点本体 | 是(镜像 tag、资源) |
05-mysql.yaml | StatefulSet + Service | 内嵌一套 MySQL 供测试 | 生产环境删掉 |
生产上 MySQL 基本都用外部实例或云上 RDS,05-mysql.yaml只在自测时留着。这么做的好处是:把这一行文件删掉,整套 YAML 就变成了「只连外部库」的形态,不用改其他任何东西。
目录结构大概是这样的:
nacos-k8s/ ├── 00-namespace.yaml ├── 01-configmap.yaml ├── 02-secret.yaml ├── 03-service.yaml ├── 03-service-console.yaml ├── 04-statefulset.yaml ├── 05-mysql.yaml # 自测用,生产删除 └── deploy.sh # 一键 apply + 等待就绪deploy.sh不是必需的,但它把「apply 完还要等 rollout」这件事固化了,省得每次手动敲:
#!/usr/bin/env bash set -euo pipefail NS=${NS:-nacos} FILES="00-namespace.yaml 02-secret.yaml 01-configmap.yaml 03-service.yaml 03-service-console.yaml 04-statefulset.yaml" for f in $FILES; do echo "==> applying $f" kubectl apply -f "$f" # 逐个 apply,出错时能立刻定位是哪个文件 done kubectl -n "$NS" rollout status statefulset/nacos --timeout=600s echo "nacos cluster ready, console: http://<node-ip>:30848/nacos"脚本里set -euo pipefail保证任何一步失败都立刻中断,不会出现「namespace 建失败了但后面的 YAML 继续 apply 到 default」这种尴尬局面。rollout status是新版 kubectl 判断就绪的方式,比kubectl get pod靠肉眼看状态靠谱,超时设 600 秒是因为首次拉镜像加初始化 schema,慢的时候确实要几分钟。NS用变量而非硬编码,方便在测试和生产两套命名空间之间切换。
2.3 存储选型:PVC 模板还是直接挂目录
Nacos 节点需要持久化的只有两处:/home/nacos/data存本地快照和协议数据,/home/nacos/logs存日志。前者丢了会导致节点重启后需要从其他节点重新同步,代价是启动变慢;后者丢了排查问题时会很痛苦。所以data必须用 PVC,logs看情况——我一般也挂 PVC,因为日志写满容器可写层会直接触发驱逐。
用volumeClaimTemplates而不是手写三个 PVC,好处是 StatefulSet 扩容时自动创建新的 PVC,命名规则是>apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: nacos labels: app: nacos spec: clusterIP: None # 关键:None 才让每个 Pod 拿到独立 DNS 记录 publishNotReadyAddresses: true # 关键:未就绪也要能解析,否则节点互相等死 selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc-client port: 9848 targetPort: 9848 - name: grpc-server port: 9849 targetPort: 9849 - name: raft port: 7848 targetPort: 7848
clusterIP: None让这个 Service 不分配虚拟 IP,kube-dns 会为每个后端 Pod 直接写 A 记录,形如nacos-0.nacos-headless.nacos.svc.cluster.local。这就是NACOS_SERVERS里那些地址的来源。
publishNotReadyAddresses: true是很多人第一次写会漏掉的一行,它的作用是 Pod 还没通过 readinessProbe 时也把 DNS 记录发出去。听起来反直觉,但 Nacos 集群启动阶段恰恰需要它:nacos-0在等nacos-1、nacos-2加入才能完成初始化,如果 DNS 只发布就绪的 Pod,三个节点就会互相等待,卡在 STARTING 状态直到超时。这个坑几乎每个初次部署的人都会踩一遍。
至于对外暴露的控制台 Service,单独写一个ClusterIP或NodePort类型即可,只暴露 8848 一个端口,9848 和 9849 留给集群内网调用。业务侧客户端连的是这个对外 Service 的地址,它会把请求随机转发到某个健康节点;而节点之间的同步走的是 headless 的完整域名,两条链路互不干扰。
3.2 StatefulSet 主体:环境变量才是真正的配置中心
下面这段是整套 YAML 的核心,值得逐行看。
apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: nacos spec: serviceName: nacos-headless # 必须指向上面那个 headless service replicas: 3 podManagementPolicy: Parallel # 并行拉起,比默认的 OrderedReady 快很多 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: initContainers: - name: fix-permission image: busybox:1.36 command: - sh - -c - chown -R 3000:3000 /home/nacos/data /home/nacos/logs volumeMounts: - { name: data, mountPath: /home/nacos/data } - { name: logs, mountPath: /home/nacos/logs } containers: - name: nacos image: nacos/nacos-server:v2.x.x # 换成你要的具体小版本 imagePullPolicy: IfNotPresent ports: - { name: http, containerPort: 8848 } - { name: grpc-client, containerPort: 9848 } - { name: grpc-server, containerPort: 9849 } - { name: raft, containerPort: 7848 } 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.nacos.svc.cluster.local" } - { name: MYSQL_SERVICE_PORT, value: "3306" } - { name: MYSQL_SERVICE_DB_NAME, value: "nacos_config" } - { name: MYSQL_SERVICE_USER, valueFrom: { secretKeyRef: { name: nacos-db, key: username } } } - { name: MYSQL_SERVICE_PASSWORD, valueFrom: { secretKeyRef: { name: nacos-db, key: password } } } - { name: MYSQL_SERVICE_DB_PARAM, value: "characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true" } - { name: JVM_XMS, value: "1g" } - { name: JVM_XMX, value: "1g" } - { name: JVM_XMN, value: "512m" } - { name: NACOS_AUTH_ENABLE, value: "true" } resources: requests: { cpu: "1", memory: "2Gi" } limits: { cpu: "2", memory: "4Gi" } volumeMounts: - { name: data, mountPath: /home/nacos/data } - { name: logs, mountPath: /home/nacos/logs } volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: your-sc-name # 显式指定,别吃默认值 resources: requests: storage: 10Gi几个参数值得单独说。podManagementPolicy: Parallel让三个 Pod 同时创建,而不是OrderedReady那样等nacos-0完全就绪再起nacos-1。Nacos 集群的成员是平级的,不需要主从顺序,并行拉起能把首次部署时间从「三倍」压缩到「一倍多一点」。
NACOS_SERVERS里必须写完整的 FQDN,不能写nacos-0:8848这种短名,也不能写 Service 的 ClusterIP。短名在跨命名空间调用时会解析失败,ClusterIP 则会被负载均衡到随机一个 Pod,导致节点以为自己连上了同伴、实际上连的是自己。
initContainers里那个chown是血泪经验。PVC 挂载后默认属主是 root,而 Nacos 镜像以非 root 用户(uid 3000)运行,直接写/home/nacos/data会报 Permission denied,Pod 秒退。用 initContainer 在主容器启动前改属主是最省事的解法,uid 以你实际使用的镜像为准,可以docker run --rm --entrypoint id nacos/nacos-server看一眼。
数据库密码通过secretKeyRef注入而不是明文写 env,是因为kubectl describe pod会把环境变量原样打出来,明文密码等于贴在控制台上。Secret 注意别用stringData直接提交到公开仓库,data字段的 base64 也只是编码不是加密。
3.3 探针:initialDelaySeconds 决定你会不会看到假故障
startupProbe: # 先给启动兜底,避免慢启动被 liveness 误杀 httpGet: path: /nacos/v1/console/health/readiness port: 8848 failureThreshold: 30 periodSeconds: 10 # 最多容忍 300 秒启动 readinessProbe: httpGet: path: /nacos/v1/console/health/readiness port: 8848 initialDelaySeconds: 20 periodSeconds: 10 timeoutSeconds: 5 livenessProbe: httpGet: path: /nacos/v1/console/health/liveness port: 8848 initialDelaySeconds: 60 # 一定要比实际启动时间长 periodSeconds: 20 failureThreshold: 3readiness 和 liveness 用的是两个不同路径,这点别写反。readiness检查的是「能不能对外服务」,数据库断了它就会返回失败,Pod 被摘出 Endpoints,客户端请求不再打进来;liveness检查的是「进程还活着吗」,如果拿 readiness 当 liveness 用,数据库抖一下就会触发重启,本来等几秒就能自愈的事被搞成雪崩。
initialDelaySeconds是最容易翻车的参数。Nacos 冷启动要连数据库、建表校验、初始化 raft,容器起来到端口真正可用经常要 40 秒以上,如果 liveness 的初始延迟给 10 秒,你会看到 Pod 起起停停、日志里全是「Started Nacos in xxx seconds」却始终不 Ready。更好的做法是用startupProbe兜住启动期,成功一次之后 liveness 才开始计时,这样启动慢和运行中卡死就能分开判断。
3.4 配置覆盖:ConfigMap 该往里放什么
不是所有配置都适合塞环境变量。Nacos 镜像支持把application.properties挂到/home/nacos/conf/下做覆盖,或者用NACOS_前缀的环境变量。我一般这么分:
环境变量放「每个节点不一样或跟部署环境强相关」的项,比如数据库地址、JVM 参数、成员列表;ConfigMap 挂载放「三个节点完全一样、且改动需要版本记录」的项,比如nacos.core.auth.plugin.nacos.token.secret.key、management.endpoints.web.exposure.include、限流相关参数。
鉴权 token 特别要注意:2.2 之后开启鉴权必须设置nacos.core.auth.plugin.nacos.token.secret.key,值需要 base64 编码且解码后不少于 32 字节,长度不够启动直接失败,日志里报的是 token 解析异常,跟鉴权本身没半毛钱关系,第一次遇到很容易往权限配置上想。
4. 从零跑起来:命名空间、MySQL 初始化到集群自检的完整命令链
4.1 前置检查:三件事没确认别急着 apply
第一件是存储类。kubectl get sc看有没有可用的 StorageClass,如果没有,PVC 会一直 Pending,StatefulSet 的 Pod 也停在 Pending,而kubectl get pod只显示 Pending 不告诉你原因,得kubectl describe pvc才看得到。第二件是镜像能不能拉,内网环境要提前把nacos/nacos-server和busybox同步到私有仓库,并把 YAML 里的 image 字段换掉。第三件是 MySQL 是否可达,先在集群里起一个临时 Pod 测连通性:
# 在集群内验证 MySQL 连通性,避免部署后才发现网络策略不通 kubectl -n nacos run mysql-check --rm -it --image=busybox:1.36 --restart=Never -- \ sh -c "nc -zv mysql.nacos.svc.cluster.local 3306"nc -zv只做 TCP 握手探测,不验证账号密码,够用来区分「网络不通」和「认证失败」这两类问题。如果这一步就失败,先去查 NetworkPolicy 和 MySQL 的 Service,别浪费时间在 Nacos 的 YAML 上。
4.2 初始化数据库 schema
Nacos 不会自动建表,第一次启动前必须先把 schema 导进去。脚本在官方发行包的conf目录下,2.x 一般叫mysql-schema.sql,1.x 叫nacos-mysql.sql。库名和 YAML 里的MYSQL_SERVICE_DB_NAME保持一致。
-- 建库,字符集必须是 utf8mb4,否则配置里带 emoji 会写入失败 CREATE DATABASE IF NOT EXISTS nacos_config DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; CREATE USER IF NOT EXISTS 'nacos'@'%' IDENTIFIED BY '换成你的密码'; GRANT ALL PRIVILEGES ON nacos_config.* TO 'nacos'@'%'; FLUSH PRIVILEGES;建完库再执行 schema 文件:
# schema 文件里没有 use 语句,导入时显式指定库名 mysql -h <mysql-host> -u root -p nacos_config < mysql-schema.sql # 验证核心表是否建好,config_info 是配置中心的主表 mysql -h <mysql-host> -u nacos -p nacos_config -e "show tables like 'config_info';"这一步跳过的典型后果是 Pod 反复重启,日志里出现Table 'nacos_config.config_info' doesn't exist,但因为启动日志刷得快,前面还有一堆连接池信息,很容易被忽略。
4.3 apply 与滚动观察
顺序很重要:namespace → secret → configmap → service → statefulset。Secret 和 ConfigMap 必须在 StatefulSet 之前,否则 Pod 会因为引用不到而卡在CreateContainerConfigError。用第 2 章那个deploy.sh一把梭也行,但建议第一次手动逐个 apply,出问题时能立刻知道停在哪一步。
kubectl apply -f 00-namespace.yaml kubectl -n nacos apply -f 02-secret.yaml kubectl -n nacos apply -f 01-configmap.yaml kubectl -n nacos apply -f 03-service.yaml kubectl -n nacos apply -f 04-statefulset.yaml # 盯着三个 Pod 起来,-w 会持续刷新状态 kubectl -n nacos get pod -l app=nacos -w正常情况下会看到nacos-0、nacos-1、nacos-2依次经历Pending → ContainerCreating → Running,但 Running 不等于 Ready,要等 READY 列变成1/1。如果超过五分钟还是0/1,直接跳去看日志,别干等:
kubectl -n nacos logs -f nacos-0 --tail=200启动成功的标志是日志里出现Nacos started successfully in cluster mode. use external storage,注意后面那句use external storage,如果显示的是standalone或者embedded storage,说明MODE或SPRING_DATASOURCE_PLATFORM没生效,集群其实没配起来。
4.4 集群自检:确认三个节点真的在同一个集群里
Pod 全 Ready 也不代表集群是好的,得确认三个节点互相认识。最直接的办法是调运维接口:
# 列出当前集群的所有成员及其状态,servers 数组里应该有三个 kubectl -n nacos exec nacos-0 -- \ curl -s "http://127.0.0.1:8848/nacos/v1/ns/operator/servers" # 单独确认某个节点的健康状态,返回 UP 才算真正可用 kubectl -n nacos exec nacos-0 -- \ curl -s "http://127.0.0.1:8848/nacos/v1/console/health/readiness"返回的 JSON 里会有一个servers数组,每个元素带ip、port、state、address。三个都在且state为UP,才说明成员表和 raft 状态都正常。如果只看到一个节点,八成是NACOS_SERVERS写错了或者 DNS 解析不到,回到 3.1 检查 headless Service。
最后从业务侧验证一遍:在任意一个业务 Pod 里用 clients 的地址连注册中心,或者直接访问控制台的/nacos路径登录,能进到配置列表页面并且能看到三个节点的集群信息页,就算部署完成。控制台那张节点表是最直观的体检报告,节点数、连接数一目了然。
5. 踩坑排查:Nacos 集群起不来的 5 个典型现场
5.1 现象:Pod 一直 CrashLoopBackOff,日志里一行Table doesn't exist
原因几乎可以确定:schema 没导,或者库名对不上。Nacos 启动时会做一次表结构校验,缺表就直接抛异常退出,容器重启后又是同样的错误,于是无限循环。另一种可能是MYSQL_SERVICE_DB_NAME写的是nacos而实际建的库叫nacos_config,两个名字差一个下划线,肉眼扫日志很容易漏。
解决方式是kubectl describe pod看环境变量是否真的是你以为的那个值,然后进到容器里(或者临时起个 mysql 客户端 Pod)手动连一次:
kubectl -n nacos exec -it nacos-0 -- sh -c \ 'mysql -h $MYSQL_SERVICE_HOST -u $MYSQL_SERVICE_USER -p$MYSQL_SERVICE_PASSWORD \ -e "use $MYSQL_SERVICE_DB_NAME; show tables;"'能列出一堆表说明库没问题,问题就在连接参数上;报Unknown database就是库名错,报Access denied就是 Secret 里的账号密码错。
5.2 现象:控制台能打开,业务服务注册不上,客户端报Client not connected
这就是 2.1 里说的端口问题。Nacos 2.x 的客户端注册走的是 gRPC,连的是 9848,如果 Service 里只暴露了 8848,客户端的 HTTP 请求能通、gRPC 通道建不起来,表现就是注册一直重试。
顺着这个思路查两处:一是 StatefulSet 的containerPort和 Service 的ports里有没有 9848、9849;二是如果前面有 Ingress 或四层负载,得确认它没把 9848 过滤掉。另外客户端连的地址如果是 Service 的 ClusterIP,长连接在 kube-proxy 的 DNAT 下表项会随 Pod 变化而失效,这也是为什么业务侧连接地址最好用支持会话保持的入口。判断方法很简单,在客户端侧telnet nacos-host 9848,不通就是链路问题,跟 Nacos 配置无关。
5.3 现象:三个 Pod 都 Ready,但集群成员表里只有一个节点
这种「看起来全绿实际是三个单机」的情况最坑,因为它不报错,直到某台机器挂了才发现配置全丢。原因在NACOS_SERVERS:写成短名、写成 ClusterIP、或者域名拼错一个字符,Nacos 在解析失败时会退化成单节点模式继续启动,日志里只有一行不起眼的 warn。
排查靠自检接口,5.1 之外的场景一律先跑curl /nacos/v1/ns/operator/servers看返回几个节点。修复时把三个完整 FQDN 写全,改成每行一个、用空格分隔的字符串(环境变量里不能有换行),改完必须滚重启——环境变量变更不会热生效。
5.4 现象:重启后集群状态错乱,或者新 Pod 起来报Permission denied
前者是存储问题,后者是权限问题。Permission denied出现在日志开头,紧接着就是容器退出,说明挂载的 PVC 属主不对。默认 PVC 挂进来是 root:root,Nacos 以非 root 用户运行,写data/nacos目录直接被拒。用 3.2 里那个 initContainer 改属主能解决,另一种做法是在securityContext里设fsGroup,但 fsGroup 对某些存储驱动的支持不一致,initContainer 更通用。
集群状态错乱则通常是 PVC 被复用了旧数据。删 StatefulSet 不会删 PVC,重建后新节点挂到旧数据上,raft 里还留着已经不存在的成员地址。处理办法是先缩容到 0、手动删掉对应 PVC、再扩回来,代价是那块数据要重新同步。所以每次做破坏性变更前,先确认 PVC 是不是真的要保留。
5.5 现象:节点跑一段时间后周期性重启,kubectl describe显示OOMKilled
Nacos 的堆默认值是按「物理机部署」的假设给的,容器 limit 设成 2Gi 时堆可能被撑到 1.5G 以上,再加上堆外内存和元空间,很容易越过 cgroup 上限被内核干掉。OOMKilled这个退出码在describe pod的Last State里能看到,但很多人只看Events里那堆探针失败的记录,误判成健康检查太严格。
正确姿势是让堆上限明显小于 limit,比如 limit 4Gi、JVM_XMX设 1g 或 2g,留出足够余量给堆外。同时把livenessProbe的failureThreshold调到 3 以上,避免一次 Full GC 停顿就触发重启。改完观察一两天,用kubectl top pod看实际内存曲线会不会贴着 limit 走,贴着走就说明还得把堆再降一档。
6. 二次开发:把这份 YAML 改造成能跟着业务走的模板
直接改 YAML 用不了几次就会乱,我的做法是加一层薄薄的变量化,不引 Helm 那么重的东西,用 Kustomize 或者envsubst加一份values.env就够了。第一步把所有环境相关的东西抽出来:
| 变量 | 用途 | 改动频率 |
|---|---|---|
NACOS_VERSION | 镜像 tag | 升级时改 |
NACOS_REPLICAS | 副本数 | 扩容时改 |
MYSQL_HOST/MYSQL_DB | 数据库连接 | 换环境时改 |
JVM_XMX | 堆上限 | 调优时改 |
NACOS_SERVERS | 成员列表 | 必须跟副本数同步改 |
最后一行是这份模板最需要工程化处理的地方。Nacos 2.x 的成员列表是启动时读死的,副本数从 3 改到 5,就必须同时把NACOS_SERVERS从三个地址扩到五个,否则新加的两个节点会以单机模式启动,加入不了集群。我一般写一个渲染脚本,按副本数自动拼出地址字符串:
#!/usr/bin/env bash # 根据副本数自动生成成员列表,避免手改漏改 NS=${NS:-nacos} REPLICAS=${NACOS_REPLICAS:-3} SERVERS="" for i in $(seq 0 $((REPLICAS - 1))); do SERVERS="${SERVERS}nacos-${i}.nacos-headless.${NS}.svc.cluster.local:8848 " done export NACOS_SERVERS="${SERVERS% }" # 去掉末尾空格 envsubst < 04-statefulset.tpl.yaml | kubectl -n "$NS" apply -f -这段的关键是envsubst只替换出现过的变量,模板里其余占位保持原样,比用 sed 拼字符串安全得多。${SERVERS% }是 Bash 的参数展开,作用是砍掉最后一个空格,因为环境变量末尾带空格在少数场景下会被当成地址的一部分。
升级路径上还有两个点要盯住。一是版本跨度别太大,2.x 内部跨两三个小版本时,先在一个节点上滚动验证,确认鉴权 token 的兼容性再推全量;二是 gRPC 端口在升级前后保持不变,但如果升级到某个调整了默认端口的版本,Service 里的 9848/9849 得跟着改,客户端也要一起升级。这两件事没有捷径,只能在预发环境完整跑一遍注册、配置推送、实例下线三个动作。
验证方法也固化下来:每次改完模板,跑一遍第 4.4 节那两条 curl,确认成员数是新的副本数、每个节点都是 UP,再在控制台的集群信息页对一眼。别嫌麻烦,我吃过一次亏——扩到五个节点但成员表还是三个,前面几天一切正常,直到被扩出来的两个节点所在的主机检修,配置中心短暂不可写,事后翻集群信息页才发现那俩一直是以单机模式在跑。从那以后我每次动副本数,都强制走一遍「渲染 → apply → 查成员表 → 对数」这套流程,宁可多花两分钟,也不留这种定时炸弹。希望这套 YAML 和这些坑,能帮你省下几个通宵。
本文还有配套的精品资源,点击获取