这一篇只做一件事:在一个已经具备 CNI、DNS 和动态存储的 Kubernetes 集群中,搭建三节点 Nacos。
系列导航:Kubernetes 重建迁移教程系列。完成本篇后,再做 Service DNS 治理和平滑升级。
文中的镜像版本、域名、密码和地址均使用占位符。正式部署前,要根据目标 Nacos 镜像核对环境变量和数据库 schema,不能直接使用 latest。
先理解最终结构
Nacos 只有三个 Pod,但需要两个 Service:
flowchart LR
A["应用客户端"] --> B["nacos-client ClusterIP"]
B --> C["nacos-0"]
B --> D["nacos-1"]
B --> E["nacos-2"]
C <--> F["nacos-headless"]
D <--> F
E <--> F
C --> G["外部 MySQL"]
D --> G
E --> G
nacos-headless的clusterIP: None,为 StatefulSet Pod 提供稳定 DNS,供三个 Nacos 节点互相发现。nacos-client是普通 ClusterIP,应用只连接它,不关心某个 Pod IP。- 配置与服务注册数据写入外部 MySQL,Pod 重建不会丢失。
Headless Service 不是多余的 Nacos 实例,业务客户端也无需直接使用它。
前置条件
先确认集群基础能力:
kubectl get nodes
kubectl get storageclass
kubectl -n kube-system get deployment coredns
kubectl run dns-smoke --image=<approved-busybox-image> --restart=Never \
--command -- nslookup kubernetes.default.svc.cluster.local
kubectl logs dns-smoke
kubectl delete pod dns-smoke
要求:
- 至少三个可调度工作节点。
- 默认 StorageClass 能动态创建 PVC,或已经准备好静态 PV。
- 外部 MySQL 已具备高可用和独立备份。
- Nacos 镜像已经同步到内部镜像仓库并固定 digest。
- 节点到 MySQL 的网络和 DNS 已验证。
第 1 步:准备命名空间与数据库
创建独立命名空间:
kubectl create namespace nacos-system
从目标 Nacos 发行包中获取对应数据库的 mysql-schema.sql,先在测试库执行,再由数据库管理员应用到正式库。不要从旧博客复制版本不明的 SQL。
执行后至少确认核心表存在:
SHOW TABLES;
SELECT COUNT(*) FROM config_info;
SELECT COUNT(*) FROM tenant_info;
数据库账号只授予 Nacos schema 所需的权限,不要在应用 Pod 中使用 MySQL root。
第 2 步:生成鉴权材料与 Secret
Nacos 鉴权 token 使用 32 字节以上的随机值再做 Base64。以下命令只把结果保存在当前 shell 变量中:
NACOS_RAW_TOKEN="$(openssl rand -hex 32)"
NACOS_AUTH_TOKEN="$(printf '%s' "$NACOS_RAW_TOKEN" | base64)"
kubectl -n nacos-system create secret generic nacos-secret \
--from-literal=mysql-host='<mysql-host>' \
--from-literal=mysql-port='<mysql-port>' \
--from-literal=mysql-database='<mysql-database>' \
--from-literal=mysql-username='<mysql-username>' \
--from-literal=mysql-password='<mysql-password>' \
--from-literal=auth-token="$NACOS_AUTH_TOKEN" \
--from-literal=identity-key='serverIdentity' \
--from-literal=identity-value='<random-identity-value>'
unset NACOS_RAW_TOKEN NACOS_AUTH_TOKEN
Secret 不应写进 Git。提交到仓库的应是 ExternalSecret、SealedSecret 模板,或一份不含值的创建说明。
第 3 步:创建两个 Service
保存为 00-services.yaml:
apiVersion: v1
kind: Service
metadata:
name: nacos-headless
namespace: nacos-system
spec:
clusterIP: None
publishNotReadyAddresses: true
selector:
app.kubernetes.io/name: nacos
ports:
- name: http
port: 8848
- name: grpc-client
port: 9848
- name: grpc-server
port: 9849
- name: raft
port: 7848
---
apiVersion: v1
kind: Service
metadata:
name: nacos-client
namespace: nacos-system
spec:
selector:
app.kubernetes.io/name: nacos
ports:
- name: http
port: 8848
targetPort: http
- name: grpc-client
port: 9848
targetPort: grpc-client
- name: console
port: 8080
targetPort: console
应用的地址应写成:
nacos-client.nacos-system.svc.cluster.local:8848
创建并检查:
kubectl apply -f 00-services.yaml
kubectl -n nacos-system get service
nacos-headless 的 ClusterIP 应显示为 None,nacos-client 应获得普通 ClusterIP。
第 4 步:创建三节点 StatefulSet
保存为 01-statefulset.yaml。下面展示核心结构,环境变量名称必须对照你固定的目标镜像再次核对:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nacos
namespace: nacos-system
spec:
serviceName: nacos-headless
replicas: 3
podManagementPolicy: Parallel
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app.kubernetes.io/name: nacos
template:
metadata:
labels:
app.kubernetes.io/name: nacos
spec:
terminationGracePeriodSeconds: 60
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: nacos
topologyKey: kubernetes.io/hostname
containers:
- name: nacos
image: <image-registry>/nacos-server:<approved-version>@sha256:<digest>
imagePullPolicy: IfNotPresent
ports:
- { name: http, containerPort: 8848 }
- { name: grpc-client, containerPort: 9848 }
- { name: grpc-server, containerPort: 9849 }
- { name: raft, containerPort: 7848 }
- { name: console, containerPort: 8080 }
env:
- { name: MODE, value: cluster }
- { name: PREFER_HOST_MODE, value: hostname }
- {
name: NACOS_SERVERS,
value: "nacos-0.nacos-headless.nacos-system.svc.cluster.local:8848 nacos-1.nacos-headless.nacos-system.svc.cluster.local:8848 nacos-2.nacos-headless.nacos-system.svc.cluster.local:8848",
}
- { name: SPRING_DATASOURCE_PLATFORM, value: mysql }
- name: MYSQL_SERVICE_HOST
valueFrom:
{ secretKeyRef: { name: nacos-secret, key: mysql-host } }
- name: MYSQL_SERVICE_PORT
valueFrom:
{ secretKeyRef: { name: nacos-secret, key: mysql-port } }
- name: MYSQL_SERVICE_DB_NAME
valueFrom:
{ secretKeyRef: { name: nacos-secret, key: mysql-database } }
- name: MYSQL_SERVICE_USER
valueFrom:
{ secretKeyRef: { name: nacos-secret, key: mysql-username } }
- name: MYSQL_SERVICE_PASSWORD
valueFrom:
{ secretKeyRef: { name: nacos-secret, key: mysql-password } }
- { name: NACOS_AUTH_ENABLE, value: "true" }
- name: NACOS_AUTH_TOKEN
valueFrom:
{ secretKeyRef: { name: nacos-secret, key: auth-token } }
- name: NACOS_AUTH_IDENTITY_KEY
valueFrom:
{ secretKeyRef: { name: nacos-secret, key: identity-key } }
- name: NACOS_AUTH_IDENTITY_VALUE
valueFrom:
{ secretKeyRef: { name: nacos-secret, key: identity-value } }
startupProbe:
httpGet: { path: /nacos/v1/console/health/started, port: http }
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
readinessProbe:
httpGet: { path: /nacos/v1/console/health/readiness, port: http }
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
livenessProbe:
httpGet: { path: /nacos/v1/console/health/liveness, port: http }
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 6
resources:
requests: { cpu: "1", memory: 2Gi }
limits: { cpu: "2", memory: 4Gi }
探针路径可能随目标版本变化。先用目标镜像在测试环境 curl 验证,再写进生产清单。
创建 StatefulSet:
kubectl apply --server-side --dry-run=server -f 01-statefulset.yaml
kubectl apply -f 01-statefulset.yaml
kubectl -n nacos-system rollout status statefulset/nacos --timeout=15m
kubectl -n nacos-system get pods -o wide
三个 Pod 应分布在三台不同的节点。若有 Pod Pending,先看事件:
kubectl -n nacos-system describe pod nacos-2
如果集群只有两台合适的节点,required 反亲和会阻止第三个 Pod 调度。这是容量规划问题,不应通过删除反亲和来掩盖。
第 5 步:验证节点发现、数据库和 Service
逐个查看日志:
for pod in nacos-0 nacos-1 nacos-2; do
echo "===== ${pod} ====="
kubectl -n nacos-system logs "$pod" --tail=100
done
检查 DNS:
kubectl -n nacos-system exec nacos-0 -- \
getent hosts nacos-1.nacos-headless.nacos-system.svc.cluster.local
检查 Service Endpoint:
kubectl -n nacos-system get endpointslice \
-l kubernetes.io/service-name=nacos-client -o wide
EndpointSlice 中应该有三个 Ready 地址。最后从临时 Pod 访问客户端 Service:
kubectl -n nacos-system run nacos-smoke --rm -it --restart=Never \
--image=<approved-curl-image> -- \
curl -fsS http://nacos-client:8848/nacos/v1/console/health/readiness
第 6 步:接入第一个应用
先选一个非核心应用,不要一次性修改所有命名空间:
spring.cloud.nacos.server-addr=nacos-client.nacos-system.svc.cluster.local:8848
spring.cloud.nacos.username=${NACOS_USERNAME}
spring.cloud.nacos.password=${NACOS_PASSWORD}
发布后同时检查:
kubectl -n <app-namespace> rollout status deployment/<app> --timeout=10m
kubectl -n <app-namespace> logs deployment/<app> --since=10m \
| grep -Ei 'nacos|register|error|exception'
再从 Nacos 控制台或 API 检查实例,而不是只看应用 Pod 是否为 Running。
完成定义
只有同时满足以下条件,Nacos 集群才算搭建完成:
- 三个 Nacos Pod 位于三个不同节点。
- 三个 Pod 都能解析另外两个 Pod 的 Headless DNS。
nacos-client有三个 Ready Endpoint。- 配置发布、读取和历史版本正常。
- 测试应用能注册、发现并调用另一个服务。
- 删除任意一个 Nacos Pod 后,应用仍能读取配置和发现服务。
- MySQL 与 Nacos 配置都具备独立备份和恢复步骤。
官方快速开始特别说明,其默认清单仅适合测试;生产环境应启用鉴权、使用可靠数据库和持久化方案,并部署在隔离内网。参考:Nacos Kubernetes Quick Start 与 Nacos 集群模式部署。
DISCUSSION
讨论与反馈