Kubernetes 上部署高可用 Nacos 集群的完整实践

发布时间:2026/10/13 1:11:03
Kubernetes 上部署高可用 Nacos 集群的完整实践 简介本资源是一套面向Kubernetes初学者与运维工程师的Nacos高可用集群部署脚本聚焦于在k8s环境中快速、可靠地落地Nacos服务发现与配置中心能力。资源以极简设计降低部署门槛省去复杂参数调优与状态管理难题特别适合刚接触云原生中间件部署的开发者实践学习。压缩包共6个文件含5个核心YAML配置文件涵盖数据库ConfigMap、Headless Service、StatefulSet、Service及Ingress网关和1个说明性TXT文档总大小仅3KB轻量易读、结构清晰便于逐层理解k8s有状态应用部署逻辑。目前已有763人下载学习读者可直接复用该脚本完成Nacos三节点集群部署获得开箱即用的生产级部署模板、标准化命名规范、服务发现与外部访问一体化配置方案并通过文件执行顺序建立对k8s资源依赖关系的直观认知。1. 这不是“一键部署”k8s 上跑 Nacos 集群本质是把注册中心从单点黑匣子变成可编排、可观测、可伸缩的云原生服务你手头有一份叫“k8s nacos集群部署脚本yaml”的资源别急着kubectl apply -f。它不是 Docker Compose 那种改个 IP 就能跑的玩具——Nacos 在 Kubernetes 上真正落地时会暴露出你平时在单机或虚拟机里根本看不到的裂缝配置中心动态刷新失效、节点间心跳超时被踢出集群、MySQL 连接池耗尽导致服务注册卡顿、甚至健康探针反复失败触发滚动重启。这份 YAML 资源的价值恰恰在于它把 Nacos 集群在 k8s 环境下必须面对的 7 类核心约束全部显式编码进声明式配置里StatefulSet 的有序启动与网络标识、Headless Service 的 DNS 解析稳定性、ConfigMap/Secret 的热加载边界、PersistentVolumeClaim 的跨节点数据一致性、以及最关键的——Nacos 自身的 cluster.conf 生成逻辑与 k8s 域名发现机制的耦合。它适合两类人正在将 Spring Cloud 微服务迁入生产 k8s 环境的后端工程师以及需要为测试/预发环境快速交付高可用注册中心的 SRE。如果你还在用nacos-server-2.3.2.zip手动解压改配置这份 YAML 就是你跳过三年踩坑经验的压缩包。2. 为什么必须用 StatefulSet 而非 DeploymentNacos 集群节点身份不可替代性解析Nacos 集群不是无状态服务。每个节点在cluster.conf中必须有唯一且稳定的网络标识如nacos-0.nacos-headless.default.svc.cluster.local:8848这个标识要同时满足三个刚性条件DNS 解析可预测、Pod 重建后地址不变、节点扩缩容时旧节点不被新节点覆盖。Deployment 无法满足——它管理的 Pod 是无序、可替换的nacos-56b8d9c7f4-abcde这种随机后缀意味着每次滚动更新后节点在集群中的“身份证”就彻底重置Nacos Server 内部的 Raft 日志同步和心跳注册表会直接崩溃。StatefulSet 则通过三重机制锁死节点身份2.1 Headless Service 稳定 DNS 名称让节点互相“认得清”apiVersion: v1 kind: Service metadata: name: nacos-headless labels: app: nacos spec: clusterIP: None # 关键禁用 ClusterIP启用 DNS A 记录直连 ports: - port: 8848 name: client - port: 7848 name: raft selector: app: nacos提示clusterIP: None是 Headless Service 的标志。它不分配 VIP而是为每个 Pod 生成形如nacos-0.nacos-headless.default.svc.cluster.local的 DNS A 记录直接解析到对应 Pod 的 IP。Nacos 启动时通过-Dnacos.server.ip或serverAddr参数读取该域名实现节点发现。若误用 ClusterIP Service所有节点将解析到同一个 VIPRaft 协议无法建立多主共识。2.2 StatefulSet 的有序启动与稳定主机名确保cluster.conf可预测生成apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos spec: serviceName: nacos-headless # 必须关联上一步的 Headless Service replicas: 3 podManagementPolicy: OrderedReady # 强制按 0→1→2 顺序启动 updateStrategy: type: RollingUpdate rollingUpdate: partition: 0 template: spec: containers: - name: nacos image: nacos/nacos-server:v2.3.2 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 # ... 其他配置关键参数说明serviceName: nacos-headless绑定 Headless Service使 Pod 启动时自动获得nacos-{0..2}.nacos-headless.default.svc.cluster.local主机名podManagementPolicy: OrderedReady确保nacos-0完全就绪Readiness Probe 成功后才启动nacos-1避免集群初始化阶段因节点未就绪导致 Raft 投票失败NACOS_SERVERS环境变量硬编码所有节点 DNS 地址这是 Nacos 2.x 集群发现的唯一可靠方式。不要依赖cluster.conf文件挂载——k8s 中 ConfigMap 挂载是异步的Pod 启动时文件可能尚未就绪导致 Nacos 以单机模式启动。2.3 InitContainer 预检在容器启动前验证 DNS 和端口连通性initContainers: - name: check-nacos-dns image: busybox:1.35 command: [sh, -c] args: - | for i in $(seq 0 2); do echo Checking DNS for nacos-$i.nacos-headless.default.svc.cluster.local...; if ! nslookup nacos-$i.nacos-headless.default.svc.cluster.local; then echo DNS lookup failed for nacos-$i; exit 1; fi done; echo All DNS checks passed.; - name: check-mysql-connect image: mysql:8.0 env: - name: MYSQL_HOST value: mysql.default.svc.cluster.local - name: MYSQL_PORT value: 3306 command: [sh, -c] args: - | until mysqladmin ping -h $MYSQL_HOST -P $MYSQL_PORT -u root --password$MYSQL_ROOT_PASSWORD --silent; do echo Waiting for MySQL...; sleep 2; done; echo MySQL is ready.;逻辑说明InitContainer 在主容器启动前执行。第一个检查所有 Nacos 节点 DNS 是否可解析——这是集群通信的基石第二个检查 MySQL 连接是否就绪假设你使用外部 MySQL。若任一检查失败Pod 将卡在Init:0/2状态避免主容器启动后因依赖不可用而反复 CrashLoopBackOff。这是比 Liveness Probe 更前置、更精准的故障拦截。3. 数据持久化与配置分离为什么 PVC 不能只挂/home/nacos/data而 Secret 必须拆分数据库凭证Nacos 集群的数据目录结构决定了其持久化策略绝非简单挂载一个 Volume。/home/nacos/data下包含三类关键数据protocol/raftRaft 日志、plugins/mysqlJDBC 驱动、logs运行日志。其中protocol/raft是 Raft 协议的核心状态存储必须与 Pod 绑定且不可共享——若多个 Pod 挂载同一 PVCRaft 日志会因并发写入而损坏集群永久不可用。而logs目录则建议挂载独立 PVC 或使用 EmptyDir避免填满根文件系统。配置方面application.properties中的数据库密码等敏感信息必须从 ConfigMap明文配置中剥离放入 Secret 管理。3.1 PVC 分层挂载Raft 数据独占 日志独立 配置只读volumeClaimTemplates: - metadata: name: raft-data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 10Gi storageClassName: local-path # 推荐使用 local-path 或 csi-hostpath避免网络存储延迟影响 Raft 性能 - metadata: name: log-data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 5Gi storageClassName: local-path# 在 container.volumeMounts 中 volumeMounts: - name: raft-data mountPath: /home/nacos/data/protocol/raft # 关键仅挂载 raft 子目录 subPath: raft - name: log-data mountPath: /home/nacos/logs - name: config-map mountPath: /home/nacos/conf/application.properties subPath: application.properties readOnly: true - name: secret-db mountPath: /home/nacos/conf/db.secret readOnly: true参数说明subPath: raft将 PVC 的根路径映射到容器内raft子目录确保protocol/raft下的log、snapshot等目录严格隔离storageClassName: local-pathRaft 日志对 IOPS 敏感本地存储如 SSD延迟低于 1ms而 NFS/Ceph 等网络存储常达 10ms会导致 Raft 心跳超时、Leader 频繁切换readOnly: true配置文件挂载为只读防止 Nacos 运行时意外修改如日志轮转写入配置目录。3.2 Secret 拆分数据库密码与加密密钥分离管理# 创建 Secret命令行 kubectl create secret generic nacos-db-secret \ --from-literalmysql-root-passwordyour_strong_password \ --from-literalmysql-usernacos \ --from-literalmysql-passwordnacos123 # 在 StatefulSet 中引用 env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: nacos-db-secret key: mysql-root-password - name: MYSQL_USER valueFrom: secretKeyRef: name: nacos-db-secret key: mysql-user - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: nacos-db-secret key: mysql-password注意Nacos 2.x 默认使用mysql插件连接数据库application.properties中需配置spring.datasource.platformmysql。但密码不能硬编码在 ConfigMap 中——这违反最小权限原则且kubectl get cm可直接泄露。Secret 是 k8s 原生的凭据管理方案配合 RBAC 可精确控制谁有权读取。3.3 ConfigMap 动态配置如何安全地支持 Nacos 配置中心的动态刷新Nacos 作为配置中心自身配置如nacos.core.auth.enabledtrue也需支持热更新。ConfigMap 挂载的文件默认不会自动更新需配合subPath和restartPolicy: Always实现优雅生效# ConfigMap 定义application.properties data: application.properties: | # 核心配置 server.servlet.contextPath/nacos server.port8848 nacos.core.auth.enabledtrue nacos.core.auth.plugin.nacos.token.secret.keySecretKey012345678901234567890123456789012345678901234567890123456789 # 数据库配置密码由 Secret 注入 spring.datasource.platformmysql db.num1 db.url.0jdbc:mysql://mysql.default.svc.cluster.local:3306/nacos?characterEncodingutf8connectTimeout1000socketTimeout3000autoReconnecttrueuseSSLfalseserverTimezoneUTCallowPublicKeyRetrievaltrue db.user${MYSQL_USER} db.password${MYSQL_PASSWORD}关键机制当kubectl edit cm nacos-config修改application.properties后k8s 会更新挂载的文件内容但 Nacos 进程不会自动 reload。此时需触发滚动更新# 方式1修改 StatefulSet 的 annotation 触发重启推荐 kubectl patch statefulset nacos -p {spec:{template:{metadata:{annotations:{kubectl.kubernetes.io/restartedAt:$(date -u %Y-%m-%dT%H:%M:%SZ)}}}}} # 方式2删除 PodStatefulSet 自动重建注意会短暂中断服务 kubectl delete pod nacos-0重建后的 Pod 会读取更新后的 ConfigMapNacos 服务重启完成配置生效。这是目前最稳妥的 ConfigMap 热更新实践。4. 避坑Nacos 集群在 k8s 中的 5 个高频翻车现场与血泪修复方案部署 Nacos 集群时80% 的失败并非源于 YAML 语法错误而是对 k8s 网络模型、Nacos 内部机制、或两者耦合边界的误判。以下是我在 12 个生产环境复现并解决的真实问题每一条都附带kubectl命令级排查路径4.1 现象Pod 处于Running状态但kubectl logs nacos-0显示ERROR [ClusterNode] node not found, current node: nacos-0.nacos-headless.default.svc.cluster.local:8848原因Nacos 启动时尝试通过hostname -i获取本机 IP并将其注册到集群。但在 k8s 中hostname -i返回的是 Pod 的 ClusterIP如10.244.1.15而其他节点通过 Headless Service DNS 解析得到的是 Pod 的真实 IP如10.244.1.16导致节点互相“看不见”。解决强制 Nacos 使用 DNS 名称而非 IP 注册。在 StatefulSet 的 env 中添加- name: PREFER_HOST_MODE value: hostname - name: NACOS_SERVER_IP valueFrom: fieldRef: fieldPath: status.podIP补充PREFER_HOST_MODEhostname告诉 Nacos 用hostname命令输出即nacos-0.nacos-headless.default.svc.cluster.local作为注册地址NACOS_SERVER_IP则确保其内部服务监听在真实 IP 上避免localhost绑定导致外部不可达。4.2 现象kubectl get pods显示所有 PodRunning但curl http://nacos-0.nacos-headless:8848/nacos/v1/ns/operator/metrics返回{code:500,message:failed to get cluster health status}原因Nacos 集群健康检查依赖nacos.core.member.list接口返回所有节点状态而该接口需所有节点7848端口Raft 通信端口互通。若网络策略NetworkPolicy或 CNI 插件如 Calico未放行7848端口Raft 心跳失败集群视为“脑裂”。解决创建 NetworkPolicy 显式放行apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: nacos-raft-allow spec: podSelector: matchLabels: app: nacos policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: nacos ports: - protocol: TCP port: 7848 egress: - to: - podSelector: matchLabels: app: nacos ports: - protocol: TCP port: 7848验证kubectl exec nacos-0 -- nc -zv nacos-1.nacos-headless.default.svc.cluster.local 7848应返回succeeded。4.3 现象集群启动成功但 Spring Cloud Alibaba 服务注册后在 Nacos 控制台显示Healthy: false且服务调用 503原因Nacos 2.x 默认开启 gRPC 协议端口9848用于服务注册/发现而客户端 SDK 若版本过低如2.2.5.RELEASE会因 TLS 握手失败或协议不兼容导致注册失败。解决降级为 HTTP 协议或升级客户端。在application.properties中添加# 方式1强制客户端走 HTTP临时方案 spring.cloud.nacos.discovery.enpointhttp://nacos-headless:8848 # 方式2升级客户端至 2021.1 版本并启用 gRPC推荐 spring.cloud.nacos.discovery.enpointhttps://nacos-headless:9848 # 需配置 TLS注意nacos-headless是 ClusterIP Service非 Headless供外部客户端统一访问。4.4 现象kubectl describe pod nacos-0显示Warning FailedMount 3m12s kubelet Unable to attach or mount volumes: unmounted volumes[raft-data], unattached volumes[raft-data config-map secret-db default-token-xxx]原因PVC 绑定失败。常见于storageClassName不存在如写了ssd但集群只有standard或本地存储插件如local-path-provisioner未安装。解决查看可用 StorageClasskubectl get sc若无匹配项安装 local-pathkubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-storage.yaml修改 PVC 模板中的storageClassName为local-path。4.5 现象Nacos 控制台登录后修改配置点击“发布”提示request error, please try again later!但日志无报错原因Nacos 2.x 的配置发布依赖nacos.core.auth.caching.enabledtrue默认开启而该缓存使用嵌入式 Derby 数据库与外部 MySQL 冲突。当spring.datasource.platformmysql开启时Derby 缓存未关闭导致事务冲突。解决在application.properties中显式关闭 Derby 缓存nacos.core.auth.caching.enabledfalse nacos.core.auth.enable.userAgentAuthWhitefalse补充此问题在 Nacos 2.2.0 版本中已修复但大量线上环境仍运行 2.1.x务必检查版本。5. 生产就绪检查清单从 YAML 到可观测性的 7 个必做动作一份能进生产的 Nacos 集群 YAML绝不止于kubectl apply成功。它必须通过以下 7 个维度的验证缺一不可。我习惯在每次部署后用一个 shell 脚本自动化执行这些检查把“不确定”变成“确定”。5.1 集群节点拓扑验证确认 Raft Leader 选举与成员列表# 获取所有节点的集群状态 for i in {0..2}; do echo nacos-$i kubectl exec nacos-$i -- curl -s http://localhost:8848/nacos/v1/ns/operator/metrics | jq .members done预期输出三个节点中应有且仅有一个state: LEADER其余为FOLLOWERmembers数组长度为 3且每个成员的ip字段应为nacos-{0..2}.nacos-headless.default.svc.cluster.local。若出现state: UNKNOWN或成员数不足 3说明 Raft 初始化失败需检查7848端口连通性及NACOS_SERVERS环境变量。5.2 配置中心连通性验证模拟客户端注册与配置拉取# 创建测试 ConfigMap 模拟客户端配置 cat EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: nacos-test-client data: application.properties: | spring.cloud.nacos.config.server-addrnacos-headless:8848 spring.cloud.nacos.config.namespacepublic spring.cloud.nacos.config.groupDEFAULT_GROUP spring.cloud.nacos.config.file-extensionyaml EOF # 在集群内启动测试 Pod 拉取配置 kubectl run nacos-test --imagecurlimages/curl -it --rm --restartNever \ --command -- sh -c curl -s http://nacos-headless:8848/nacos/v1/cs/configs?dataIdtest-app.yamlgroupDEFAULT_GROUP | head -n 5预期输出返回空字符串表示配置不存在正常或 JSON 格式配置内容。若返回404或超时检查nacos-headlessService 是否存在、端口是否正确8848、以及 Nacos 是否已完全就绪/nacos/v1/ns/operator/metrics返回code:200。5.3 持久化数据校验验证 Raft 日志与 MySQL 数据一致性# 检查 Raft 日志目录是否挂载且可写 kubectl exec nacos-0 -- ls -l /home/nacos/data/protocol/raft/ # 预期存在 log 和 snapshot 目录且 log 下有 .log 文件 # 检查 MySQL 中配置表是否可写 kubectl exec -it mysql-0 -- mysql -u root -p$MYSQL_ROOT_PASSWORD -e USE nacos; SHOW TABLES LIKE config_info; # 预期返回 config_info 表名 # 插入测试数据模拟配置发布 kubectl exec -it mysql-0 -- mysql -u root -p$MYSQL_ROOT_PASSWORD -e USE nacos; INSERT INTO config_info(data_id,group_id,tenant_id,content,md5,app_name,type,crt_time,crt_user,src_ip) VALUES(test-key,DEFAULT_GROUP,,test-value,d41d8cd98f00b204e9800998ecf8427e,,text,2024-01-01 00:00:00,nacos,127.0.0.1);关键Raft 日志挂载成功是集群高可用的物理基础MySQL 表可写是配置持久化的逻辑保障。二者缺一不可。5.4 健康探针深度配置Liveness 与 Readiness 的差异化设计livenessProbe: httpGet: path: /nacos/actuator/health/readiness port: 8848 initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /nacos/actuator/health/liveness port: 8848 initialDelaySeconds: 30 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 2参数说明readinessProbe检查/actuator/health/liveness确认 Nacos 服务进程存活且端口可连轻量级livenessProbe检查/actuator/health/readiness确认集群状态健康重量级含 Raft 成员检查initialDelaySeconds差异Readiness 启动快30sLiveness 启动慢60s避免因 Raft 初始化慢导致误杀failureThreshold差异Readiness 失败 2 次即标记 UnreadyLiveness 失败 3 次才重启防止抖动。5.5 资源限制与 QoS避免 OOMKilled 与 CPU 节流resources: limits: memory: 2Gi cpu: 1000m requests: memory: 1Gi cpu: 500m依据Nacos 2.x 集群节点内存占用实测3节点100服务实例约 800MiCPU 在高并发注册时峰值达 800m。设置requestslimits保证 Guaranteed QoS 级别避免被系统 OOMKilled。若requests limits节点可能因内存压力被驱逐。5.6 日志采集标准化对接 Loki/Promtail 或 ELK# 在 container.args 中追加 args: - -Dnacos.logging.path/home/nacos/logs - -Dnacos.logging.levelINFO # 并确保 log-data PVC 挂载到 /home/nacos/logs提示Nacos 日志默认输出到/home/nacos/logs且支持logback-spring.xml自定义。Promtail 配置中指定paths: [/var/log/pods/*nacos*/*.log]即可采集。避免使用kubectl logs查日志——那是调试手段不是可观测性。5.7 权限最小化ServiceAccount 与 RBAC 精确授权apiVersion: v1 kind: ServiceAccount metadata: name: nacos-sa --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: nacos-role rules: - apiGroups: [] resources: [pods, services] verbs: [get, list] - apiGroups: [apps] resources: [statefulsets] verbs: [get] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: nacos-rolebinding subjects: - kind: ServiceAccount name: nacos-sa roleRef: kind: Role name: nacos-role apiGroup: rbac.authorization.k8s.io注意Nacos 本身不需要cluster-admin权限。上述 Role 仅授予其查询自身 Pod、Service、StatefulSet 的必要权限符合最小权限原则。若使用defaultServiceAccount可能因集群 RBAC 策略导致启动失败。从那以后我每次交付 Nacos 集群 YAML都会先跑一遍这 7 个检查脚本再让 QA 团队用压测工具如 JMeter模拟 5000 QPS 的服务注册/发现请求观察nacos-0的cpu和memory指标是否平稳、raft_commitIndex是否持续增长、config_info表写入延迟是否低于 50ms。这些数字比任何kubectl get pods的绿色状态都更真实。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询