综合练习:部署 backend
这一课你将能做到
- 提交一套完整可 apply 的清单:Namespace + ConfigMap + Secret + Deployment(探针/资源齐全)+ Service(可选 Ingress)
- 用 port-forward(或 Ingress)打通本站风格的 API 冒烟,并解释每一步「通/不通」的判定依据
- 完成一次改镜像滚动更新与
rollout undo,写下它与 docker compose up 发布的差异
为什么要学
前面 11 课是零件,本课把它们装配成整车:把 docker-essentials 里构建的 backend 镜像(Spring Boot + MySQL/Redis 配置外置)接进 K8s 主路径对象。这一课做完,你就具备了「把一个无状态服务放上 K8s」的完整能力——这也是大多数公司 K8s 岗位的实际日常工作内容。
目标不是一次上生产,而是形成「声明期望 → 观察状态 → 排障定位 → 回滚止损」的肌肉记忆。做完本课,把清单文件存进仓库,它就是你的第一份可复用的部署模板,也是通往 Helm/GitOps 的原材料。
先建立图像
ConfigMap(backend-config) ──envFrom──┐
Secret(db-secret) ──env──┐ │
▼ ▼
Deployment(backend) × 2 副本
│ probes: /actuator/health
│ resources: requests/limits
▼
Service backend(ClusterIP 80→8080)
│
┌──────────┴───────────┐
port-forward(本机调试) Ingress(可选,/api 分流)
│
你的 curl
MySQL / Redis:集群外(Compose/托管),DB_HOST 指向可达地址
| 资源 | 本课最低要求 |
|---|
| Deployment | 2 副本、标签 app=backend、readiness+liveness、requests/limits |
| Service | ClusterIP,selector 匹配,80→8080(占位镜像为 80) |
| ConfigMap | SPRING_PROFILES_ACTIVE、DB_HOST、REDIS_HOST |
| Secret | DB_PASSWORD(演示值即可) |
| Ingress | 可选;host/path 复刻现网 Nginx |
跟着做
步骤 1:命名空间与镜像准备
kubectl create namespace coderoadmap --dry-run=client -o yaml | kubectl apply -f -
kubectl config set-context --current --namespace=coderoadmap
镜像二选一:
- 真镜像:把 docker-essentials 构建的 backend 导入本地集群(不推仓库的本地镜像必须 load):
minikube image load your-backend:1.0.0
- 占位镜像:没有现成镜像就用
nginx:1.27-alpine 走通全部对象,之后再换真 tag。
步骤 2:先 apply 配置与密钥(顺序很重要)
:
backend-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: backend-config
data:
SPRING_PROFILES_ACTIVE: "prod"
DB_HOST: "192.168.49.1" # minikube 里宿主机的固定网关地址(Compose 的 MySQL 在宿主机)
REDIS_HOST: "192.168.49.1"
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
DB_PASSWORD: "change-me-demo"
kubectl apply -f backend-config.yaml -f backend-secret.yaml
kubectl get cm,secret
真实口令不要进 Git;本课用演示值,键名结构照搬即可。apply 顺序:配置/密钥 → 工作负载,否则 Pod 卡 CreateContainerConfigError。
步骤 3:apply 完整的 Deployment 与 Service
backend-deploy.yaml(占位镜像版,端口 80;真镜像把 image 换掉、探针与端口换成 8080 + /actuator/health):
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 2
revisionHistoryLimit: 5
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: api
image: nginx:1.27-alpine
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: backend-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend
ports:
- name: http
port: 80
targetPort: 80
kubectl apply -f backend-deploy.yaml
kubectl get pods -l app=backend -w
kubectl get endpoints backend
验收:Pod 全部 Running 且 READY 1/1,Endpoints 有 2 个 IP。
步骤 4:API 冒烟
kubectl port-forward svc/backend 18080:80
# 另开终端
curl -sS http://127.0.0.1:18080/ | head -3
真 backend 镜像时改用:curl http://127.0.0.1:18080/actuator/health,应返回 {"status":"UP"}。失败按三板斧定位:describe 看 Events → logs 看应用 → get endpoints 看挂载。
步骤 5:发布与回滚演练
kubectl set image deployment/backend api=nginx:1.25-alpine
kubectl rollout status deployment/backend
kubectl rollout history deployment/backend
kubectl rollout undo deployment/backend
kubectl rollout status deployment/backend
kubectl get pods -l app=backend
应完整观察到:新旧 Pod 交替 → revision 记录 → 回到上一版本。对照本站发版:tag 由 CI 产出、集群只消费;回滚不需要重新构建,只回到上一个 revision。
步骤 6:可选 Ingress 与复盘
若第 9 课的 ingress 插件仍启用,把 Ingress 的 namespace 对齐后 apply(参考第 9 课清单),用 curl -H "Host: app.local" http://$(minikube ip)/ 再冒烟一次。
- 与
docker compose up 比,多了哪些对象?(Deployment/Service/CM/Secret/探针/资源声明)
- 排错多了哪两步?(describe 事件层、endpoints 健康层)
- MySQL 放哪最省心?(集群外/托管,DB_HOST 指外部地址)
kubectl delete namespace coderoadmap
讲清楚要点
- 标签串联一切:Deployment 的 selector、Service 的 selector、HPA 目标、排障命令全靠
app=backend——命名规范在第一天就定死。
- apply 顺序即依赖顺序:Namespace → ConfigMap/Secret → Deployment → Service → Ingress;用「先配置后负载」避免 CreateContainerConfigError。
- 验收看 Ready 与 Endpoints,不看 Running:Running 只代表进程活着,Ready + Endpoints 才代表「能接流量」——这是发布验收的硬标准。
- 镜像 tag 可追溯是回滚的前提:CI 产出的固定 tag + revisionHistoryLimit 保历史,undo 才有明确落点;
:latest 是回滚噩梦。
- 探针与依赖耦合是「保护」不是「故障」:MySQL 未就绪时 readiness 失败、摘流、稍后自愈——K8s 不保证启动顺序,但保证最终可用。
- 本站全貌 = 本课清单 × 2 + 存储 + 入口:platform 再来一套同样的 Deployment+Service,MySQL/Redis 外置,Ingress 聚合
/ 与 /api——你已经能推演出整站的部署图。
你可能会懵的地方
-
ImagePullBackOff(用了自己的镜像时)?
本地构建的镜像不在 minikube 里:minikube image load 镜像:tag,或推送到可访问仓库并配 imagePullSecrets(私有仓库)。
-
连不上 MySQL?
集群内 Pod 访问宿主机 Compose 的 MySQL,minikube(docker 驱动) 用网关地址 192.168.49.1(即 Docker 网桥的宿主 IP);云集群用内网地址或 ExternalName。MySQL 还要放行该网段并确认账号 host。
-
要不要一上来就 Helm?
先手写清楚每个对象(本课价值所在),Helm 只是把这套清单模板化、参数化——下一个 track 的事。不理解对象直接上 Helm,排错会退化成「改 chart 碰运气」。
-
platform 和 backend 谁先上?
先 API 后前端:backend 冒烟通过后再部署 platform 并配 Ingress 分流,问题域一次只有一个。
-
内存里的 Compose 还要保留吗?
保留。本地开发 Compose 最顺手,K8s 清单用于「准生产验证」——两套配置共享同一套键名约定(第 7 课),迁移成本已降到最低。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 清单散落在终端历史里 | 环境不可复现 | 全部文件化、进仓库 |
| Service 端口与容器端口不一致 | Endpoints 有 IP 但连不通 | 核对 port/targetPort/containerPort |
用 :latest 且缓存旧镜像 | 「更新了却没变」 | 钉 tag,必要时 --image-pull-policy=Always |
| 验收只看 Running | 流量打到未 Ready 实例 | 看 READY 与 get endpoints |
| 只发布不演练回滚 | 真故障时手忙脚乱 | 每次发布练习必做一次 undo |
卡点急救
- Pod 卡
CreateContainerConfigError:引用的 ConfigMap/Secret 名或 key 缺失——describe 点名缺谁,按「先配置后负载」补 apply。
- Endpoints 为空 / curl 拒绝连接:selector 与标签对账 → Pod READY 是否 1/1 → Service 端口与容器端口是否一致;逐层收窄。
- undo 之后服务仍异常:回滚到的 revision 本身就有问题,或配置对象(CM/Secret)已被改而未随版本回退——回滚范围要包含「镜像 + 配置」整体,这正是「清单进 Git」的意义。
面试常问
-
描述你把一个 Spring Boot 服务部署上 K8s 的完整流程。
① CI 构建镜像推仓库(固定 tag);② 编写清单:ConfigMap(配置外置)+ Secret(口令)+ Deployment(2 副本、readiness/liveness 指向 /actuator/health、requests/limits、envFrom/env 注入)+ Service(ClusterIP);③ apply 顺序:配置 → 负载 → Service;④ 验收:Pod Ready、Endpoints 就绪、port-forward/Ingress 冒烟;⑤ 发布:set image 或改清单触发滚动更新,rollout status 观察;⑥ 异常则 rollout undo 回滚。数据库采用托管或集群外,通过 Service 名/ExternalName 连接。
-
K8s 里服务连不上,你的排查套路是什么?
自底向上四层:① Pod 层——get pods 看状态,异常类型对号(ImagePullBackOff 查镜像、CrashLoopBackOff 查 logs --previous、Pending 查资源/调度);② 事件层——describe 看 Events 的关键报错;③ 网络层——get endpoints 是否有健康后端(标签、readiness);④ 入口层——Service 端口映射、Ingress 规则与 Controller。配合 kubectl run curl 在集群内复现,把「网络问题」和「应用问题」分开。
-
为什么生产更倾向「无状态服务上 K8s、数据库托管」?
K8s 的核心优势(副本弹性、滚动发布、自愈调度)天然匹配无状态服务;数据库的核心诉求(稳定 IO、备份恢复、主从切换)在云托管上更成熟省心。混合架构用稳定网络标识打通,兼顾弹性与数据安全,也是多数团队的性价比最优解——强行把一切塞进集群只会同时承担两种复杂度。
自测三问
- 写出本课最低资源集合及其 apply 依赖顺序。
- Endpoints 为空时,你的三层排查动作分别是什么?
- 若只把 backend 上 K8s、MySQL 留在宿主机 Compose,
DB_HOST 应填什么?凭什么?
小结一句话
用 Namespace+ConfigMap+Secret+Deployment+Service 在本地集群跑通 backend 的部署、冒烟、滚动与回滚,即完成本 track 的主路径闭环——下一步把这套清单模板化进 Helm 与 GitOps 流水线。
延伸
- Deploying an application(官方教程)
- Helm(下一步方向:把本课清单模板化、参数化,管理多环境发布)
- 下一课预告:本 track 到此收官。建议的下一步:① 把本课清单提交进你的仓库并跑通一次真实 CI 发布;② 进入 Helm/GitOps 进阶,把「手写 apply」升级为「声明式流水线」;③ 备考 CKA 巩固集群层知识。