ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes上部署高可用Nacos集群:生产级架构设计与实战

Kubernetes上部署高可用Nacos集群:生产级架构设计与实战 1. 项目概述与核心价值最近在搞微服务架构的落地服务注册与发现中心是绕不开的一环。Nacos 作为阿里开源的一站式动态服务发现、配置管理和服务管理平台凭借其易用性和强大的功能已经成了很多团队的首选。但在生产环境单点部署的 Nacos 显然不够看高可用集群是基本要求。而 Kubernetes 作为容器编排的事实标准在 K8s 上部署和管理 Nacos 集群就成了一个非常典型的、必须掌握的运维场景。这个项目说白了就是要把 Nacos 的高可用集群塞进 K8s 里让它能像其他 K8s 应用一样享受声明式部署、弹性伸缩、自愈和便捷的运维管理。听起来好像就是写个 YAML 文件的事实际操作起来从存储选型、网络配置到集群发现机制每一步都有不少门道。我把自己最近在生产环境折腾 Nacos on K8s 的完整过程包括踩过的坑和验证过的稳定方案梳理成这篇笔记。无论你是刚开始接触 K8s 的开发者还是正在为生产环境寻求可靠部署方案的运维这篇内容应该都能给你提供一条清晰的路径和可复现的实操细节。2. 架构设计与核心组件解析2.1 为什么选择 StatefulSet 而非 Deployment这是第一个关键决策点。Nacos 集群节点是有状态的每个节点有自己唯一的 ID比如nacos-0,nacos-1,nacos-2并且它们需要持久化存储自己的数据如 Derby 数据库文件或者外接的 MySQL 数据。Deployment 管理的 Pod 是无状态且可互换的显然不符合要求。StatefulSet 完美匹配了我们的需求稳定的、唯一的网络标识符Pod 名称statefulset-name-ordinal-index和对应的 Headless Service 域名是稳定的。即使 Pod 重启或重新调度它的名称和域名不变。这对于 Nacos 集群节点间相互发现和通信至关重要。按顺序的部署和扩缩容默认情况下Pod 按索引顺序0, 1, 2...创建和终止。这为集群初始化比如选举 leader提供了一定的可控性。稳定的持久化存储通过volumeClaimTemplates可以为每个 Pod 动态创建独立的 PersistentVolumeClaim (PVC)实现存储与 Pod 实例的绑定。Pod 重建后仍然能挂载到原来的数据卷。所以我们的核心工作负载对象就是 StatefulSet。一个典型的命名会是nacos-statefulset它管理的 Pod 将是nacos-0,nacos-1,nacos-2。2.2 存储方案选型嵌入式 Derby 还是外置 MySQLNacos 支持两种存储模式内嵌的 Derby 数据库以及外部的集中式数据库如 MySQL。在 K8s 环境下选择直接影响集群的数据一致性和运维复杂度。嵌入式 Derby (不推荐用于生产集群)原理每个 Nacos 节点使用自己内嵌的 Derby 数据库。节点间通过 Raft 协议同步服务注册表等内存数据但配置信息等持久化数据默认不同步。K8s 下的问题每个 Pod 的持久化卷里存着自己的 Derby 数据文件。如果某个 Pod 故障新调度的 Pod 挂载了新的空卷或者挂载了其他节点的卷数据不一致都会导致该节点数据丢失或混乱。虽然 Nacos 支持通过某种方式同步 Derby 数据但非常复杂且非主流。结论在 K8s StatefulSet 中即使每个 Pod 有独立存储使用嵌入式 Derby 也无法保证整个集群配置数据的一致性。生产环境强烈不建议使用。外置 MySQL (推荐方案)原理所有 Nacos 节点连接同一个外部的 MySQL 数据库集群主从或高可用架构。所有持久化数据命名空间、配置、用户权限等都存储在中心化的 MySQL 里天然保证一致性。K8s 下的优势数据一致性所有节点读写同一数据源无数据同步烦恼。运维简单数据库的备份、恢复、扩容由专业的 DBA 或云数据库服务负责与 Nacos 应用本身解耦。节点无状态化Nacos 节点本身可以视为“无状态”更符合云原生理念虽然我们仍用 StatefulSet 是为了稳定的网络标识。节点故障后新建的 Pod只要连接串正确就能立刻加入集群。结论对于生产级 K8s 部署必须使用外置 MySQL。这通常意味着你需要先在 K8s 外或 K8s 内通过另一个 StatefulSet 或 Operator部署一个高可用的 MySQL 集群并提前创建好 Nacos 所需的数据库和用户。注意即使使用外置 MySQLNacos 节点内存中维护的服务实例注册表依然是通过节点间的 Raft 协议进行同步的这部分数据不是存在 MySQL 里的。MySQL 主要负责存储那些需要持久化的元数据。2.3 集群节点发现机制K8s Service 的妙用Nacos 集群节点需要知道彼此的存在才能组成集群。在传统虚拟机部署时我们往往需要在一个配置文件中列出所有节点的 IP 和端口。在 K8s 中由于 Pod IP 会变我们不能写死 IP。这里我们利用 K8s 的Headless Service和StatefulSet的特性来实现自动发现。创建一个 Headless ServiceclusterIP: None其选择器selector指向我们的 Nacos StatefulSet。这个 Service 本身没有集群 IP但它会为每个匹配的 Pod 创建一条 DNS A 记录格式为pod-name.headless-svc-name.namespace.svc.cluster.local。在我们的 Nacos StatefulSet 配置中可以通过环境变量或配置文件让每个 Pod 启动时使用一个固定的模式来构建集群节点列表。例如如果我们知道集群规模是 3那么节点列表就可以构建为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这样无论 Pod 如何调度只要 Pod 名称和 Service 域名稳定集群成员就能相互解析和通信。3. 完整部署实操与配置详解接下来我们一步步拆解部署过程。假设我们的目标是在nacos命名空间部署一个 3 节点的 Nacos 集群使用外置 MySQL。3.1 前置条件与环境准备Kubernetes 集群一个正常运行的 K8s 集群1.16并配置好kubectl命令行工具。外置 MySQL 数据库假设我们已经有一个高可用的 MySQL 8.0 集群连接信息如下地址mysql-ha.example.com:3306数据库名nacos_config用户名nacos密码YourStrongPassword123!创建命名空间kubectl create namespace nacos3.2 核心资源配置文件拆解我们将创建几个关键的 YAML 文件。这里我会把核心部分贴出来并逐行解释。1. 创建 ConfigMap (nacos-cm.yaml) 用于存储 Nacos 的公共配置文件主要是cluster.conf和application.properties。注意cluster.conf的内容我们通过一个巧妙的初始化容器来动态生成而不是写死。apiVersion: v1 kind: ConfigMap metadata: name: nacos-config namespace: nacos data: # 这里只放 application.properties 的基础部分数据库连接信息通过环境变量或Secret注入更安全 application.properties: | # 启用数据源 spring.datasource.platformmysql # 数据库数量我们只有一个主库但Nacos配置要求至少写一个 db.num1 # 下面这些具体的连接信息我们将通过环境变量来替换避免硬编码在ConfigMap中 db.url.0jdbc:mysql://${MYSQL_SERVICE_HOST}:${MYSQL_SERVICE_PORT}/${MYSQL_DATABASE}?characterEncodingutf8connectTimeout1000socketTimeout3000autoReconnecttrueuseSSLfalse db.user.0${MYSQL_USER} db.password.0${MYSQL_PASSWORD} # 其他重要配置 server.servlet.contextPath/nacos # 开启认证生产环境建议开启 nacos.core.auth.enabledfalse # 先关闭方便测试生产环境务必改为true并配置密钥 nacos.core.auth.server.identity.keyserverIdentity nacos.core.auth.server.identity.valuesecurity # 节点角色默认为 both (同时负责读写和集群间同步) nacos.core.member.meta.rolesROLE_BOTH2. 创建 Headless Service (nacos-headless-svc.yaml) 为 StatefulSet 的 Pod 提供稳定的网络标识。apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: nacos labels: app: nacos spec: clusterIP: None # Headless Service 的关键 ports: - port: 8848 name: server - port: 9848 name: raft-rpc # Nacos 2.0 新增的端口用于节点间RPC通信 - port: 9849 name: grpc # Nacos 2.0 客户端gRPC通信端口 selector: app: nacos # 这个选择器必须和后面的StatefulSet匹配3. 创建对外访问的 Service (nacos-svc.yaml) 为了让集群外或其他命名空间的服务能访问 Nacos 控制台和 API我们需要一个常规的 Service。这里使用 NodePort 类型方便演示生产环境通常用 Ingress。apiVersion: v1 kind: Service metadata: name: nacos namespace: nacos labels: app: nacos spec: type: NodePort # 生产环境建议使用 ClusterIP Ingress ports: - name: server port: 8848 targetPort: 8848 nodePort: 30000 # 指定一个NodePort范围或让K8s自动分配 - name: raft-rpc port: 9848 targetPort: 9848 - name: grpc port: 9849 targetPort: 9849 selector: app: nacos4. 创建 StatefulSet (nacos-statefulset.yaml) 这是最核心的部分内容较长我们分段解析。apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: nacos spec: serviceName: nacos-headless # 必须指向前面创建的Headless Service replicas: 3 # 集群节点数 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: # 初始化容器用于动态生成 cluster.conf initContainers: - name: init-cluster-conf image: busybox:1.35 command: [sh, -c] args: - | set -ex # 获取当前Pod的序号如 nacos-0 则 INDEX0 INDEX$(hostname | awk -F- {print $NF}) # 根据StatefulSet名称和Headless Service名称生成集群节点列表 # 假设我们知道集群规模是3 (REPLICAS3) REPLICAS3 DOMAINnacos-headless.nacos.svc.cluster.local CLUSTER_CONF for i in $(seq 0 $(($REPLICAS-1))); do CLUSTER_CONF${CLUSTER_CONF}$(printf \nacos-%d.%s:8848\ $i $DOMAIN)\n done # 将生成的列表写入到共享卷的指定位置 echo -e $CLUSTER_CONF /home/nacos/conf/cluster.conf cat /home/nacos/conf/cluster.conf volumeMounts: - name: config-volume mountPath: /home/nacos/conf # 挂载到Nacos的配置目录 # 主容器运行Nacos Server containers: - name: nacos image: nacos/nacos-server:v2.2.3 # 建议使用特定版本而非latest imagePullPolicy: IfNotPresent ports: - containerPort: 8848 name: server - containerPort: 9848 name: raft-rpc - containerPort: 9849 name: grpc # 环境变量用于传递数据库连接信息和JVM参数 env: - name: MODE value: cluster # 指定集群模式 - name: PREFER_HOST_MODE value: hostname # 使用hostname(即Pod名称)作为节点标识 - name: SPRING_DATASOURCE_PLATFORM value: mysql - name: MYSQL_SERVICE_HOST value: mysql-ha.example.com # 你的MySQL地址 - name: MYSQL_SERVICE_PORT value: 3306 - name: MYSQL_DATABASE value: nacos_config - name: MYSQL_USER valueFrom: secretKeyRef: name: nacos-mysql-secret # 建议使用Secret存储密码 key: username - name: MYSQL_PASSWORD valueFrom: secretKeyRef: name: nacos-mysql-secret key: password - name: JVM_XMS value: 512m # 初始堆内存 - name: JVM_XMX value: 512m # 最大堆内存 - name: JVM_XMN value: 256m # 年轻代大小 # 资源请求与限制根据实际负载调整 resources: requests: memory: 1Gi cpu: 500m limits: memory: 2Gi cpu: 1000m # 健康检查 livenessProbe: httpGet: path: /nacos/actuator/health port: 8848 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 readinessProbe: httpGet: path: /nacos/actuator/health port: 8848 initialDelaySeconds: 30 periodSeconds: 5 volumeMounts: - name: config-volume mountPath: /home/nacos/conf - name: logs-volume mountPath: /home/nacos/logs - name:>apiVersion: v1 kind: Secret metadata: name: nacos-mysql-secret namespace: nacos type: Opaque data: username: bmFjb3M # echo -n nacos | base64 password: WW91clN0cm9uZ1Bhc3N3b3JkMTIzIQ # echo -n YourStrongPassword123! | base643.3 执行部署与验证按顺序应用配置文件kubectl apply -f nacos-cm.yaml kubectl apply -f nacos-secret.yaml kubectl apply -f nacos-headless-svc.yaml kubectl apply -f nacos-svc.yaml kubectl apply -f nacos-statefulset.yaml观察部署状态# 查看StatefulSet和Pod创建情况 kubectl -n nacos get statefulsets kubectl -n nacos get pods -l appnacos -w # 等待所有Pod状态变为 Running 且 Ready (2/2如果有sidecar的话) # 查看初始化容器的日志确认cluster.conf生成正确 kubectl -n nacos logs nacos-0 -c init-cluster-conf # 查看Nacos主容器日志 kubectl -n nacos logs nacos-0 -f验证集群状态通过 NodePort 访问 Nacos 控制台http://任意NodeIP:30000/nacos。默认账号密码是nacos/nacos。在控制台集群管理 - 节点列表中应该能看到三个节点它们的 IP 地址应该是各自的 Pod IP状态应为UP。可以尝试停掉一个 Pod (kubectl -n nacos delete pod nacos-0)观察 StatefulSet 是否会自动重建一个新的nacos-0并且新 Pod 是否能自动重新加入集群在节点列表中恢复 UP 状态。4. 生产环境进阶配置与调优基础部署跑通只是第一步要用于生产还需要考虑更多。4.1 配置持久化与高可用 MySQL前面我们用了外置 MySQL但“外置”具体怎么部署对于严格要求建议云托管服务直接使用云厂商提供的 RDS如 AWS RDS, Aliyun RDS, Tencent Cloud CDB它们自带高可用、备份、监控省心省力。只需确保 Nacos 所在的 K8s 集群网络能访问到 RDS 的内网地址。自建 K8s 集群内 MySQL如果必须在 K8s 内可以使用成熟的 Operator如mysql-operator或presslabs/mysql-operator来部署一个包含主从复制、自动故障转移的 MySQL 集群。切记Nacos 的数据库和业务数据库最好物理隔离。4.2 开启认证与使用 TLS生产环境必须开启 Nacos 认证并建议对控制台和 API 访问启用 TLS。开启认证修改 ConfigMap 中的nacos.core.auth.enabledtrue并设置nacos.core.auth.plugin.nacos.token.secret.key为一个足够复杂且保密的密钥同样建议放在 Secret 中。所有客户端连接时都需要配置用户名和密码。配置 TLS/HTTPS这通常在 Ingress 层面解决。为 Nacos 的 Service 创建 Ingress 资源并配置 TLS 证书。这样外部访问通过 HTTPS 加密内部 Pod 之间通信仍可用 HTTP。如果要求 Pod 间通信也加密则需要在 Nacos 应用内配置 SSL并管理证书复杂度较高需权衡必要性。4.3 资源限制与监控告警资源限制前面 StatefulSet 中已经设置了resources.requests/limits。需要根据实际监控数据调整。Nacos 的内存占用与注册的服务实例数和配置数量强相关。建议初期设置合理的 Limits 防止单个 Pod 吃光节点资源同时根据监控观察值调整 Requests 以提高调度效率。监控Nacos 自身指标Nacos 2.0 提供了基于 Micrometer 的指标端点 (/nacos/actuator/prometheus)可以很方便地被 Prometheus 抓取。监控关键指标如服务实例数、配置数量、HTTP/GRPC 请求 QPS、延迟、错误率、JVM 内存/GC 情况、集群节点状态和任期term等。K8s 层面监控监控 Pod 的 CPU、内存使用率、重启次数、网络流量。告警基于上述监控指标设置告警规则例如集群节点 DOWN 的数量超过 1、JVM 内存使用率持续超过 80%、请求错误率突增等。4.4 数据备份与灾难恢复即使数据库高可用定期备份仍是必须的。MySQL 数据库备份使用你熟悉的 MySQL 备份工具如mysqldump、xtrabackup或云 RDS 的自动备份功能定期对nacos_config数据库进行全量和增量备份。Nacos 配置文件导出对于非常重要的配置可以考虑定期通过 Nacos Open API 将配置数据导出为文件存档到对象存储如 S3、OSS中。恢复演练定期测试备份数据的恢复流程确保在极端情况下如误删命名空间、数据库逻辑错误能快速恢复业务。5. 常见问题排查与运维技巧在实际运维中你肯定会遇到各种问题。这里记录几个典型场景和排查思路。5.1 集群节点无法形成集群节点列表一直显示 DOWN这是最常见的问题。排查思路检查cluster.conf文件进入 Pod 查看/home/nacos/conf/cluster.conf内容是否正确。命令kubectl -n nacos exec nacos-0 -- cat /home/nacos/conf/cluster.conf。确认里面的域名是否能解析到正确的 Pod IP。可以在 Pod 内用nslookup或ping测试。检查网络连通性确认 Pod 之间网络是否互通特别是端口8848(HTTP API)、9848(Raft RPC) 是否开放。可以使用telnet命令在 Pod 内测试kubectl -n nacos exec nacos-0 -- telnet nacos-1.nacos-headless.nacos.svc.cluster.local 9848。检查日志查看 Nacos 节点的日志重点关注alipay-jraft.log和nacos-cluster.log。常见的错误有连接被拒绝、认证失败、节点 ID 冲突等。检查数据库连接确认所有节点都能正常连接外置 MySQL。查看日志中是否有数据库连接异常。确认数据库用户有足够的权限。检查防火墙或网络策略如果 K8s 集群使用了网络插件如 Calico, Cilium并配置了 NetworkPolicy需要确保nacos命名空间内的 Pod 允许相互访问所需的端口。5.2 Pod 重启后数据“丢失”或服务列表为空可能原因 1使用了嵌入式 Derby。这是最可能的原因。Pod 重启后挂载了新的空卷数据自然没了。解决方案立刻切换到外置 MySQL。可能原因 2数据库连接失败。Pod 启动时无法连接 MySQL导致从空数据库初始化。检查数据库服务状态和连接配置。可能原因 3客户端未正确配置集群地址。客户端只连接了某一个 Pod该 Pod 重启期间客户端无法上报心跳导致服务实例被摘除。解决方案客户端应配置所有 Nacos 节点的地址通过前面提到的 Headless Service 域名列表或配置一个负载均衡器如 Nginx地址。5.3 客户端报错 “Connection refused” 或 “no server available”排查思路检查客户端配置的地址确认客户端配置的 Nacos 服务器地址是否正确是否能从客户端网络环境解析和访问。如果是 K8s 内部的服务应用nacos.nacos.svc.cluster.local:8848。如果是外部则是 NodePort 或 Ingress 地址。检查 Nacos Service 和 Pod 状态kubectl -n nacos get svc,ep查看 Service 的 Endpoints 是否正常包含了所有健康的 Pod IP。检查 Pod 的 readinessProbe如果 readinessProbe 失败Pod 不会加入到 Service 的 Endpoints 列表。检查 Pod 的readiness状态和日志。检查端口映射确认 Service 的targetPort与容器暴露的containerPort一致。5.4 内存占用过高或频繁 Full GC可能原因注册的服务实例或配置项数量极大例如数十万。优化建议调整 JVM 参数在 StatefulSet 的环境变量中调整JVM_XMS,JVM_XMX,JVM_XMN。适当增加堆内存并优化新生代与老年代比例。可以加入 GC 日志参数方便分析。启用 Nacos 的数据分片和路由功能对于超大规模场景可以考虑部署多个 Nacos 集群并通过域名或负载均衡进行分片但这会引入额外的运维复杂度。清理无用数据建立定期清理机制通过 Nacos API 清理长期离线的服务实例和不再使用的配置。升级硬件为运行 Nacos 的 K8s Node 节点分配更多内存。5.5 运维技巧优雅升降级与配置热更新滚动更新直接修改 StatefulSet 的镜像版本K8s 会默认以滚动更新的方式逐个替换 Pod。由于我们使用外置数据库每个新 Pod 启动后都能连接到中心数据库因此滚动更新对服务影响很小。建议先更新一个 Pod观察稳定后再更新其余。配置热更新修改 ConfigMap 后需要让 Pod 内的 Nacos 重新加载配置。Nacos 本身不支持直接监听 ConfigMap 变化。通常有两种做法重启 Pod这是最直接的方式。可以通过kubectl rollout restart statefulset nacos -n nacos命令来滚动重启所有 Pod。对于高可用集群短暂重启一个 Pod 通常不影响整体服务。使用 Sidecar 同步使用一个像Reloader这样的工具或者在 Pod 内增加一个 Sidecar 容器来监听 ConfigMap 变化然后通过发送信号或调用 API 的方式通知 Nacos 重载配置。这种方法更复杂但无需重启。对于生产环境如果配置不常变采用滚动重启的方式更简单可靠。部署和维护一个高可用的 Nacos 集群是微服务稳定性的一块重要基石。在 K8s 上做这件事虽然初期配置看起来有些繁琐但一旦跑通其带来的自动化运维、弹性伸缩和故障自愈能力是传统部署方式难以比拟的。最关键的就是吃透 StatefulSet、Headless Service 和外置数据库这几个核心概念剩下的就是根据实际业务负载在监控数据的指导下进行细致的调优和加固。
返回列表