综合练习与 K8s 方向
这一课你将能做到
- 独立完成一次全链路演练:Spring Boot 项目 → 多阶段 Dockerfile → compose 三件套 → 一键部署 → 数据持久化验证 → 镜像发布
- 用一份自检清单验收你的容器化工程,确保每一课的关键点(缓存、卷、网络、健康检查、安全)都没有漏项
- 说清 Docker 之后为什么要学 Kubernetes,能画出两者的概念映射表并规划下一步路径
为什么要学
前 11 课的知识是散装的:Dockerfile 在第 5、6 课,卷在第 8 课,网络在第 9 课,编排在第 10 课。真实项目里它们必须同时正确,任何一环掉链子都是「本地好好的,部署就出事」。本课把它们串成一条可重复执行的部署流水线——这既是你的实战验收,也是可以直接抄进个人项目/简历的工程模板。
最后,本课回答 Docker 学习的终极问题:「然后呢?」答案是 Kubernetes——但先要把单机玩明白,K8s 的每个概念在 Docker 里都有对应物,你会带着地图进入下一站。
先建立图像
完整流水线全景图:
开发机 生产服务器(Linux)
┌────────────────────────────┐ ┌──────────────────────────┐
│ mvn package(或镜像内构建) │ │ docker pull ←── Registry │
│ docker build(多阶段+非root)│──push────→ │ docker compose up -d │
│ docker compose 配置 │ │ ├ app :8080 →公网 │
│ docker tag + push │ │ ├ mysql 数据卷持久化 │
└────────────────────────────┘ │ └ redis 仅内网互通 │
└──────────────────────────┘
最终项目结构(这就是你的模板):
my-project/
├── src/ # Spring Boot 源码
├── pom.xml
├── Dockerfile # 多阶段 + 非 root + TZ + exec 格式
├── .dockerignore
├── docker-compose.yml # app + mysql + redis
├── .env # 密码(不入 git)
├── .env.example # 密码模板(入 git)
└── .gitignore # 含 .env
跟着做
步骤 1:完善 Dockerfile(多阶段 + 非 root + 健康检查)
# ---- 构建阶段 ----
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
# ---- 运行阶段 ----
FROM eclipse-temurin:21-jre
ENV TZ=Asia/Shanghai
RUN useradd -r -u 10001 appuser
USER appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appuser /build/target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
健康检查不用额外装 curl(JRE 镜像没有),直接复用 Java:
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD ["java", "-jar", "-Dspring.main.web-application-type=none", "-Dloader.main=org.springframework.boot.actuate.endpoint.HealthEndpointMain", "app.jar"]
过于复杂时,务实做法是健康检查放到 compose 层做(下一步),镜像内不放——两种选一即可。
步骤 2:最终版 docker-compose.yml
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: appdb
volumes:
- mysql-data:/var/lib/mysql
networks: [app-net]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 5s
timeout: 3s
retries: 12
start_period: 30s
restart: unless-stopped
redis:
image: redis:7-alpine
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
volumes:
- redis-data:/data
networks: [app-net]
restart: unless-stopped
app:
build: .
image: myteam/demo-app:1.0.0
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/appdb
SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD}
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks: [app-net]
restart: unless-stopped
deploy:
resources:
limits:
memory: 1g
volumes:
mysql-data:
redis-data:
networks:
app-net:
(deploy.resources.limits 在 compose v2 中对 docker compose up 生效,实现内存限额。)
步骤 3:本地一键验收
docker compose up -d --build
docker compose ps # mysql (healthy)、app (running)
curl http://localhost:8080/actuator/health
步骤 4:执行「灾难演练」验证数据与自愈
# 演练 1:数据跨容器存活
docker compose exec mysql mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "CREATE DATABASE demo; USE demo; CREATE TABLE t(id INT); INSERT INTO t VALUES (7);"
docker compose up -d --force-recreate --no-deps mysql # 重建 mysql 容器
docker compose exec mysql mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "SELECT * FROM demo.t;"
# 输出 7 —— 卷生效
# 演练 2:进程自愈
docker compose exec app kill 1 # 杀掉主进程,观察容器
docker compose ps # restart 策略把它拉了回来
# 演练 3:全量重建后数据仍在
docker compose down
docker compose up -d
docker compose exec mysql mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "SELECT * FROM demo.t;"
# 输出 7 —— down 不删卷
步骤 5:发布镜像到仓库
docker login
docker tag myteam/demo-app:1.0.0 myteam/demo-app:1.0.0-a1b2c3d # 版本+commit 双 tag
docker push myteam/demo-app:1.0.0
docker push myteam/demo-app:1.0.0-a1b2c3d
步骤 6:服务器部署(Linux + Engine)
# 服务器上首次部署
git clone <你的仓库> && cd my-project # 或只 scp compose 文件与 .env
cp .env.example .env && vim .env # 填生产密码
docker compose pull # 拉取 registry 上的 app 镜像
docker compose up -d
curl http://localhost:8080/actuator/health
步骤 7:日常更新与回滚 SOP
# 更新(零停机目标下的标准三步)
docker compose pull app
docker compose up -d app
docker image prune -f
# 回滚:改 .env 中镜像 tag 或 compose 里 image 的版本,再 up -d
步骤 8:验收自检清单(逐项打勾)
- Dockerfile:多阶段构建 ✓ 非 root ✓ exec 格式入口 ✓ TZ ✓
.dockerignore 排除 .git/logs ✓ 构建上下文 < 50MB
- compose:服务名互连 ✓ healthcheck + service_healthy ✓ restart 策略 ✓
- 数据:mysql/redis 均 named volume ✓ down 不丢数据(演练 3 通过)✓
- 安全:密码只在 .env ✓ .env 不入 git ✓ mysql/redis 无公网端口 ✓
- 发布:双 tag(版本+commit)✓ 服务器 pull 部署 ✓ 回滚流程演练过 ✓
讲清楚要点
-
这套模板覆盖了单机生产的完整闭环:构建(多阶段)、编排(compose)、持久化(卷)、时序(healthcheck)、自愈(restart)、发布(registry)、回滚(tag 切换)。把它当作你的脚手架,而不是从零发明。
-
「构建一次,处处运行」是容器化的灵魂:CI 或本机构建出的镜像 push 一次,测试/预发/生产拉的都是同一个 digest——排查问题时环境差异这个变量被彻底消除。
-
K8s 要解决的问题是单机方案的三个天花板:
- 单机 = 单点故障,机器挂了服务全停 → K8s 多节点调度与自愈;
- 流量涨了只能纵向加配 → K8s 横向扩缩容(HPA);
- 手动部署/回滚易错 → K8s 声明式滚动发布与版本回退。
-
概念映射表(Docker → K8s),进入 K8s 前先背熟:
| Docker 世界 | K8s 世界 | 说明 |
|---|
| 容器 | Pod | Pod 是 1~N 个紧耦合容器的调度单位 |
| compose service | Deployment | 定义镜像、副本数、更新策略 |
| restart: unless-stopped | ReplicaSet 自愈 | 挂了自动重建并重新调度 |
| -p 8080:8080 | Service | 集群内稳定访问入口 + 负载均衡 |
| 容器名 DNS | Service DNS/Ingress | 集群内/外部访问统一抽象 |
| .env / 环境变量 | ConfigMap / Secret | 配置与密钥的集群级管理 |
| named volume | PV / PVC | 持久卷与动态供给 |
| healthcheck | liveness/readiness probe | 存活与就绪两个维度 |
-
K8s 学习路径建议:先用 minikube 或 kind 在本机起集群 → 掌握 Pod/Deployment/Service 三件套 → 加上 Ingress、ConfigMap/Secret → 再学 PV/PVC 与 HPA → 最后看 Helm 与 GitOps。你现在的 compose 心智模型能直接迁移。
你可能会懵的地方
-
什么时候才真的需要 K8s?
信号:多台服务器、需要高可用与零停机发布、流量波动需要弹性、微服务数量多到 compose 文件难以维护。如果你的系统就是单机几个服务,compose + 稳定的 SOP 是更务实的选择——不要为了简历堆砌复杂度。
-
K8s 还用 Docker 吗?听说「K8s 抛弃了 Docker」?
K8s 1.24 起弃用的是 dockershim(K8s 直连 Docker 的适配层),改用 containerd 等标准 CRI 运行时。但你构建的 OCI 镜像完全通用,Docker 构建的镜像在 K8s 里照跑不误。你学的 Docker 知识没有作废,反而是 K8s 的地基。
-
Spring Boot 上 K8s 前还要补什么?
健康检查端点(actuator 已就绪)、优雅停机、日志输出到 stdout(让 K8s 收集)、配置外部化(ConfigMap 替代 application-prod.yml)、无状态化(会话不进内存)。这五点在容器阶段就该养成。
-
我的项目只有一个 Spring Boot + MySQL,这套模板是不是过度设计?
不是。卷、健康检查、restart 策略、双 tag,每一样都在防一类具体事故;省略它们省下的十分钟,会在某次凌晨故障里加倍还回去。复杂度可以裁剪,习惯不能省。
-
下一步最值得投入的方向排序?
① K8s 基础(部署能力跃迁);② CI/CD(GitHub Actions / Jenkins,把本课 SOP 自动化);③ 可观测性(Prometheus + Grafana 监控容器);④ 服务网格与云原生进阶。按此顺序投入产出比最高。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 各课知识孤立使用(有卷没健康检查、有 compose 没 restart) | 事故从漏项里长出来 | 按本课自检清单逐项验收 |
| 服务器上 docker build 部署 | 构建慢、依赖网络、环境不一致 | CI/本地构建 → push → 服务器 pull |
| 只有 latest 一个 tag | 无法回滚、无法追溯 | 版本 + commit 双 tag |
| 直接上 K8s 跳过 compose | 缺单机编排直觉,概念悬空 | 先 compose 熟练,再迁移 K8s |
| 演练只验证「能启动」 | 数据丢失/自愈失效上线才发现 | 本课三个灾难演练必须全过 |
| 面试讲容器化只说「会 docker 命令」 | 停留在工具层 | 讲完整流水线 + 每个环节防的事故 |
卡点急救
- 服务器 compose up 报找不到镜像:compose 里
image 与你 push 的仓库全名不一致;或忘了先 docker login 私有仓库。核对全名后 docker compose pull。
- 演练 1 里 SQL 报密码错误:
-p$MYSQL_ROOT_PASSWORD 是 shell 变量,需先在当前 shell set -a; source .env; set +a(或直接手敲密码)。
- kill 1 后容器没被拉起:
restart: unless-stopped 在你手动 compose stop 后不会自动恢复;确认策略写对且不是手动停止状态。
- 健康检查一直 starting:首次 MySQL 初始化超过
start_period;或 actuator 未引入依赖。加 spring-boot-starter-actuator 并放宽时间。
- K8s 入门无从下手:装 minikube(
minikube start),把本课 compose 的 app 翻译成 Deployment + Service YAML,跑通后再加 Ingress——一次只迁移一个概念。
面试常问
-
完整描述你如何把一个 Spring Boot 项目容器化并上生产?(串讲题,必背)
按「构建 → 编排 → 持久化 → 发布 → 部署 → 运维」六段回答:多阶段 Dockerfile(依赖缓存+非 root+exec 入口);compose 编排 app/mysql/redis(服务名互连+healthcheck 时序);数据落 named volume 并定期备份;双 tag 推私库;服务器 pull + up -d;restart 自愈 + 演练过数据重建与回滚。能加上「每个环节防什么事故」就是高分答案。
-
Docker 到 Kubernetes 的演进逻辑?
单机容器解决了环境一致性与交付标准化,但多机场景下无法自动调度、故障自愈、弹性伸缩。K8s 以声明式 API 管理集群资源:Pod/Deployment/Service 对应容器/编排/访问入口,本质是把 compose 的思想扩展到多机。
-
生产环境你们怎么做容器化应用的更新与回滚?
更新:CI 构建新版本镜像(版本+commit tag)→ push → 服务器 compose pull && compose up -d app(或 K8s 滚动更新)。回滚:把镜像 tag 切回上一版本重新部署,秒级完成;配合健康检查确保旧版本流量切走前新版本就绪。
-
容器化给运维带来了哪些改变?
部署物从「脚本+文档」变成不可变镜像;扩容从「装环境」变成「拉镜像起容器」;排障从「登机器翻文件」变成「logs/exec/stats 标准化操作」;配置从「机器上散落」变成「环境变量/compose/配置中心集中管理」。
自测三问
- 不看文档,手写完整的多阶段 Dockerfile(含非 root、TZ、exec 格式、MaxRAMPercentage)。
- 说出灾难演练三连(数据跨容器、进程自愈、全量重建)各自的命令与预期结果。
- 把 compose 里的
app 服务翻译成 K8s 概念,需要哪几个资源对象?各自对应 compose 的哪段配置?
小结一句话
把 11 课串成「构建 → 编排 → 持久化 → 发布 → 部署 → 自愈」的完整流水线,你就掌握了单机容器化的全部工程能力——而这张概念地图,正是通往 Kubernetes 的入场券。
延伸