资讯动态

K8s部署Nacos集群:从StatefulSet到YAML完整实践与避坑指南

发布时间:2026/10/16 11:20:47 来源:尧图企业网站定制
简介这是一份面向Kubernetes环境的Nacos集群极简部署脚本针对需要在Kubernetes中快速搭建高可用Nacos的运维与开发人员也适合刚接触K8s与Nacos的小白。资源将复杂配置拆分为按编号排列的YAML文件依次执行即可完成数据库依赖、Headless Service、StatefulSet、对外Service与Ingress的创建显著降低手工修改与排错成本。资源包共6个文件其中5个YAML对应上述各环节另有1个readme说明文档用于引导使用整体压缩包仅3KB十分轻量。整个过程无需额外插件按编号顺序执行即可完成部署。目前已有764人学习下载。通过该脚本读者不仅能获得一套可直接运行的Nacos集群配置还能借助清晰的YAML划分理解组件间依赖关系便于在测试环境快速验证或在此基础上做二次定制。适合已有基本K8s概念、希望快速落地中间件部署的团队或个人使用。1. 为什么在 K8s 里部署 Nacos 集群总在第一步就翻车k8s nacos集群部署脚本yaml——这个标题我太熟了因为每次帮人排查Nacos集群问题最后都能定位到三件事网络、存储、版本。这三个词看着不复杂但叠在 Kubernetes 上就变出很多具体烦恼Pod 的 IP 是动态的节点之间的域名得自己搭节点重启后旧 IP 还在集群列表里赖着不走新版本镜像默认多监听一个 9848 的 gRPC 端口少暴露一个端口客户端就反复报错。这篇文章不打算讲 Nacos 原理而是把在 k8s 里部署 Nacos 集群这件事从选型、YAML 编写到验证完整走一遍。你会看到完整的 Headless Service、StatefulSet、环境变量配置和可复用的部署脚本也会看到我整理的五条血泪坑。适合已经在用 K8s、想把 Nacos 从单机搬到集群的读者也适合正在被Pod 起来了但集群没起来折磨的人。2. 先把三样底牌定死存储、镜像版本、网络模型2.1 有状态服务选型为什么只用 StatefulSet PVCNacos 集群是典型的有状态应用对外提供服务、集群内部节点间通信都需要稳定的身份标识。在 K8s 里跑有状态应用最标准的答案就是 StatefulSet这没什么好犹豫的。它和 Deployment 最大的区别在于StatefulSet 创建的 Pod 名称是有序且固定的比如 nacos-0、nacos-1、nacos-2配合无头服务可以拿到稳定的 DNS 域名。翻译成人话每个 Nacos 节点都得能被点名找到。你如果图省事用 Deployment 加随机 Pod 名节点 A 重启后变成了完全陌生的名字集群里其他节点不认识它整个集群就散了。StatefulSet 保证的是nacos-0 重启以后还是 nacos-0域名还是那个域名节点在集群里的身份不会变。存储也是同样的逻辑。Nacos 虽然配了 MySQL 做外部持久化但节点本地还有 data 和 logs 目录需要落盘。data 目录存放 Raft 快照、临时文件logs 目录存放运行日志。这两个目录不持久化Pod 一重建日志全没了快照文件丢了虽然能从 MySQL 恢复但对排查问题来说是灾难。存储类这边我一般建议先确认 K8s 集群的动态供给是否可用kubectl get storageclass如果输出里能看到一条带(default)标记的存储类那 StatefulSet 里不写 storageClassName 也能自动创建 PVC。如果一条都没有说明集群没有动态供给这时候要么装一个存储插件要么老老实实手动建 PV。我不建议在这地方偷懒后续 PVC 卡在 Pending 的坑根源基本都是这里。还有一点Nacos 集群模式下必须配外部数据库官方内置的 Derby 只能单机玩。三个节点各带一个 Derby数据互相看不见和集群完全是两码事。数据库可以部署在 K8s 集群内也可以部署在集群外的虚机上关键是 Nacos 节点能通过网络访问到它。我在实际项目里更倾向于用集群外已有的 MySQL这样备份、迁移、升级都不受 K8s 重建周期影响。2.2 镜像与版本nacos-server 1.x 和 2.x 部署逻辑差在哪版本选择上Nacos 1.x 和 2.x 在 K8s 里的部署差异非常大。最核心的变化是2.x 引入了 gRPC 通信。客户端注册后不是直接走 HTTP 了而是通过 gRPC 长连接和 Nacos 服务端通信集群节点之间走 Raft 协议默认在 8848 主端口上加 1000 得到 9848 的 gRPC 端口再加 1000 得到 9849 的服务端 gRPC 端口。这个差异直接决定了 Service 要开哪些端口。1.x 只用开 88482.x 至少还要开 9848否则客户端注册时能连上但后续心跳和推送全部超时。很多刚上手的人只映射了 8848Pod 全是 Running客户端却疯狂报错折腾一整天最后发现是端口没开全。镜像 tag 我建议锁稳定版本比如 v2.3.x 这个系列。下载下来先别急着部署用 docker 拉下来看一眼启动日志确认版本号再配参数。不要图新用 latest也不要用特别老的 1.4.x。如果是从 1.x 升上来的老集群先搞清升级路径再动手直接跨大版本升级的翻车现场我在第 5 章会细讲。2.3 网络模型Headless Service 到底在解决什么问题Nacos 集群节点之间要互相通信必须知道彼此的地址。物理机部署时直接填内网 IP 就行但在 K8s 里 Pod 的 IP 是临时的随时可能因为重建而变化。如果用 IP 做节点间通信那每次 Pod 重启集群里其他节点还在尝试连旧 IP节点就丢了。Headless Service 就是来解决这个问题的。它把 clusterIP 设为 None不承担负载均衡职责但会给每个 Pod 分配一条稳定的 DNS 记录。比如 serviceName 是 nacos-headless那nacos-0.nacos-headless.nacos.svc.cluster.local就永远指向当前这个 Pod 的 IP哪怕 Pod 漂了域名也会自动更新到新 IP。所以在 Nacos 的环境变量里最稳的写法是设PREFER_HOST_MODEhostname配合 NACOS_SERVERS 里写三个稳定域名让节点之间通过 hostname 互相发现而不是直接记 IP。Nacos 2.x 也支持通过 fieldRef 拿 Pod IP 做注册但 IP 方式在节点重建后会有残留问题我一般不用。同一个命名空间内可以简写域名比如nacos-0.nacos-headless但跨命名空间或生产环境我建议直接写完整 FQDNnacos-0.nacos-headless.namespace.svc.cluster.local一劳永逸。部署完先在 Pod 里 ping 一下域名确认解析正常再启动容器能省掉后面大量排查时间。3. 手写 Nacos 集群 YAML从无头服务到 StatefulSet 全量拆解3.1 命名空间与 Headless Service先给每个副本一个可解析的域名这一节开始进入动手阶段。先创建命名空间我习惯用nacos这个名字你按团队规范改也行。然后是无头服务它负责给每个 Pod 发稳定域名apiVersion: v1 kind: Namespace metadata: name: nacos --- apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: nacos labels: app: nacos spec: clusterIP: None ports: - name: http port: 8848 targetPort: 8848 - name: grpc port: 9848 targetPort: 9848 selector: app: nacos这段 YAML 里最关键的字段是clusterIP: None它告诉 K8s 不要给这个 Service 分配集群内虚拟 IP。无头服务创建后K8s 会为每个后端 Pod 生成一条 A 记录格式就是podName.serviceName.namespace.svc.cluster.local。端口这里我把 8848 和 9848 都提前声明了。8848 是 Nacos 的主端口提供 HTTP 服务和客户端 API9848 是 gRPC 端口客户端发现集群地址后会自动连这个端口。StatefulSet 创建出来的 Pod 会自动挂到这个无头服务后面前提是 selector 匹配上app: nacos这个标签。3.2 StatefulSet 完整 YAML三副本、反亲和、环境变量一次性写对接下来是核心的 StatefulSet。我直接给出一个能用的完整定义然后逐个字段解释apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: nacos spec: serviceName: nacos-headless replicas: 3 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: nacos topologyKey: kubernetes.io/hostname containers: - name: nacos-server image: nacos/nacos-server:v2.3.2 ports: - containerPort: 8848 name: http - containerPort: 9848 name: grpc env: - name: MODE value: cluster - name: PREFER_HOST_MODE value: hostname - name: NACOS_REPLICAS value: 3 - 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.mysql.svc.cluster.local - name: MYSQL_SERVICE_DB_NAME value: nacos_config - name: MYSQL_SERVICE_PORT value: 3306 - name: MYSQL_SERVICE_USER value: nacos - name: MYSQL_SERVICE_PASSWORD valueFrom: secretKeyRef: name: nacos-db-secret key: password - name: JVM_XMS value: 1g - name: JVM_XMX value: 1g - name: JVM_XMN value: 512m volumeMounts: - name: nacos-data mountPath: /home/nacos/data - name: nacos-log mountPath: /home/nacos/logs volumeClaimTemplates: - metadata: name: nacos-data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 10Gi - metadata: name: nacos-log spec: accessModes: [ReadWriteOnce] resources: requests: storage: 10GiserviceName字段必须和无头服务的名字一致这是 StatefulSet 能拿到稳定域名的前提。replicas: 3是常规集群规模小于 3 个节点时 Raft 选举容易脑裂生产环境不建议 1 个或 2 个节点跑集群模式。反亲和配置用preferredDuringSchedulingIgnoredDuringExecution它不会硬性阻止调度只是尽量把三个 Pod 分散到不同宿主机上。如果你们节点数很少硬反亲和可能导致 Pod 一直在 Pending所以这里用软策略更稳妥。存储部分我用volumeClaimTemplates而不是固定 PVC这是 StatefulSet 的标准用法。每个 Pod 会自动生成一块独立的 PVC名字形如nacos-data-nacos-0、nacos-log-nacos-0互不干扰。data 和 log 分开持久化日志不会挤占数据盘空间。3.3 环境变量逐个解析NACOS_SERVERS、数据库连接、JVM 参数环境变量是整套 YAML 里最容易写错也最值得逐条说明的地方。我按功能分组讲清楚。MODE 和 PREFER_HOST_MODE 这组是集群模式的开关。MODEcluster让 Nacos 以集群方式启动而不是单机 standalone。PREFER_HOST_MODEhostname告诉 Nacos 节点之间用主机名互相识别而不是拿 IP。在 K8s 环境里Pod 的 IP 不稳定主机名却是固定的这能省掉很多集群状态异常的问题。NACOS_REPLICAS 和 NACOS_SERVERS 是节点发现的核心。官方镜像启动脚本会根据 NACOS_REPLICAS 计算集群节点数结合 NACOS_SERVERS 里列出的地址完成节点初始化。这里有个细节NACOS_SERVERS 里三个地址要写完整 FQDN端口必须带 8848。顺序随意但三个都要在少一个节点就起不来集群。数据库连接参数注意SPRING_DATASOURCE_PLATFORMmysql这个值告诉 Nacos 用 MySQL 而不是内置 Derby。MYSQL_SERVICE_HOST 指向的是 MySQL 服务地址如果 MySQL 也在 K8s 里就写它的 Service 域名。MYSQL_SERVICE_DB_NAME 要和你在 MySQL 里建的库名保持一致并且建库的时候要先执行 Nacos 官方提供的初始化 SQL否则启动时报表不存在。密码这里用 secretKeyRef 从 Secret 读不要在 YAML 里写明文。创建 Secret 的命令我放在第 4 章部署环节。如果你只是本地测试先用明文密码也能跑但生产环境务必走 Secret 方式。JVM 参数 JVM_XMS / JVM_XMX / JVM_XMN 分别对应堆初始大小、堆最大值和新生代大小。镜像默认的 JVM 参数偏大2G 起的都有小内存节点直接 OOM。我习惯按节点规格给 1G 这个量级配置中心这种重内存操作足够用。如果你们配置很多、服务注册量很大可以放宽到 2G但别超过 Pod 内存 limit。4. 部署与验证用 kubectl 把集群跑起来并确认健康4.1 启动集群的最小命令集apply、检查、日志YAML 准备好之后部署命令其实就三四条。先把数据库密码 Secret 建好再 apply 整份 YAMLkubectl create namespace nacos kubectl create secret generic nacos-db-secret -n nacos \ --from-literalpassword你的数据库密码 kubectl apply -f nacos.yaml注意 create secret 和 apply 的顺序不能反。StatefulSet 里的 Pod 启动时需要读取 Secret如果 Secret 不存在Pod 会一直处于 CreateContainerConfigError 状态等半天也不见好。你可以用kubectl get events -n nacos --sort-by.lastTimestamp看到具体的报错。apply 完之后先看 Pod 状态kubectl get pods -n nacos -o wide正常情况下每个 Pod 会依次经历 Pending、ContainerCreating、Running 三个阶段整个过程大概一到两分钟。如果某个 Pod 长时间停在 ContainerCreating用kubectl describe pod nacos-0 -n nacos看事件多半是 PVC 或镜像拉取的问题。看 Nacos 启动日志确认集群启动过程没有报错kubectl logs -f nacos-0 -n nacos日志里出现Nacos started successfully in cluster mode字样说明这个节点启动成功。但我建议你再等十几秒因为节点启动成功只是本机进程起来了三个节点互相组成集群还需要一点时间。4.2 验证集群状态节点列表、配置写入、Leader 选举Pod 都 Running 不代表集群真的组好了需要验证节点之间的 Raft 选举和元数据同步。最直接的方式是 Nacos 控制台但在集群里我不喜欢开 NodePort而是用端口转发在本地看kubectl port-forward svc/nacos-server -n nacos 8848:8848先在 Service 里加一个普通的 ClusterIP 服务名字比如 nacos-serverselector 同样指向app: nacos端口映射 8848 和 9848。然后浏览器打开http://localhost:8848/nacos输入默认账号密码 nacos/nacos 进入控制台在集群管理里的节点列表应该能看到三个节点。如果不想开浏览器用 API 验证更直接curl -X GET http://localhost:8848/nacos/v1/console/health/readiness返回 200 说明就绪。接下来验证配置写入能力往 Nacos 里写一条配置再读出来curl -X POST http://localhost:8848/nacos/v1/cs/configs \ -d dataIdtest.yamlgroupDEFAULT_GROUPcontenthello: world curl -X GET http://localhost:8848/nacos/v1/cs/configs?dataIdtest.yamlgroupDEFAULT_GROUP能写能读说明数据库连接正常配置存储没问题。最后验证 Raft 选举把 nacos-0 杀掉观察另外两个节点是否会自动选出新 Leader配置读取是否不受影响kubectl delete pod nacos-0 -n nacosStatefulSet 会自动重建 nacos-0新旧交替期间控制台里节点列表可能短暂显示节点数变少但等新 Pod 启动完成加入集群后节点数会恢复为 3。如果这时候节点数一直少一个就说明集群节点发现有问题去查第 5 章的坑。4.3 客户端接入集群地址到底该填什么部署完集群最后一步是让业务服务接进来。客户端填的地址不是某个节点的 IP而是 K8s 里的 Service 域名。对于 Java 技术栈通常配置这样一段spring.cloud.nacos.discovery.server-addrnacos-server.nacos.svc.cluster.local:8848 spring.cloud.nacos.config.server-addrnacos-server.nacos.svc.cluster.local:8848这个地址指向的 ClusterIP Service 会把请求负载均衡到三个节点上。客户端拿到响应后会从 Nacos 返回的节点列表里解析出每个节点的真实地址然后直接连对应节点的 9848 gRPC 端口。所以 9848 端口在 Service、NodePort 或负载均衡器里必须暴露否则客户端能注册但后续心跳超时。如果业务服务和 Nacos 不在同一个命名空间域名就要写全比如nacos-server.nacos.svc.cluster.local。在同一个命名空间里可以简写成nacos-server。我习惯在配置文件里始终写完整 FQDN省得以后挪命名空间时还要改配置。5. Nacos 集群部署避坑指南五条血泪经验按顺序查5.1 PVC Pending 导致 Pod 卡在 ContainerCreating现象三个 Pod 全部停在 ContainerCreatingkubectl get pvc -n nacos看到 NAME 以nacos-data-nacos-开头的 PVC 状态是 Pendingkubectl describe pod nacos-0里最后一行写着waiting for PVC ... to be created。原因集群里没有可用的 StorageClass或者你在 volumeClaimTemplates 里写的 storageClassName 和集群里实际存在的对不上。还有一种情况是存储插件装好了但没把它设为默认存储类StatefulSet 又没显式指定存储类动态供给就不知道该用哪个。解决先执行kubectl get storageclass看集群有哪些存储类确认有(default)标记的那条。如果 StatefulSet 里写了 storageClassName删掉让它走默认如果集群根本没有存储类需要先部署存储插件或者手动创建 PV 再创建同名 PVC。注意 PVC 名字要和 StatefulSet 生成的完全一致比如nacos-data-nacos-0一个 Pod 对应一块 PVC。5.2 节点之间互相注册不上NACOS_SERVERS 和域名解析的坑现象每个节点都启动成功了但控制台节点列表只有一个节点日志里反复出现raft相关报错或者unable to connect to peer。原因NACOS_SERVERS 里域名拼写错误或者无头服务的 selector 没匹配上 Pod导致域名解析不到。另一个常见原因是把域名写成了nacos-0.nacos-headless这种简写但命名空间不是默认的default解析就失败了。解决先在 Pod 里测试域名解析kubectl exec -it nacos-0 -n nacos -- sh -c ping nacos-1.nacos-headless.nacos.svc.cluster.local能解析出 IP 再谈下一步。如果 ping 不通检查无头服务是否存在、selector 是否命中。如果域名没问题再看 Pod 之间 8848 端口通不通可以用curl试试访问对端节点的/nacos/路径确认网络没被 NetworkPolicy 拦。5.3 内存 OOM 导致节点反复重启现象kubectl get pods -n nacos看到 Pod 好一阵又重启kubectl describe pod里 Last State 是 OOMKilled。原因Nacos 镜像默认 JVM 堆设置偏大常见的是-Xms2g -Xmx2g而你的节点内存可能只有 4G还跑着其他服务K8s 的 Pod limit 一压就 OOM。另外 2.x 版本的元数据、健康检查在集群模式下会多占一些堆外内存。解决显式设置 JVM 参数。在环境变量里加 JVM_XMS1g、JVM_XMX1g、JVM_XMN512m然后重启 Pod。如果配置中心管理的配置量特别大也可以把堆给到 2G但要同步调大 Pod 的 resources.limits.memory比如给 4Gi。改完再去kubectl logs确认启动日志里 JVM 参数生效。5.4 只开了 8848 端口客户端连不上 9848现象业务服务能注册成功但一段时间后日志里出现gRPC request timeout、Client not connected之类的报错注册列表里的实例频繁上下线。原因客户端通过 8848 拿到 Nacos 节点列表后会自动连接每个节点的 9848 端口做 gRPC 长连接。你只暴露了 88489848 被防火墙或 Service 挡住了长连接自然建不起来。解决检查 Service 里是否加了 9848 端口映射NodePort 或负载均衡器是否也放行了 9848。如果是云环境安全组规则里也要加这条。这里给一个自查命令kubectl exec -it nacos-0 -n nacos -- sh -c curl -s http://localhost:9848服务端对 gRPC 请求会返回一些响应或至少不是连接被拒。如果连接直接拒绝说明配置没生效回去改 Service。这个坑我踩过一次之后已经养成了习惯所有 Nacos 相关的 Service 定义里端口永远是 8848 和 9848 成对出现。5.5 版本跨度大升级后节点无法加入集群现象从 1.4.x 直接升到 2.3.x旧节点还好新节点日志里一直报元数据不一致或者节点无法加入集群。原因Nacos 1.x 和 2.x 的集群元数据格式、节点间通信协议都变了跨大版本升级不是换镜像那么简单。如果数据库里的配置数据还带着 1.x 的格式新版本节点启动时可能无法正确加载。解决有大版本升级需求时先备份数据库和本地 data 目录然后按官方升级路径一步步走1.x 先升到 2.x 早期的稳定版本确认数据正常后再升到最新的 2.3.x。升级前先在 staging 环境完整跑一遍部署和验证流程尤其是第 4 章讲的配置写入和读出测试没问题再动生产。6. 最后把 YAML 收进一个脚本让集群可以反复重建部署到这个程度我通常会把整个流程收进一个脚本方便测试环境反复重建集群也方便以后扩容。脚本的核心思路是把变量从 YAML 里抽出来用环境变量注入最后统一 apply。#!/usr/bin/env bash # deploy-nacos.sh 用法: ./deploy-nacos.sh namespace mysql-host mysql-password set -euo pipefail NAMESPACE${1:-nacos} MYSQL_HOST${2:?请传入 MySQL 地址} MYSQL_PASSWORD${3:?请传入 MySQL 密码} IMAGE_TAG${NACOS_IMAGE_TAG:-v2.3.2} export NAMESPACE MYSQL_HOST MYSQL_PASSWORD IMAGE_TAG kubectl create namespace ${NAMESPACE} --dry-runclient -o yaml | kubectl apply -f - kubectl create secret generic nacos-db-secret -n ${NAMESPACE} \ --from-literalpassword${MYSQL_PASSWORD} \ --dry-runclient -o yaml | kubectl apply -f - # 用 envsubst 渲染 YAML 模板再 apply envsubst nacos.yaml.tpl | kubectl apply -f - # 等待 StatefulSet 完成滚动 kubectl rollout status sts/nacos -n ${NAMESPACE} --timeout300s echo Nacos 集群部署完成。脚本里envsubst会把模板中的$NAMESPACE、$MYSQL_HOST等变量替换成实际值。你需要把第 3 章的 YAML 复制一份保存为nacos.yaml.tpl把命名空间、数据库地址、镜像 tag 换成变量占位符。这样一套配置就能在 dev、staging、prod 任意环境复用不会再出现这个环境改了没同步到那个环境的问题。还有一件重要的事删除集群时要注意顺序。我习惯先删 StatefulSet再删 PVC最后删 Servicekubectl delete sts nacos -n nacos --cascadeorphan kubectl delete pvc -n nacos -l appnacos kubectl delete svc -n nacos nacos-headless nacos-server--cascadeorphan会让 StatefulSet 删除时保留 PVC 数据这样万一只是临时想停掉集群数据还在重新 apply 一份 YAML 就能原地复活。如果确定数据不要了再单独删 PVC。这个习惯帮我避免过很多次误删数据的事故。这套方案我前后在几个测试环境里重建过不下十次最耗时的永远是数据库初始化那一步其余环节从 apply 到三节点全部就绪基本控制在三分钟内。如果你部署时遇到控制台只有单节点、客户端连不上、Pod 反复重启按第 5 章的顺序逐条排查大部分问题都能定位到。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑