知序
返回
保存草稿
发布
H1
H2
H3
1
2
3
Excalidraw 图
AI 写作
AI 优化
目录
输入内容后显示目录
Markdown
# 05 - 实战:源码 → 镜像 → K8s 上线 > 前四篇是零件:镜像怎么造、容器怎么跑、镜像怎么进仓库、提交怎么触发流水线。这篇把它们装成一条**闭环**。 > 走完一遍:写代码 → 构建镜像 → 推仓库 → 部署到 K8s → 改一行 → 滚动更新 → 回滚,前面所有概念会各就各位。 > 示例是一个最小可跑的 Spring Boot 应用,用到的 YAML 与前四篇保持一致,可以直接照抄。 --- ## 1. 目标形态  *图:端到端交付链路:源码经 CI 产出镜像推入仓库,再由 K8s 滚动更新对外提供服务* ```text ┌──────────┐ │ 开发者 │ git push └────┬─────┘ │ 触发 v ┌──────────┐ build & push ┌──────────────────────┐ │ CI 流水线 │───────────────>│ 私有镜像仓库 │ └──────────┘ │ registry.example.com │ └──────────┬───────────┘ │ 拉取镜像 v ┌──────────────────────┐ │ Deployment(2 副本) │ │ image: ...:sha-9f3a1c2│ └──────────┬───────────┘ │ 滚动更新 v ┌──────────────────────┐ │ Service(ClusterIP) │ │ 80 → 8080 │ └──────────┬───────────┘ v port-forward / Ingress → 外网 ``` 链路上每个组件的职责: | 环节 | 组件 | 职责 | |---|---|---| | 代码托管 | Git 仓库 | 唯一事实来源;每次提交产生一个 commit 哈希 | | 构建 | CI 流水线 / Dockerfile | 把源码变成不可变镜像,tag 带 commit 短哈希 | | 存储 | 私有镜像仓库 | 保存镜像供 K8s 拉取,凭据隔离在此 | | 调度运行 | K8s Deployment | 声明副本数与镜像版本,负责滚动更新与自愈 | | 稳定入口 | K8s Service | 给一组随时生灭的 Pod 一个稳定地址 | | 配置 | ConfigMap / Secret | 把环境相关配置从镜像里剥出来 | | 对外 | Ingress / port-forward | 让集群外的请求能打进来 | 示例应用叫 `order-service`,只有两个接口:一个业务接口、一个给探针用的健康检查。之所以不用真实业务,是因为**这条链路和业务复杂度无关**,越干净的例子越能看清每一环。 > 一句话:**这条链路上每一个箭头,都是前四篇里的一块。** 这一篇只做一件事:把它们接起来。 --- ## 2. 准备应用(顺带把优雅退出的配置补齐) 项目结构用一个标准 Maven 单模块工程就够:  *图:order-service 示例项目结构:就这几样文件,够跑通整条交付链路* ```text order-service/ ├── pom.xml # 依赖与打包插件 ├── Dockerfile # 本篇第 3 节写 ├── src/main/java/com/example/order/ │ ├── OrderApplication.java # 启动类 │ └── PingController.java # /api/ping └── src/main/resources/ └── application.yml # 配置(本篇重点) ``` `pom.xml` 的关键片段 —— 只留 Web、Actuator、Java 17: ```xml <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- actuator 提供 /actuator/health,K8s 探针就指向它 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> ``` `application.yml` 只有几行,但每一行在 K8s 里都有意义: ```yaml server: port: 8080 shutdown: graceful # 收到 SIGTERM 后先停止收新请求,再等已有请求处理完 spring: lifecycle: timeout-per-shutdown-phase: 20s # 优雅退出的最长等待时间,超过就强退 management: endpoints: web: exposure: include: health,info # 只暴露探针和排查需要的端点 endpoint: health: probes: enabled: true # 开启 /actuator/health/liveness 与 /readiness ``` 关于**日志**有一条铁律:容器里**不要往文件写日志**。日志输出到 stdout(Spring Boot 默认就是 console),由容器运行时接管,再交给日志收集系统。往容器文件系统写日志,Pod 一被删除日志就没了,可写层还会不断膨胀。 业务接口简单到不能再简单,但它的返回值要能让我们"一眼看出跑的是哪个版本": ```java @RestController public class PingController { @GetMapping("/api/ping") public Map<String, String> ping() { return Map.of("status", "ok", "version", "1.0.0"); } } ``` ```text $ curl -s localhost:8080/api/ping {"status":"ok","version":"1.0.0"} ``` `version` 字段是故意的"版本探针":滚动更新后只要它从 `1.0.0` 变成别的值,就知道新代码真的生效了。 > 一句话:**优雅退出、探针端点、stdout 日志,这三件事必须在写代码时就配好**,它们不是运维阶段才考虑的,而是镜像要适配容器环境的前提。 --- ## 3. 写 Dockerfile 用多阶段构建:构建阶段有完整 JDK 和 Maven,运行阶段**只要 JRE**。逐行都有一句"为什么": ```dockerfile # ---------- 构建阶段:只有这里需要 JDK 和 Maven ---------- FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build # 先只拷 pom.xml:pom 不变时,依赖层直接命中缓存,不用重下 COPY pom.xml . RUN mvn -B -q dependency:go-offline # 再拷源码:改业务代码只会让下面这层重建,依赖层仍然复用 COPY src ./src RUN mvn -B -q clean package -DskipTests # ---------- 运行阶段:只要 JRE,镜像小一大截 ---------- FROM eclipse-temurin:17-jre-jammy WORKDIR /app ENV TZ=Asia/Shanghai # 基础镜像默认 UTC,不设日志时间差 8 小时 ENV LANG=C.UTF-8 # 显式 UTF-8,避免中文日志乱码 # 非 root 运行:先建用户,再把工作目录交给它 RUN useradd -r -u 1001 -m appuser && chown -R appuser:appuser /app USER 1001 # 只从构建阶段拿 jar,JDK、Maven、.m2、源码都不进运行镜像 COPY --from=builder --chown=appuser:appuser /build/target/*.jar /app/app.jar # exec 形式:java 进程就是 PID 1,能收到 SIGTERM,优雅退出才生效 ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "/app/app.jar"] ``` 两个关键点单独解释。**其一,`COPY pom.xml` 与 `COPY src` 必须分开**:只要 `pom.xml` 没变,`dependency:go-offline` 那层就命中缓存,省掉整个依赖下载;改业务代码只让后面几层重建,十几秒就能出新镜像。 **其二,`MaxRAMPercentage=75` 和容器内存 limit 的关系**:现代 JVM 在容器里会读 cgroup 的 `limits.memory` 决定堆上限,默认堆约占容器内存的 1/4,`-XX:MaxRAMPercentage=75` 把它调到 75%。若 K8s 给 `limits.memory: 1Gi`,堆上限约 768 MiB,剩下约 256 MiB 留给堆外内存(Metaspace、线程栈、直接内存)。**这里不设,JVM 可能按宿主机内存算堆,一旦超过容器 limit 就被 OOMKilled。** 它与第 6 节的 `resources.limits` 是配套的。 > 一句话:**镜像里的 JVM 参数、时区、用户、入口形式,全都要为"容器环境"适配。** 在物理机上跑惯的默认值,直接搬进容器会踩坑。 --- ## 4. 本地构建与冒烟测试 提交给流水线之前,先在本地把整条链走通一次。这一步叫**冒烟测试**:不追求覆盖全,只求"能起、能通、能看日志"。 ```bash mvn -B clean package -DskipTests # 1. 打包(先跳过测试,本地图快) docker build -t order-service:1.0.0 . # 2. 构建镜像 docker images | grep order-service # 3. 确认镜像存在、看体积 docker run -d --name order-smoke -p 8080:8080 \ -e TZ=Asia/Shanghai order-service:1.0.0 # 4. 后台跑起来 curl -s localhost:8080/actuator/health # 5. 探健康检查 curl -s localhost:8080/api/ping # 6. 探业务接口 docker logs -f --tail 50 order-smoke # 7. 看日志 docker rm -f order-smoke # 8. 清理 ``` 第 3 步的期望输出,体积应在 200MB 量级(多阶段构建的成果;若 700MB+ 说明多阶段没写对): ```text $ docker images | grep order-service order-service 1.0.0 9f3a1c2e7b04 10 seconds ago 213MB ``` 第 5、6 步的期望输出,以及第 7 步日志应有的样子(时间戳末尾 `+08:00`;若看到 `Z` 就是 UTC,回去检查 `ENV TZ`): ```text $ curl -s localhost:8080/actuator/health {"status":"UP"} $ curl -s localhost:8080/api/ping {"status":"ok","version":"1.0.0"} # docker logs 关键行 2026-10-15T10:42:11.338+08:00 INFO --- [main] c.e.order.OrderApplication : Started OrderApplication in 2.614 seconds ``` **冒烟测试是本地最后一道关。** 它可以很土,但绝不能省 —— 本地都起不来的镜像,在 K8s 里只会更隐蔽(`CrashLoopBackOff` 反复重启,还得多花时间排查)。 > 一句话:**能进 K8s 的镜像,必须是本地 `docker run` 起来、`curl` 通过、日志正常的镜像。** --- ## 5. 推到私有仓库 沿用第 03 篇的标签规范:**同时打版本 tag 和不可变的 sha tag**。 ```bash docker login registry.example.com # 1. 登录私有仓库 SHORT_SHA=$(git rev-parse --short=7 HEAD) # 2. 取当前提交短哈希 docker tag order-service:1.0.0 registry.example.com/prod/order-service:1.0.0 # 3. 双 tag docker tag order-service:1.0.0 registry.example.com/prod/order-service:sha-$SHORT_SHA docker push registry.example.com/prod/order-service:1.0.0 # 4. 推送 docker push registry.example.com/prod/order-service:sha-$SHORT_SHA ``` 推送输出看到 `digest: sha256:...` 就是成功并拿到了内容哈希: ```text $ docker push registry.example.com/prod/order-service:1.0.0 The push refers to repository [registry.example.com/prod/order-service] 7c9e1b2a4f88: Pushed a3f0d5c6e712: Pushed 1.0.0: digest: sha256:4b8f...c2d1 size: 1587 ``` 验证最干脆的方式是**换一台机器拉一次**(两个 tag 指向同一个 digest,可用 `docker inspect --format='{{index .RepoDigests 0}}'` 比对):`docker pull registry.example.com/prod/order-service:sha-9f3a1c2`。 > 一句话:**push 成功不等于部署能用。** 换台机器 `pull` 一次,才算真的确认镜像进了仓库。 --- ## 6. 部署到 K8s 这份 `app.yaml` 放四个对象,用 `---` 分隔,可整体 `kubectl apply`。 ```yaml apiVersion: v1 kind: ConfigMap metadata: name: order-service-config namespace: prod data: SPRING_PROFILES_ACTIVE: "prod" LOGGING_LEVEL_ROOT: "INFO" --- apiVersion: v1 kind: Secret metadata: name: order-service-secret namespace: prod type: Opaque stringData: SPRING_DATASOURCE_PASSWORD: "CHANGE_ME" # 换成真实密码;别把真实值提交进 git --- apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: prod spec: replicas: 2 selector: matchLabels: app: order-service strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 0 # 零停机的关键 template: metadata: labels: app: order-service spec: imagePullSecrets: - name: regcred # 私有仓库凭证,漏了就是 ImagePullBackOff containers: - name: order-service image: registry.example.com/prod/order-service:1.0.0 imagePullPolicy: IfNotPresent # tag 不变就不重复拉,加快启动 ports: - containerPort: 8080 envFrom: - configMapRef: name: order-service-config - secretRef: name: order-service-secret resources: requests: cpu: "250m" memory: "512Mi" limits: cpu: "1" memory: "1Gi" readinessProbe: # 依赖外部组件,决定能否接流量 httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 15 periodSeconds: 10 livenessProbe: # 只看自己,失败才重启 httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 40 periodSeconds: 20 --- apiVersion: v1 kind: Service metadata: name: order-service namespace: prod spec: type: ClusterIP selector: app: order-service ports: - name: http port: 80 targetPort: 8080 ``` 按顺序 apply 并验证: ```bash kubectl create namespace prod # 0. 一次性创建 kubectl -n prod create secret docker-registry regcred \ --docker-server=registry.example.com \ --docker-username=<用户名> --docker-password=<密码> kubectl apply -f app.yaml # 1. 提交资源 kubectl -n prod get pods -w # 2. 盯着 Pod 起来 kubectl -n prod get endpoints order-service # 3. Endpoints 不能为空! kubectl -n prod port-forward svc/order-service 8080:80 # 4. 打通验证 # → 另开一个终端:curl -s localhost:8080/api/ping ``` `kubectl get endpoints` 为空,是新手最常见的"Pod 明明 Running,服务却调不通"的原因 —— 说明就绪探针没通过,Pod 没被塞进 Service 后端列表。 几个关键字段为什么这么配: | 字段 | 作用 | 配错的后果 | |---|---|---| | `replicas: 2` | 至少两个副本,容一个挂掉 | 单副本 = 仍有单点,滚更那一刻服务会断 | | `imagePullSecrets` | 用私有仓库凭证拉镜像 | 拉不到,Pod 卡在 `ImagePullBackOff` | | `resources.requests` | 告诉 Scheduler 要多少资源,决定调度到哪台 | 不设就可能被塞满,和别的服务抢资源 | | `resources.limits` | 容器内存上限,决定 OOM 的边界 | 超了就被 `OOMKilled`;太小应用起不来 | | `readinessProbe` | 决定 Pod 能不能接流量 | 不设则启动未完成就被塞流量,返回 503 | | `livenessProbe` | 决定 Pod 要不要被重启 | 设成依赖 DB 的接口,DB 抖动 → 全部 Pod 集体重启 | | `envFrom` | 把 ConfigMap/Secret 注入为环境变量 | 配置硬编码进镜像,同一镜像没法多环境复用 | > 一句话:**`requests` 决定"能不能调度",`limits` 决定"会不会被杀",探针决定"接不接流量"。** 这三组字段配错,症状各不相同,但都很难查。 --- ## 7. 改一行代码,走完滚动更新 把 `/api/ping` 的返回值从 `1.0.0` 改成 `1.0.1`,观察整套机制如何联动。滚动更新的过程(更详细的图见「云原生」课程 01 篇):  *图:滚动更新时新旧 ReplicaSet 的 Pod 此消彼长,全程可用副本数不低于 2* ```text 滚动更新(maxSurge=1, maxUnavailable=0) 初始: RS-旧 [Pod][Pod] ① 起新: RS-旧 [Pod][Pod] RS-新 [Pod] ← 多出 1 个(maxSurge) ② 摘旧: RS-旧 [Pod][ ] RS-新 [Pod] ← 摘 1 个旧的,可用数不掉 ③ 交替: RS-旧 [Pod][ ] RS-新 [Pod][Pod] ④ 完成: RS-旧 [ ][ ] RS-新 [Pod][Pod] ← 旧 RS 保留副本 0 = 回滚点 ``` ```bash # 1. 改代码:把 PingController 里 version 改成 "1.0.1" # 2. 重新构建并推送(新 tag = 1.0.1 与新的 sha) mvn -B clean package -DskipTests && docker build -t order-service:1.0.1 . SHORT_SHA=$(git rev-parse --short=7 HEAD) docker tag order-service:1.0.1 registry.example.com/prod/order-service:1.0.1 docker tag order-service:1.0.1 registry.example.com/prod/order-service:sha-$SHORT_SHA docker push registry.example.com/prod/order-service:1.0.1 docker push registry.example.com/prod/order-service:sha-$SHORT_SHA # 3. 让 Deployment 用新镜像(触发滚动更新) kubectl -n prod set image deployment/order-service \ order-service=registry.example.com/prod/order-service:1.0.1 # 4. 盯住滚动更新;看新旧 Pod 交替(旧的在 Terminating,新的在 Running) kubectl -n prod rollout status deployment/order-service kubectl -n prod get pods # 5. 验证接口返回新值 curl -s localhost:8080/api/ping # 期望:{"status":"ok","version":"1.0.1"} ``` 接着演示**回滚**,这也是第 5 节要费劲打不可变 tag 的原因: ```bash kubectl -n prod rollout history deployment/order-service # 看历史版本 kubectl -n prod rollout undo deployment/order-service # 回退到上一个版本 kubectl -n prod rollout status deployment/order-service # 确认回退成功 curl -s localhost:8080/api/ping # 期望又变回 1.0.0 ``` 回滚为什么快?因为两件事都还在:**旧镜像还在仓库里**(tag 没被覆盖),**旧 ReplicaSet 还在集群里**(副本数被缩到 0,但定义没删)。`rollout undo` 只是把旧的 ReplicaSet 重新放大 —— 不需要重新构建,也不需要重新拉取(节点上大概率还有镜像缓存)。 最后是"**怎么确认当前跑的是哪个版本**",这是排障时的基本素养: ```bash # 方式一:取 Deployment 里容器用的镜像 kubectl -n prod get deployment order-service \ -o jsonpath='{.spec.template.spec.containers[0].image}' # → .../order-service:1.0.0 # 方式二:看某个 Pod 实际用的镜像 kubectl -n prod describe pod <pod-name> | grep Image: ``` > 一句话:**部署新版本是 `set image`,确认版本是 `jsonpath`,回滚是 `rollout undo`。** 这三个动作熟练了,日常发布就没了悬念。 --- ## 8. 排障对照表(本篇重点) 上线后 90% 的问题都逃不过下面这些。记住"现象 → 原因 → 第一步命令",比死记概念有用得多: | 现象 | 可能原因 | 第一步排查命令 | |---|---|---| | Pod 状态 `ImagePullBackOff` | 镜像地址写错 / 没配 `imagePullSecrets` / tag 不存在 | `kubectl -n prod describe pod <pod>`(看 Events) | | Pod 状态 `CrashLoopBackOff` | 应用启动就崩:配置错、依赖连不上、端口占用 | `kubectl -n prod logs <pod> --previous` | | 反复重启,日志里看不出错 | `livenessProbe` 太敏感,应用没起来就被杀 | `kubectl -n prod describe pod <pod>`(看探针失败事件) | | Pod 是 Running,服务却调不通 | `targetPort` 与实际端口不符,或探针没通过 | `kubectl -n prod get endpoints order-service` | | 探针路径一直 404 | 探针路径不对,或端点没暴露 | `kubectl -n prod exec <pod> -- curl -s localhost:8080/actuator/health` | | 日志时间差 8 小时 | 容器默认 UTC,没设 `TZ` | `kubectl -n prod exec <pod> -- date` | | 中文日志乱码 | 容器字符集不是 UTF-8 | `kubectl -n prod exec <pod> -- locale` | | Pod 状态 `OOMKilled` | `limits.memory` 太小,或 JVM 没按容器限额分配 | `kubectl -n prod describe pod <pod>`(看 `Reason: OOMKilled`) | | `/actuator/health` 返回 DOWN | 健康检查把 DB/Redis 也算进去了,依赖挂了 | `curl -s localhost:8080/actuator/health`(看哪个组件 DOWN) | | 改了镜像,Pod 不更新 | tag 没变(还是 `1.0.0`),Deployment 认为无变化 | `kubectl -n prod get deployment order-service -o jsonpath='{.spec.template.spec.containers[0].image}'` | 排查的通用顺序,与「云原生」课程 05 篇一致:  *图:从 kubectl get pods 开始的排障决策树:按状态分叉,从内到外排查* ```text kubectl get pods ← 先看状态 ↓ kubectl describe pod ← 看 Events,80% 的原因写在这里 ↓ kubectl logs <pod> [--previous] ← 看应用自己的报错 ↓ kubectl get endpoints ← Running 但连不上,多半是没通过就绪探针 ↓ kubectl exec <pod> -- curl ... ← 进去自己 curl,区分应用问题还是网络问题 ``` > 一句话:**`describe` 看集群视角,`logs` 看应用视角,两个都要看。** 只看其中一个,永远会漏掉一半信息。 --- ## 9. 上线自检清单 ```text □ 镜像用明确的版本 tag(1.0.0 / sha-xxxxxxx),生产不用 latest □ 容器以非 root 用户运行(Dockerfile 里有 USER) □ 配了 resources.requests 与 limits,且 JVM 用了 MaxRAMPercentage □ readiness 与 liveness 分离,liveness 不依赖数据库等外部组件 □ 应用开启了优雅退出(server.shutdown=graceful),ENTRYPOINT 是 exec 形式 □ 配置全部外置到 ConfigMap / Secret,镜像里没有环境相关配置 □ 副本数 ≥ 2,且没有把两个副本挤在同一台机器 □ 知道怎么回滚,并且真的演练过一次(rollout undo) □ 镜像里没有任何凭据(用 docker history 检查过) □ 日志输出到 stdout,没有往容器内写文件日志 □ 时区(TZ)与字符集(UTF-8)都正确 □ 健康检查路径正确,kubectl get endpoints 不为空 ``` 十二条里最容易漏的是"真的演练过一次回滚"。知道命令和实际跑过一次是两回事 —— 真上线那天,你不希望自己的第一次回滚是"在生产上第一次试"。 --- ## 10. 踩过的坑 ```text 现象:本地 docker build 成功,CI 里 mvn 报「不支持目标发行版本 17」 原因:构建阶段基础镜像的 JDK 版本不是 17 对策:builder 固定用 maven:3.9-eclipse-temurin-17 现象:镜像 800MB+,拉取特别慢 原因:把整个 JDK + Maven + 源码都打进了运行镜像 对策:多阶段构建,运行阶段只用 JRE,COPY --from 只拿 jar 现象:容器里 date 显示时间差 8 小时 原因:基础镜像默认 UTC,Dockerfile 没设 TZ 对策:ENV TZ=Asia/Shanghai,并在 docker run / K8s env 里也带上 现象:改了一行代码,K8s 里 Pod 却没重启 原因:镜像 tag 没变(还是 1.0.0),Deployment 认为无变化 对策:每次构建推唯一 tag(sha-xxxxxxx),部署时引用新 tag 现象:滚动更新时外部请求出现少量 502 原因:没配 readinessProbe,新 Pod 未就绪就被塞流量 对策:配就绪探针,并设 maxUnavailable: 0 现象:Pod 被删除后,正在处理的请求报 connection reset 原因:java 不是 PID 1(ENTRYPOINT 用了 shell 形式),收不到 SIGTERM 对策:用 exec 形式 ENTRYPOINT,应用自己处理 SIGTERM 做优雅退出 现象:Pod 被 OOMKilled,节点上明明还有内存 原因:limits.memory 小于 JVM 默认堆上限 对策:-XX:MaxRAMPercentage=75,并把 limits 留出堆外余量 现象:Service 的 Endpoints 一直是空的,服务调不通 原因:就绪探针路径写错,Pod 永远不 Ready 对策:kubectl exec 进容器 curl 一下探针路径,确认返回 200 ``` --- ## 11. 本篇小结  *图:本篇小结:写代码到回滚的完整闭环,每一环都对应前四篇的一个知识点* ```text 写代码(优雅退出 / 探针端点 / stdout 日志) │ v Dockerfile 多阶段构建(JDK+Maven 构建 → 只留 JRE 运行) │ v 本地冒烟测试(docker run + curl + logs) │ v 推私有仓库(tag = 1.0.0 + sha-<短哈希>,两个 tag 同一 digest) │ v kubectl apply(ConfigMap / Secret / Deployment / Service) │ v get pods -w + get endpoints + port-forward ← 验证 │ v 改一行代码 → 重新 build/push → kubectl set image ← 滚动更新 │ ├─ 正常:rollout status 通过 └─ 出问题:rollout undo 回退(旧镜像还在、旧 RS 还在) 出问题的定位顺序: get pods → describe pod → logs(--previous) → get endpoints → exec curl ``` 到这里,从源码到线上,整条链路你至少亲手走过一遍了。镜像从哪来、怎么造、怎么交付 —— 这三个问题有了完整的答案。 --- ## 12. 下一步可以学什么  *图:接下来学什么:从 Helm 到 Service Mesh 的六级路线,以及现在所处的位置* ```text 镜像与交付熟了(本课到此) │ v 先把"无状态服务"玩熟(Deployment / Service / 滚动更新 / 回滚) │ v Helm(把一堆 YAML 参数化、版本化,一次装一套、按环境覆盖) │ v 可观测性(日志聚合 + Prometheus 指标 + 链路追踪) │ v HPA(按 CPU / QPS 自动扩缩容,让副本数跟着流量走) │ v StatefulSet / Operator(有状态服务:数据库、消息队列、自定义控制器) │ v Service Mesh(Istio 等:流量治理、灰度发布、mTLS 加密) ``` 这个顺序不是"越往后越高级",而是**依赖关系**:Helm 解决"YAML 太多、多环境难维护";可观测性是"看清线上在发生什么"的前置,没有指标和日志,HPA 与排障都是盲人摸象;HPA 建立在"服务无状态、能水平扩展"之上;StatefulSet / Operator 是把有状态服务复杂度真正降下来的东西,但要求你先理解无状态那一整套;Service Mesh 是最后一步,它假设你已有稳定服务和完善可观测性。 这些内容,本站的「云原生」课程已覆盖其中大部分 —— 它从 K8s 入门讲起,第 04 篇就是"完整实战:一套 Spring Boot + MySQL 服务上线"。学完本课再看那门课,`Deployment`、`Service`、`ConfigMap`、`Secret` 这些对象你应该已不再陌生,可以把精力放在**它们为什么这么设计**上。镜像和交付是云原生世界里最"底层"的一块,这块打牢了,往上叠什么都不会晃。
预览
发布文章
添加摘要让读者更快了解文章内容
取消
确认发布
AI 写作助手
输入一个主题,AI 将为你生成一篇 Markdown 博客文章
AI 正在写作中...
取消
开始生成
AI 优化助手
描述你的修改需求,AI 将在原文基础上进行补充或微调
AI 正在优化中...
取消
开始优化
画布
从已有图里选
✕
图名
保存后生成 /images/excalidraw/<图名>.svg(只能用字母、数字、短横线、下划线、点)
取消
保存并插入