实训06 Kubernetes双节点集群搭建与应用部署
环境说明
| 设备 | IP | 系统 | 角色 |
|---|---|---|---|
| VM1 | 10.4.0.1 | Debian 12 | 网关/DNS/DHCP(不加入集群) |
| VM2 | 10.4.0.10 | Debian 12 | K8s Worker 节点(node2) |
| VM3 | 10.4.0.20 | Debian 12 | Harbor 私有仓库(10.4.0.20,HTTP:80) |
| VM4 | 10.4.0.100 | CentOS 7 | K8s Control Plane(master) |
集群版本信息 - Kubernetes:v1.28.15(kubeadm/kubelet/kubectl 1.28.0) - 容器运行时:containerd 1.6.33(VM4)/ 1.6.20(VM2) - CNI:Calico v3.27.4(Pod 网段 10.244.0.0/16) - Ingress:ingress-nginx v1.10.0(NodePort 32756)
关键凭据 - Harbor:admin / Harbor12345,http://harbor.mxdx.local - MySQL:crm_user / Crm@123456,库名 crm - Redis:密码 Redis@123456
第一层级:Kubernetes双节点集群搭建
任务1:所有节点基础环境准备(VM2、VM4)
① 关闭 Swap
kubelet 要求节点禁用 Swap。临时关闭 + 永久注释 fstab:
swapoff -a
sed -i 's/^\(.*swap.*\)$/#\1/' /etc/fstab
free -h


排障记录(下午发现):VM4 重启后 kubelet 报
running with swap on is not supported,原因 CentOS 7 重启后 Swap 被重新启用。修复:重新swapoff -a+ 注释 fstab +systemctl mask swap.target彻底屏蔽,kubelet 恢复。
② 安装 Containerd
VM2(Debian 12,apt):
apt-get install -y containerd
VM4(CentOS 7,Docker 自带 containerd.io):
yum install -y containerd.io
统一配置(生成默认配置 + 启用 SystemdCgroup):
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl enable --now containerd



排障记录:修改 config.toml 后 containerd 未生效——CentOS 7 的 systemd unit 默认
ExecStart=/usr/bin/containerd不带 –config 参数。修复:/etc/systemd/system/containerd.service.d/override.conf写ExecStart=/usr/bin/containerd --config=/etc/containerd/config.toml+ daemon-reload + restart。
③ 配置内核参数
cat > /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
modprobe overlay && modprobe br_netfilter
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system


④ 配置 Harbor 私有仓库域名解析
echo "10.4.0.20 harbor.mxdx.local" >> /etc/hosts
ping -c 3 harbor.mxdx.local


任务2:安装 Kubernetes 组件(VM2、VM4)
VM4(CentOS 7,YUM 源)
cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF
yum install -y kubelet-1.28.0 kubeadm-1.28.0 kubectl-1.28.0
systemctl enable kubelet
VM2(Debian 12,APT 源)
curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | gpg --dearmor --batch --yes --no-tty -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main' > /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-get install -y kubelet=1.28.0-00 kubeadm=1.28.0-00 kubectl=1.28.0-00
apt-mark hold kubelet kubeadm kubectl # 锁定版本
systemctl enable kubelet


排障记录:VM2(Debian 12)默认仓库自带 kubeadm 1.30.14,直接
apt install会装错版本。且curl | gpg无 tty 导致 GPG 导入失败、阿里云源未生效。修复:gpg --dearmor --batch --yes --no-tty+apt-mark unhold+--allow-downgrades降到 1.28.0-00 + 重新 hold。
任务3:初始化控制平面(VM4)
初始化集群
kubeadm init \
--apiserver-advertise-address=10.4.0.100 \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=v1.28.15 \
--pod-network-cidr=10.244.0.0/16
输出:Your Kubernetes control-plane has initialized successfully! 控制面组件 5s 内健康。
配置 kubectl 凭证
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl get nodes


排障记录(kubeadm init 两次失败): 1. 第一次
CRI v1 runtime API is not implemented——containerd 未加载 config(见任务1②),修复 override.conf 后通过。 2. 第二次timed out waiting for the condition——kubelet 拉取registry.k8s.io/pause:3.6被墙(kubeadm--image-repository不改变 sandbox_image)。修复:containerd config.toml 中sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9",并kubeadm config images pull预拉阿里云镜像。
重要:NoSchedule 污点(
node-role.kubernetes.io/control-plane:NoSchedule)保留,控制面不调度业务 Pod。
任务4:加入 Worker 节点(VM2)
生成 join 命令(VM4)
kubeadm init 输出自带 token 和 CA hash,也可用 kubeadm token create --print-join-command 重新生成。
执行 join(VM2)
kubeadm join 10.4.0.100:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
输出:This node has joined the cluster。

排障记录:VM2 join 前因历史残留(/etc/kubernetes/kubelet.conf 已存在、端口 10250 被占)报 preflight 错误。修复:
kubeadm reset -f+ 清理/etc/kubernetes/*/var/lib/kubelet/*后重试成功。
任务5:部署 Calico 网络插件(VM4)
# 下载官方 manifest,并确保 CALICO_IPV4POOL_CIDR 与 init 一致
curl -fsSL -o calico.yaml https://raw.githubusercontent.com/projectcalico/calico/v3.27.4/manifests/calico.yaml
sed -i 's|# - name: CALICO_IPV4POOL_CIDR|- name: CALICO_IPV4POOL_CIDR|; s|# value: "192.168.0.0/16"| value: "10.244.0.0/16"|' calico.yaml
kubectl apply -f calico.yaml

排障记录(Calico 镜像拉取):calico/node:v3.27.4(200MB+)走 containerd docker.io 镜像加速源超时(1panel.live 卡在基础层)。解决:VM4 上用 Docker
docker pull docker.m.daocloud.io/calico/node:v3.27.4(daocloud 完整路径直连)→docker tag成 docker.io 名 →docker save | ctr -n k8s.io images import -导入 containerd → 删除旧 Pod 触发重建即 Running。VM2 上的 calico-node 走 containerd mirror 反而自己拉通。containerd 配置坑:containerd 1.6 的
mirrors与config_path不能共存(报mirrors cannot be set when config_path is provided,CRI 插件加载失败)。改用config_path = "/etc/containerd/certs.d"+certs.d/docker.io/hosts.toml管理镜像加速。
任务6:部署 Ingress-Nginx 控制器(VM4)
# baremetal 方式
curl -fsSL -o ingress-nginx.yaml https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/baremetal/deploy.yaml
kubectl apply -f ingress-nginx.yaml
kubectl get svc -n ingress-nginx


Service 端口记录(后续访问需要): - HTTP:80:32756/TCP → NodePort 32756 - HTTPS:443:31176/TCP
排障记录(ingress-nginx 镜像源): 1. 先 sed 换成阿里云 cn-hangzhou 镜像 → 镜像不存在(pull access denied)。 2. containerd 配
registry.k8s.iomirror=ustc → DNS 解析失败;mirror=aliyun → 也异常。 3. 正解:k8s.m.daocloud.io(daocloud 的 registry.k8s.io 代理),crictl pull秒成功。 4. 连续两次追加同 key 的 mirror 段导致 TOMLduplicated tables,containerd 起不来 →containerd config default重新生成 + 一次性追加。
第二层级:应用配置管理
任务1:创建命名空间(VM4)
kubectl create namespace crm
kubectl config set-context --current --namespace=crm

crm 命名空间用于隔离 CRM 系统所有资源,后续所有操作均在此命名空间下。
任务2:创建 ConfigMap(非敏感配置)
apiVersion: v1
kind: ConfigMap
metadata:
name: crm-config
namespace: crm
data:
MYSQL_HOST: "10.4.0.20"
MYSQL_PORT: "3306"
MYSQL_DATABASE: "crm"
REDIS_HOST: "10.4.0.20"
REDIS_PORT: "6379"
API_URL: "http://crm-api-svc:5000"
kubectl apply -f crm-config.yaml
kubectl get configmap crm-config -o yaml

说明:任务书示例写
MYSQL_DATABASE: crm_db,但 VM3 实际库名是crm,第三层级部署时发现连接报Access denied for user 'crm_user'@'10.4.0.%' to database 'crm_db',已 patch 为crm修复。
任务3:创建 Secret(敏感信息)
Base64 编码:
echo -n "crm_user" | base64 # Y3JtX3VzZXI=
echo -n "Crm@123456" | base64 # Q3JtQDEyMzQ1Ng==
echo -n "Redis@123456" | base64 # UmVkaXNAMTIzNDU2
apiVersion: v1
kind: Secret
metadata:
name: crm-secret
namespace: crm
type: Opaque
data:
MYSQL_USER: Y3JtX3VzZXI=
MYSQL_PASSWORD: Q3JtQDEyMzQ1Ng==
REDIS_PASSWORD: UmVkaXNAMTIzNDU2

任务4:理解 Base64 编码机制
echo -n "Crm@123456" | base64 # 编码 → Q3JtQDEyMzQ1Ng==
echo -n "Q3JtQDEyMzQ1Ng==" | base64 -d # 解码 → Crm@123456
核心认知:Base64 是编码(Encoding)而非加密(Encryption),可轻易逆解码,传输中等同于明文。生产环境真正安全需配合 RBAC 权限控制、K8s 静态加密(EncryptionConfiguration)或外部 KMS。
任务5:创建 Harbor 镜像拉取凭证
kubectl create secret docker-registry harbor-cred \
--docker-server=harbor.mxdx.local \
--docker-username=admin \
--docker-password=Harbor12345 \
--docker-email=admin@mxdx.local \
-n crm

类型为 kubernetes.io/dockerconfigjson,供 Deployment 的 imagePullSecrets 引用(任务书写 harbor-cred,另建了 harbor-registry 同内容)。
第三层级:应用部署到 Kubernetes
任务1:部署后端 API(VM4)
Deployment YAML(crm-api)
- 副本数:2
- 镜像:
harbor.mxdx.local/crm/crm-api:v1 - imagePullPolicy:
IfNotPresent - imagePullSecrets:引用
harbor-cred - 资源限制:requests 100m/128Mi,limits 500m/512Mi
- livenessProbe:
/health(initialDelay 15s,period 10s) - readinessProbe:
/health(initialDelay 10s,period 5s) - 环境变量:全部从 ConfigMap(crm-config)和 Secret(crm-secret)注入
kubectl apply -f crm-api-deployment.yaml
Service YAML(crm-api-svc)
apiVersion: v1
kind: Service
metadata:
name: crm-api-svc
namespace: crm
spec:
type: ClusterIP
selector:
app: crm-api
ports:
- port: 5000
targetPort: 5000
任务2:部署前端(VM4)
crm-frontend Deployment(副本 2,镜像 harbor.mxdx.local/crm/crm-frontend:v1,探针检查 80 端口)+ Service crm-frontend-svc(ClusterIP: 80)。
kubectl apply -f crm-frontend-deployment.yaml



验证(4 个 Pod 全部 Running + 应用健康)
kubectl get pods -n crm -o wide # crm-api×2 + crm-frontend×2 全部 1/1 Running
curl -s http://10.106.168.205:5000/health # {"mysql":true,"redis":true}
curl -s http://10.106.168.205:5000/api/customers # 首查 source:mysql,再查 source:redis


排障记录: - containerd 配 Harbor(HTTP 仓库):
insecure_skip_verify只跳过证书校验,Harbor 是纯 HTTP 仍失败。必须用config_path + hosts.toml:certs.d/harbor.mxdx.local/hosts.toml中server = "http://harbor.mxdx.local"。 - 探针路径:任务书写 readinessProbe/api/ready,但 app.py 实际只有/health,统一改用/health,否则 Pod 永不 Ready。 - MYSQL_DATABASE 修正:crm_db → crm(见第二层级任务2说明),kubectl patch configmap crm-config --merge '{"data":{"MYSQL_DATABASE":"crm"}}'+ rollout restart 后/health恢复{"mysql":true,"redis":true}。
任务3:配置 Ingress 外部访问
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: crm-ingress
namespace: crm
spec:
ingressClassName: nginx
rules:
- host: crm.mxdx.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: crm-api-svc
port: {number: 5000}
- path: /
pathType: Prefix
backend:
service:
name: crm-frontend-svc
port: {number: 80}
验证:
kubectl get ingress -n crm # ADDRESS: 10.4.0.10
curl -H 'Host: crm.mxdx.local' http://10.4.0.10:32756/ # 200 前端
curl -H 'Host: crm.mxdx.local' http://10.4.0.10:32756/api/customers # 200 后端数据
Ingress 资源状态(kubectl get ingress -n crm):
NAME CLASS HOSTS ADDRESS PORTS AGE
crm-ingress nginx crm.mxdx.local 10.4.0.10 80 5h53m
外部访问链路验证(NodePort 32756,curl 加 Host 头模拟域名):
GET / → HTTP 200(前端页面)
GET /api/customers → HTTP 200(后端 JSON 数据)
排障记录(集群重启自愈验证):整体关机重启后 crm-api Pod 曾反复 CrashLoopBackOff。根因:VM3 的 MySQL 启动最慢(3306 晚于其他服务监听),期间
/health连不上 MySQL、响应耗时 1.02s 超过探针 timeout=1s,被判探针失败而重启。MySQL 就绪后探针恢复、4 个 Pod 自动回到 1/1 Running——这是 K8s 探针 + Deployment 自愈机制的典型体现。
任务4:排查部署问题(按需执行)
实际遇到的故障与排查命令: | 故障 | 排查命令 | 实际根因 | | — | — | — | | Pod 未 Running | kubectl describe pod | 探针路径 /api/ready 不存在 → 改 /health | | 镜像拉取失败 | kubectl describe pod Events | Harbor HTTP 仓库未配 hosts.toml | | MySQL 连接失败 | kubectl logs | MYSQL_DATABASE 写成 crm_db,实际库名 crm | | liveness 探针超时被杀 | kubectl describe pod | v2 代码 redis 连接缺密码参数,响应超 1s |
第四层级:滚动更新与回滚
第一步:准备新版本镜像
修改后端 API 代码,版本号 v1 → v2:
sed -i 's/{"service": "crm-api", "status": "running"}/{"service": "crm-api", "status": "running", "version": "v2"}/' /data/crm-source/backend/app.py
docker build -t harbor.mxdx.local/crm/crm-api:v2 .
docker push harbor.mxdx.local/crm/crm-api:v2



排障记录(v2 探针超时):v2 更新后 Pod 持续
Liveness probe failed: context deadline exceeded被重启。根因:v2 源码 health() 的 redis 连接缺少password=REDIS_PASSWORD or None(对比 v1 有),连 Redis@123456 超时 3s,探针 timeout=1s 判死。修复代码后重建镜像解决。另:同 tag 镜像更新后 node2 会缓存旧镜像,需crictl rmi清缓存。
第二步:执行滚动更新
kubectl set image deployment/crm-api crm-api=harbor.mxdx.local/crm/crm-api:v2 -n crm
kubectl rollout status deployment/crm-api -n crm
输出:deployment "crm-api" successfully rolled out
第三步:观察滚动更新过程
循环访问 API(终端A):
while true; do curl -s http://10.106.168.205:5000/; echo; sleep 1; done
# 输出从 {"service":"crm-api","status":"running"} 逐渐变为 {"service":"crm-api","status":"running","version":"v2"}
watch Pod(终端B):
watch -n 1 kubectl get pods -n crm -l app=crm-api
# 新 Pod Running、旧 Pod Terminating 交替


理解:K8s 滚动策略为「先启动新 Pod → 等待就绪 → 再终止旧 Pod」,maxSurge/maxUnavailable 默认 25%,保证服务不中断。
第四步:查看更新历史
kubectl rollout history deployment/crm-api -n crm
kubectl rollout history deployment/crm-api -n crm --revision=1

第五步:模拟故障并回滚
构造 v3(/health 返回 500):
sed -i 's/"version": "v2"/"version": "v3"/' app.py
sed -i 's/^def health():/def health():\n return "Internal Server Error", 500/' app.py
python -m py_compile app.py # 语法校验
docker build -t harbor.mxdx.local/crm/crm-api:v3 .
docker push harbor.mxdx.local/crm/crm-api:v3




更新到 v3 并观察失败:
kubectl set image deployment/crm-api crm-api=harbor.mxdx.local/crm/crm-api:v3 -n crm
kubectl get pods -n crm -l app=crm-api # 新 Pod 0/1,RESTARTS 增加
kubectl describe pod -n crm -l app=crm-api | grep -A1 "Liveness probe failed"

/health 返回 500 → liveness 探针失败 → 容器被杀重启,新 Pod 无法就绪。
回滚到 v2:
kubectl rollout undo deployment/crm-api -n crm
kubectl rollout status deployment/crm-api -n crm
curl -s http://10.106.168.205:5000/ # {"version":"v2"} 服务恢复

第六步:指定版本回滚
kubectl rollout undo deployment/crm-api -n crm --to-revision=1
kubectl rollout status deployment/crm-api -n crm
kubectl rollout history deployment/crm-api -n crm
curl -s http://10.106.168.205:5000/ # {"service":"crm-api","status":"running"}(无 version 字段 = v1)

验证能回滚到任意历史版本:–to-revision=1 回到最初 v1(API 无 version 字段),回滚记录已追加到 history。
第五层级:服务发现与负载均衡
第一步:验证负载均衡
多次访问 Service ClusterIP,观察请求分发到不同 Pod:
for i in $(seq 1 10); do curl -s -o /dev/null -w '%{http_code} ' http://10.106.168.205:5000/health; done; echo
# 10 次全部 200
kubectl logs -n crm crm-api-7f5b9d99fc-9zpds | grep -c 'GET /health' # Pod1 请求数
kubectl logs -n crm crm-api-7f5b9d99fc-pqt85 | grep -c 'GET /health' # Pod2 请求数


理解:Service 默认基于 iptables 轮询(round-robin)将 ClusterIP 流量分发到后端 Pod。实测 10 次访问两个 Pod 分别收到 7 次和 3 次(轮询非严格均分),历史累计 135:131 基本均衡。
第二步:验证服务发现
从前端 Pod 内部通过 Service 名称访问后端:
FPOD=$(kubectl get pods -n crm -l app=crm-frontend -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n crm $FPOD -- nslookup crm-api-svc
kubectl exec -n crm $FPOD -- curl -s -w '\nHTTP %{http_code}\n' http://crm-api-svc:5000/health

理解: - DNS Server =
10.96.0.10(CoreDNS/kube-dns) - 完整 DNS 记录格式:服务名.命名空间.svc.cluster.local,即crm-api-svc.crm.svc.cluster.local- 短名crm-api-svc自动补全为完整 FQDN,解析到 ClusterIP 10.106.168.205 - Pod 内用 Service 名访问后端 → HTTP 200,{"mysql":true,"redis":true}
第三步:验证故障转移
删除一个后端 Pod,观察流量转移 + Deployment 自动恢复:
终端A(持续访问):
while true; do code=$(curl -s -o /dev/null -w '%{http_code}' http://10.106.168.205:5000/health); echo "$(date +%H:%M:%S) $code"; sleep 1; done
终端B(删除 Pod + watch):
POD=$(kubectl get pods -n crm -l app=crm-api -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod -n crm $POD
kubectl get pods -n crm -l app=crm-api -w



验证结论:删除期间 8 次连续访问全 200、36 次持续访问无中断——Service 自动将流量转到剩余 Pod;Deployment 立即创建新 Pod 恢复副本数。
第四步:观察 Pod 自愈
kubectl get deploy crm-api -n crm # 删除前 2/2
POD=$(kubectl get pods -n crm -l app=crm-api -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod -n crm $POD
kubectl get pods -n crm -l app=crm-api # 连续看:Terminating → 新 Pod ContainerCreating → Running
kubectl get deploy crm-api -n crm # 删除后 2/2 恢复




理解:replicas 的作用是确保始终有指定数量的 Pod 运行。删除任一 Pod,Deployment 控制器立即创建新 Pod 补位,从创建到 Running 仅数秒。
总结
集群最终状态
- 双节点集群:VM4 control-plane + VM2 worker(node2),均 Ready
- Kubernetes v1.28.15 + Calico v3.27.4(10.244.0.0/16)+ Ingress-Nginx v1.10.0
- CRM 应用:crm-api×2 + crm-frontend×2 全 Running
- 外部访问:
http://crm.mxdx.local:32756(NodePort,Ingress 路由到前端/API)
核心排障记录汇总(三段式)
| 阶段 | 问题 | 根因 | 解决 |
|---|---|---|---|
| 集群搭建 | kubeadm init CRI 错误 | containerd systemd 未加载 config | override.conf 加 –config |
| 集群搭建 | init 超时 | sandbox_image registry.k8s.io 被墙 | 改阿里云 pause:3.9 |
| 集群搭建 | Calico/Ingress 镜像拉取失败 | 国内镜像源不可用 | daocloud/k8s.m.daocloud.io + ctr import |
| 应用部署 | crm-api 连不上 MySQL | 库名 crm_db vs 实际 crm | patch ConfigMap |
| 滚动更新 | v2 探针超时被杀 | redis 连接缺密码参数 | 修代码重建镜像 |
| 滚动更新 | 同 tag 镜像不更新 | node2 缓存旧镜像 | crictl rmi 清缓存 |
| Harbor | 502 / login 401 | redis 数据损坏 + nginx 缓存旧连接 | 清 redis + 重启 nginx |
| 集群重启 | crm-api CrashLoopBackOff | MySQL 启动慢,/health 超 1s 探针判失败 | 等 MySQL 就绪后自动自愈 |
关键资源清单
kubectl get ns crm
kubectl get all -n crm
kubectl get configmap,secret -n crm
kubectl get ingress -n crm
kubectl rollout history deployment/crm-api -n crm
验收记录
验收标准
| 验收项 | 操作 | 预期结果 | 分值 |
|---|---|---|---|
| 集群状态 | kubectl get nodes | 双节点 Ready | 5分 |
| 调度隔离验证 | kubectl get pods -n crm -o wide | 4个Pod全部运行在VM2;VM4因污点无业务Pod | 10分 |
| ConfigMap | kubectl get configmap -n crm | 配置项正确 | 5分 |
| Secret | kubectl get secret -n crm | 密码为Base64编码值 | 5分 |
| Service | kubectl get svc -n crm | 两个Service(ClusterIP) | 5分 |
| 前端访问 | 一体机浏览器访问 | 显示CRM界面 | 5分 |
| API访问 | curl访问/api/customers | 返回JSON数据 | 5分 |
验收结果与截图证据
① 集群状态(5分)✅
kubectl get nodes

结果:localhost.localdomain(VM4 control-plane)与 node2(VM2)均 Ready ✅
② 调度隔离验证(10分)✅
kubectl get pods -n crm -o wide
kubectl describe node localhost.localdomain | grep -A2 Taints


结果:crm-api×2 + crm-frontend×2 全部运行在 node2(VM2);VM4 因 node-role.kubernetes.io/control-plane:NoSchedule 污点无业务 Pod ✅
③ ConfigMap(5分)✅
kubectl get configmap crm-config -n crm -o yaml

结果:MYSQL_HOST / MYSQL_PORT / MYSQL_DATABASE / REDIS_HOST / REDIS_PORT / API_URL 六项配置正确 ✅
④ Secret(5分)✅
kubectl get secret crm-secret -n crm -o yaml

结果:MYSQL_USER / MYSQL_PASSWORD / REDIS_PASSWORD 均为 Base64 编码值(Y3JtX3VzZXI= / Q3JtQDEyMzQ1Ng== / UmVkaXNAMTIzNDU2)✅
⑤ Service(5分)✅
kubectl get svc -n crm

结果:crm-api-svc(ClusterIP 10.106.168.205:5000)与 crm-frontend-svc(ClusterIP 10.96.246.248:80)两个 Service ✅
⑥ 前端访问(5分)✅
一体机浏览器访问 http://crm.mxdx.local:32756(或 http://10.4.0.10:32756),预期显示 CRM 前端界面。

结果:浏览器显示 “CRM 客户列表” 表格,包含张三/李四/王五 3 条客户数据(姓名/邮箱/电话),数据来源 mysql ✅
前置条件:一体机 hosts 加
10.4.0.10 crm.mxdx.local(或用 IP 直连),路径为 Ingress → crm-frontend-svc → crm-api-svc → VM3 MySQL。
⑦ API访问(5分)✅
curl -s http://10.106.168.205:5000/api/customers

结果:返回 customers 表 JSON 数据(张三/李四/王五),source:mysql,再查 source:redis ✅
Comments NOTHING