K8s中Nacos集群部署三大核心:StatefulSet+Headless+外置MySQL 简介这是一套面向Kubernetes初学者与运维人员的Nacos高可用集群部署脚本专为简化云原生环境下服务发现与配置中心的落地而设计。资源提供开箱即用的YAML清单文件覆盖数据库配置、Headless Service、StatefulSet、Service及Ingress全流程显著降低K8s部署Nacos的认知门槛与操作复杂度适合无K8s集群管理经验但具备基础Linux和YAML语法能力的开发者快速上手。压缩包共6个文件5个YAML配置文件1个说明文本总大小仅3KB轻量精简其中YAML文件按执行顺序编号命名逻辑清晰便于理解组件依赖关系与部署节奏。目前已有763人学习下载读者可直接复用脚本完成Nacos三节点集群在K8s中的稳定部署获得可验证的生产就绪型配置模板、清晰的组件分层结构以及关键参数注释参考大幅缩短环境搭建周期。1. 为什么你写的 Nacos 集群 YAML 在 k8s 里起不来不是镜像拉不到是服务发现链路根本没闭环你手头有一份标着「k8s nacos集群部署脚本yaml」的文件kubectl apply -f 后 Pod 状态卡在CrashLoopBackOff或PendingNacos 控制台能打开但节点列表永远只显示自己服务注册后消费者查不到实例——这不是配置写错了而是你漏掉了 k8s 环境下 Nacos 集群最致命的三个隐性前提节点间通信必须走 Pod IP 而非 Service ClusterIP、持久化存储必须跨 Pod 生命周期存活、集群发现机制必须绕过 k8s Service 的 DNS 轮询黑盒。这份笔记不讲 Docker Compose 怎么跑单机也不抄 Helm Chart 的黑盒封装而是用纯 YAML 原生 k8s 对象从 StatefulSet 的拓扑控制、Headless Service 的 DNS 解析原理、Nacos 自定义集群配置三者咬合出发带你亲手搭出一个能过生产灰度、支持滚动更新、节点增删自动同步的 Nacos 集群。适合正在落地微服务注册中心、被 Nacos 页面报错request error, please try again later!卡住的中级 DevOps 工程师和 Java 架构师——你不需要懂 Spring Cloud Alibaba 内部怎么调用但得清楚 k8s 的 Endpoints 和 Nacos 的cluster.conf文件之间那条看不见的数据线是怎么接通的。2. 用 StatefulSet Headless Service 搭建可伸缩的 Nacos 集群为什么不用 DeploymentNacos 集群节点不是无状态的 Web 服务每个实例必须拥有固定网络标识hostname、独立存储路径、可预测的启动顺序。Deployment 生成的 Pod 名称随机、Service ClusterIP 会做负载均衡、挂载的 PVC 无法绑定到特定 Pod——这三者叠加直接导致 Nacos 启动时读取不到其他节点地址集群初始化失败。StatefulSet 是唯一解它保证 Pod 名称按序生成nacos-0, nacos-1, nacos-2配合 Headless Service 提供稳定的 DNS 记录nacos-0.nacos-headless.default.svc.cluster.local让每个 Pod 能通过 hostname 直连其他节点。下面是最小可行的集群骨架3 节点起步Nacos 官方推荐最小集群规模2.1 创建 Headless Service暴露每个 Pod 的真实 IP禁用 ClusterIP# nacos-headless-svc.yaml apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: default spec: clusterIP: None # 关键禁用 ClusterIP启用 Headless 模式 publishNotReadyAddresses: true # 允许未就绪 Pod 的 A 记录提前注册避免启动竞争 ports: - port: 8848 name: client - port: 7848 name: raft selector: app: nacos提示publishNotReadyAddresses: true是血泪经验。Nacos 启动要 30 秒若 DNS 不提前注册nacos-0 启动时查不到 nacos-1 的地址集群发现直接失败。这个参数让 CoreDNS 在 Pod 还在ContainerCreating阶段就返回其 IP解决“先有鸡还是先有蛋”问题。2.2 定义 StatefulSet固定主机名、独立 PVC、有序启动# nacos-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: default spec: serviceName: nacos-headless # 必须指向上一步的 Headless Service 名 replicas: 3 podManagementPolicy: OrderedReady # 强制按 0→1→2 顺序启动并等待就绪 updateStrategy: type: RollingUpdate rollingUpdate: partition: 0 # 滚动更新时从最高序号 Pod 开始更新 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: containers: - name: nacos image: nacos/nacos-server:v2.3.2 imagePullPolicy: IfNotPresent ports: - containerPort: 8848 name: client - containerPort: 9848 name: client-ssl - containerPort: 7848 name: raft env: - name: MODE value: cluster - name: NACOS_SERVERS value: nacos-0.nacos-headless.default.svc.cluster.local:8848 nacos-1.nacos-headless.default.svc.cluster.local:8848 nacos-2.nacos-headless.default.svc.cluster.local:8848 - name: PREFER_HOST_MODE value: hostname - name: NACOS_APPLICATION_PORT value: 8848 - name: NACOS_SERVER_IP valueFrom: fieldRef: fieldPath: status.podIP volumeMounts: - name: data mountPath: /home/nacos/data - name: logs mountPath: /home/nacos/logs - name: init-config mountPath: /home/nacos/init.d/custom.properties subPath: custom.properties volumes: - name: init-config configMap: name: nacos-init-config # 注意PVC 模板在 volumeClaimTemplates 中定义不在 volumes 下 volumeClaimTemplates: # 为每个 Pod 动态创建独立 PVC - metadata: name: data annotations: volume.beta.kubernetes.io/storage-class: local-path # 替换为你环境的真实 StorageClass spec: accessModes: [ReadWriteOnce] resources: requests: storage: 5Gi - metadata: name: logs annotations: volume.beta.kubernetes.io/storage-class: local-path spec: accessModes: [ReadWriteOnce] resources: requests: storage: 2Gi逻辑说明serviceName: nacos-headless将 StatefulSet 绑定到 Headless Service这是 Pod 获取稳定 DNS 名称的前提NACOS_SERVERS环境变量硬编码了全部节点的 FQDN这是 Nacos 集群发现的唯一依据Nacos 不支持通过 k8s API 动态发现节点PREFER_HOST_MODE: hostname强制 Nacos 使用 hostname 注册而非 Pod IP确保集群内服务发现走 DNS 而非直连 IP避免 IP 变更导致心跳失效volumeClaimTemplates为每个 Pod 创建专属 PVCdata目录存集群元数据如 raft log、snapshotlogs存日志便于排查——没有这一步Pod 重启后集群状态全丢等同于单点故障。2.3 初始化 ConfigMap注入集群必需的 custom.propertiesNacos v2.x 默认不读取cluster.conf而是依赖custom.properties中的nacos.core.member.lookup.typeraft和nacos.core.member.lookup.pluginnacos。这个文件必须挂载到容器内/home/nacos/init.d/目录下且名称必须为custom.propertiesNacos 启动时硬编码查找路径# nacos-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: nacos-init-config namespace: default data: custom.properties: | # 集群模式核心开关 nacos.core.member.lookup.typeraft nacos.core.member.lookup.pluginnacos # Raft 通信端口必须与容器 port 7848 一致 nacos.core.raft.port7848 # 关闭嵌入式 Derby强制使用外部数据库生产必须 nacos.core.db.num1 nacos.core.db.url.0jdbc:mysql://mysql-nacos.default.svc.cluster.local:3306/nacos?characterEncodingutf8connectTimeout1000socketTimeout3000autoReconnecttrueuseSSLfalseserverTimezoneAsia/Shanghai nacos.core.db.usernacos nacos.core.db.passwordnacos123 # 日志级别调低方便观察集群握手过程 logging.level.com.alibaba.nacosINFO参数说明nacos.core.member.lookup.typeraft是 Nacos 2.x 集群的基石替代了老版本的cluster.conf文件nacos.core.db.url.0必须指向一个高可用 MySQL 实例建议用 StatefulSet 部署 MySQL或接入云 RDSNacos 集群元数据如服务实例、配置快照全部存在数据库本地磁盘只存临时 raft log若你暂无 MySQL可临时用nacos.core.member.lookup.typestandalone测试但此模式绝对不可用于生产节点宕机即丢失全部注册信息。3. 配置外置 MySQL为什么 Nacos 集群不能只靠本地磁盘Nacos 官方文档明确警告“Embedded storage is only for testing and development purposes.”。在 k8s 环境中这句话的代价被放大十倍StatefulSet 的 PVC 是绑定到具体 Node 的若 nacos-0 所在 Node 故障即使 PVC 被调度到新 NodeRaft 日志的连续性也大概率中断集群无法自动恢复。外置 MySQL 不仅解决数据持久化更承担了集群成员状态同步、配置变更广播、服务健康检查结果聚合三大核心职责。以下是生产级 MySQL 部署要点3.1 创建 MySQL StatefulSet保证主库高可用# mysql-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql namespace: default spec: serviceName: mysql replicas: 1 # 生产建议用 MHA 或 Percona XtraDB Cluster此处简化为单主 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0.33 env: - name: MYSQL_ROOT_PASSWORD value: root123 - name: MYSQL_DATABASE value: nacos - name: MYSQL_USER value: nacos - name: MYSQL_PASSWORD value: nacos123 ports: - containerPort: 3306 volumeMounts: - name: mysql-persistent-storage mountPath: /var/lib/mysql volumes: - name: mysql-persistent-storage persistentVolumeClaim: claimName: mysql-pv-claim --- # mysql-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pv-claim namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi storageClassName: local-path # 替换为你的 StorageClass注意MySQL PVC 必须使用ReadWriteOnce且 StorageClass 需支持Retain回收策略避免误删 PV 导致 Nacos 数据库彻底丢失。3.2 初始化 Nacos 数据库表结构Nacos 发行包中自带 SQL 脚本需手动执行。进入 MySQL Pod 执行kubectl exec -it mysql-0 -- mysql -unacos -pnacos123 nacos /path/to/nacos-mysql.sqlSQL 脚本来源下载 Nacos v2.3.2 发行包解压后路径为distribution/conf/nacos-mysql.sql。该脚本创建config_info,service_info,instance等 12 张核心表缺少任意一张Nacos 启动时会报Table nacos.config_info doesnt exist并退出。3.3 验证数据库连接在 Nacos Pod 内测试进入任一 Nacos Pod用telnet和mysql命令双重验证# 进入 Pod kubectl exec -it nacos-0 -- sh # 测试 MySQL 网络连通性端口 3306 telnet mysql-nacos.default.svc.cluster.local 3306 # 测试 MySQL 认证需提前安装 mysql-client mysql -h mysql-nacos.default.svc.cluster.local -unacos -pnacos123 -e SELECT COUNT(*) FROM information_schema.tables WHERE table_schemanacos;关键现象若telnet成功但mysql命令报Access denied说明用户名密码错误或 MySQL 用户权限未授权给该 host若telnet失败检查 MySQL Service 名称是否拼写正确必须是mysql-nacos与 ConfigMap 中jdbc:mysql://mysql-nacos...一致。4. 避坑Nacos 集群在 k8s 中的 5 个高频翻车现场部署过程中90% 的失败不是 YAML 写错而是对 k8s 网络模型和 Nacos 启动流程的理解偏差。以下是我在 3 个不同客户环境踩过的真坑按发生频率排序4.1 现象Pod 日志反复打印failed to requestnacos-0一直无法加入集群原因NACOS_SERVERS环境变量中的域名无法解析或目标 Pod 未启动成功。常见于Headless Service 名称与 StatefulSet 的serviceName字段不一致publishNotReadyAddresses: true未开启nacos-0 启动时 DNS 查不到 nacos-1 的 A 记录CoreDNS 配置错误集群内无法解析*.svc.cluster.local域名。解决在 nacos-0 Pod 内执行nslookup nacos-1.nacos-headless.default.svc.cluster.local确认能返回 IP检查 CoreDNS 日志kubectl logs -n kube-system deployment/coredns看是否有NXDOMAIN错误临时将NACOS_SERVERS改为nacos-0.nacos-headless.default.svc.cluster.local:8848单节点模式验证基础环境是否正常。4.2 现象Nacos 控制台能打开但「集群管理」页面节点数始终为 1原因Nacos 启动后未正确加载custom.properties或其中nacos.core.member.lookup.type值错误。解决进入 Pod 查看挂载文件ls -l /home/nacos/init.d/确认custom.properties存在且权限为 644检查 Nacos 启动日志kubectl logs nacos-0 | grep -i raft\|member\|lookup应看到RaftPeerSet initialized和MemberManager initialized若日志出现Failed to load member lookup plugin说明custom.properties格式错误如多了一个空格或中文字符。4.3 现象滚动更新时新 Pod 启动失败报java.net.BindException: Address already in use原因Nacos 默认监听0.0.0.0:8848但 StatefulSet 更新时旧 Pod 未完全终止端口被占用。解决在 StatefulSet 的env中添加- name: NACOS_SERVER_PORT value: 8848 - name: NACOS_SERVER_IP valueFrom: fieldRef: fieldPath: status.podIP并确保容器内nacos/bin/startup.sh未被修改——官方镜像已处理端口复用逻辑切勿自行改startup.sh添加-Dnacos.server.port8848参数会导致 JVM 参数冲突。4.4 现象服务注册后在控制台「服务列表」能看到但「实例列表」为空原因Nacos 客户端 SDK 版本与服务端不兼容。Nacos v2.3.x 要求客户端使用2.2.0版本老版本如 1.4.0无法上报 v2 协议的实例心跳。解决检查客户端 pom.xml 或 build.gradle升级com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery至2022.0.0.0在客户端启动日志中搜索NacosDiscoveryClient确认输出Registering service xxx with nacos且无Unsupported protocol报错。4.5 现象Nacos 页面修改密码报错request error, please try again later!原因Nacos 控制台前端请求/v1/auth/users/update接口但该接口依赖nacos-auth模块而默认镜像未启用鉴权。解决在 ConfigMap 的custom.properties中添加# 启用鉴权 nacos.core.auth.enabledtrue nacos.core.auth.system.typenacos nacos.core.auth.plugin.nacos.token.expire.seconds18000 nacos.core.auth.plugin.nacos.token.secret.keySecretKey012345678901234567890123456789012345678901234567890123456789注意token.secret.key必须为 64 位字符串否则启动报错Invalid key length首次登录用默认账号nacos/nacos登录后立即修改密码。5. 验证集群健康与动态扩缩容用 curl 和 kubectl 做真刀真枪的压力测试部署完成不等于集群可用。真正的验证必须模拟生产流量注册 100 服务、每秒 500 心跳、手动 kill 一个 Pod 观察自动恢复。以下是可直接执行的验证清单5.1 三步验证集群握手成功执行以下命令必须全部返回200 OK且响应体包含code:200# 1. 检查各节点自身健康状态 curl -s http://$(kubectl get pod nacos-0 -o jsonpath{.status.podIP}):8848/nacos/v1/ns/operator/metrics | jq .code # 2. 检查集群节点列表应在响应中看到 3 个节点 curl -s http://$(kubectl get pod nacos-0 -o jsonpath{.status.podIP}):8848/nacos/v1/ns/operator/cluster/state | jq .members | length # 3. 检查 Raft 状态leader 应只有一个follower 两个 curl -s http://$(kubectl get pod nacos-0 -o jsonpath{.status.podIP}):8848/nacos/v1/ns/operator/raft/state | jq .raftGroupMember预期输出步骤 2 返回3步骤 3 返回的 JSON 中leader字段值为nacos-0.nacos-headless.default.svc.cluster.local:8848或 nacos-1/nacos-2其余为follower。5.2 模拟服务注册与发现用 curl 直接调用 Nacos OpenAPI# 注册一个测试服务替换 IP 为任一 Nacos Pod 的 IP NACOS_IP$(kubectl get pod nacos-0 -o jsonpath{.status.podIP}) curl -X POST http://$NACOS_IP:8848/nacos/v1/ns/instance?serviceNametest-serviceip10.10.10.10port8080weight1.0ephemeraltrue # 查询该服务的所有实例 curl -s http://$NACOS_IP:8848/nacos/v1/ns/instance/list?serviceNametest-service | jq .hosts | length # 注销实例 curl -X DELETE http://$NACOS_IP:8848/nacos/v1/ns/instance?serviceNametest-serviceip10.10.10.10port8080ephemeraltrue关键指标注册后 1 秒内/instance/list应返回1注销后 1 秒内返回0。若延迟超过 5 秒检查nacos.core.notify.interval配置默认 5000ms是否被意外修改。5.3 动态扩容从 3 节点平滑扩到 5 节点# 修改 StatefulSet 副本数无需删除重建 kubectl scale statefulset nacos --replicas5 # 观察新 Pod 启动日志确认加入集群 kubectl logs -f nacos-3 | grep -i join cluster\|raft peer set # 验证集群节点数变为 5 curl -s http://$(kubectl get pod nacos-0 -o jsonpath{.status.podIP}):8848/nacos/v1/ns/operator/cluster/state | jq .members | length血泪经验扩容后新节点nacos-3和nacos-4的NACOS_SERVERS环境变量无需修改因为 StatefulSet 的.spec.template.spec.containers.env是全局生效的所有 Pod 都读取同一份NACOS_SERVERS字符串。Nacos Raft 协议会自动将新节点纳入共识组整个过程无需人工干预。5.4 故障注入测试验证高可用底线# 1. 删除 leader 节点模拟 Node 故障 kubectl delete pod nacos-0 # 2. 等待 30 秒检查新 leader 是否选举成功 kubectl logs nacos-1 | grep -i become leader # 3. 立即注册一个新服务验证服务发现不中断 curl -X POST http://$(kubectl get pod nacos-1 -o jsonpath{.status.podIP}):8848/nacos/v1/ns/instance?serviceNamefailover-testip192.168.1.100port8080 # 4. 检查服务是否在新 leader 上可见 curl -s http://$(kubectl get pod nacos-1 -o jsonpath{.status.podIP}):8848/nacos/v1/ns/instance/list?serviceNamefailover-test | jq .hosts[0].ip合格标准从删除 Pod 到新服务注册成功全程不超过 45 秒/instance/list返回的 IP 必须是192.168.1.100。若超时检查nacos.core.raft.heartbeat.interval默认 5000ms和nacos.core.raft.election.timeout默认 15000ms是否被调大——生产环境建议保持默认值激进调小反而引发频繁脑裂。我带团队落地 Nacos 集群时坚持一条铁律所有 YAML 必须通过kubectl apply -f一键部署所有验证脚本必须写成 Bash 函数放入 CI 流水线每次发布前自动执行 5.1~5.4 全套测试。曾经有次因疏忽没验证publishNotReadyAddresses上线后凌晨三点告警——3 个节点互相认为对方失联注册中心雪崩。现在我的.bashrc里永久挂着一行 aliasalias nacos-checkkubectl get pod -l appnacos curl -s http://$(kubectl get pod nacos-0 -o jsonpath{.status.podIP}):8848/nacos/v1/ns/operator/cluster/state | jq .members | wc -l。希望帮到你。本文还有配套的精品资源点击获取