← 返回场景决策

🚀 部署运维场景决策

什么时候用 / 什么时候不用 / 边界在哪里 — 9 大核心场景全解析

Docker K8s 蓝绿部署 金丝雀发布 回滚 日志收集 监控告警 灰度发布 优雅停机

1. Docker — 容器化决策

Docker = 集装箱 应用和所有依赖打包在一起,到哪都能跑
不用Docker = 散装运输 每台机器都要装环境,"在我电脑上能跑"
什么时候用 Docker
  • 环境一致性:开发/测试/生产环境统一 — 消除"我电脑上能跑"
  • 微服务部署:每个服务一个容器 — 独立部署/扩缩容
  • CI/CD:构建产物就是 Docker 镜像 — 一致性保证
  • 快速启动:秒级启动 — 比虚拟机快100倍
什么时候不用 Docker
  • GUI 应用:桌面软件 — Docker 不适合跑GUI
  • 高性能计算:需要直接操作硬件/GPU — 容器有隔离开销
  • 数据存储:数据库容器化需谨慎 — 数据持久化复杂
  • 极简项目:就一个 jar 包 — 直接 java -jar 更简单

💻 Spring Boot 多阶段构建

📀 myapp
📁 deploy/
📁 docker/
📄 Dockerfile
docker-compose.yml
.dockerignore
# Dockerfile — 多阶段构建,减小镜像体积 FROM eclipse-temurin:17-jdk-alpine AS build WORKDIR /app COPY . . RUN ./mvnw package -DskipTests FROM eclipse-temurin:17-jre-alpine WORKDIR /app COPY --from=build /app/target/*.jar app.jar # 非root用户运行 RUN addgroup -S app && adduser -S app -G app USER app EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] # 构建和运行 # docker build -t myapp:1.0 . # docker run -p 8080:8080 myapp:1.0
面试金句 多阶段构建(Multi-stage Build):第一阶段用 JDK 编译打包,第二阶段只 COPY jar 到 JRE 镜像中,最终镜像不含 Maven 缓存和源码,体积从 800MB+ 降至 ~200MB。非 root 用户运行是容器安全的基本要求,防止容器逃逸后获取宿主机 root 权限。

💻 Docker Compose 本地开发

📀 myapp
📁 deploy/
📁 docker/
Dockerfile
docker-compose.yml
.dockerignore
# docker-compose.yml version: '3.8' services: app: build: . ports: ["8080:8080"] environment: SPRING_PROFILES_ACTIVE: dev DB_HOST: mysql REDIS_HOST: redis depends_on: [mysql, redis] mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: app_dev ports: ["3306:3306"] redis: image: redis:7-alpine ports: ["6379:6379"] # 一键启动全套环境 # docker-compose up -d
面试金句 docker-compose 定义多容器应用的编排方式:depends_on 声明启动顺序但不等待就绪(MySQL 容器启动 ≠ MySQL 可连接),生产环境需配合 healthcheck 或重试机制。服务名即 DNS 名,app 容器内可用 mysql:3306 直接访问数据库,无需 IP。

2. Kubernetes — 容器编排决策

K8s = 自动化港口 集装箱(容器)来了,自动调度、扩缩容、自愈
什么时候用 K8s
  • 微服务 > 5 个:服务多需要统一管理 — K8s 自动调度
  • 弹性扩缩容:流量高峰自动扩容 — HPA (Horizontal Pod Autoscaler)
  • 高可用:服务挂了自动重启 — 自愈能力
  • 多环境:dev/staging/prod 统一管理 — Namespace 隔离
什么时候不用 K8s
  • 小项目 < 3 个服务:K8s 运维成本远大于收益 — Docker Compose 就够
  • 没有运维团队:K8s 学习曲线极陡 — 出问题没人会修
  • 单机部署:一台服务器跑所有服务 — 没必要编排

💻 K8s 部署 YAML

📀 order-service
📁 k8s/
deployment.yaml
service.yaml
hpa.yaml
📁 ingress/
apiVersion: apps/v1 kind: Deployment metadata: name: order-service spec: replicas: 3 # 3个副本 selector: matchLabels: { app: order-service } template: metadata: labels: { app: order-service } spec: containers: - name: order-service image: registry.example.com/order-service:1.0.0 ports: [{ containerPort: 8080 }] resources: requests: { memory: "512Mi", cpu: "200m" } limits: { memory: "1Gi", cpu: "500m" } livenessProbe: # 存活检查 httpGet: { path: /actuator/health/liveness, port: 8080 } initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: # 就绪检查 httpGet: { path: /actuator/health/readiness, port: 8080 } initialDelaySeconds: 10 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: order-service spec: selector: { app: order-service } ports: [{ port: 80, targetPort: 8080 }] --- # HPA 自动扩缩容 apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: order-service-hpa spec: scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: order-service } minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }
面试金句 resources.requests 是调度依据(K8s 据此决定 Pod 放哪个 Node),limits 是硬上限(超过 memory limit 触发 OOMKilled)。HPA 根据 CPU 利用率(实际/request)自动调整副本数,阈值为 70% 意味着平均每个 Pod CPU 使用率达到 70% 就触发扩容。Liveness 失败→重启 Pod,Readiness 失败→摘除流量但不重启

3. 部署策略决策

📊 部署策略决策表

策略原理停机时间回滚速度资源消耗适合
滚动更新逐步替换旧Pod默认策略
蓝绿部署两套环境切换秒级切换2倍关键服务
金丝雀发布小流量先验证高风险变更
重建先停旧再启新开发环境

💻 金丝雀发布 Nginx 配置

📀 deploy
📁 nginx/
nginx.conf
upstream.conf
📁 k8s/
canary-deployment.yaml
# Nginx 金丝雀:10%流量到新版本 upstream backend { server old-service:8080 weight=9; # 90%流量 server new-service:8080 weight=1; # 10%流量 } # K8s 金丝雀(更精细控制) # kubectl apply -f canary-deployment.yaml # 观察10分钟,无异常则逐步增加流量 # kubectl scale deployment order-service-canary --replicas=5 # 全部正常后,删除旧版本
面试金句 金丝雀发布(Canary Release):先让小比例流量(如 10%)打到新版本,观察错误率/延迟/业务指标无异常后再逐步放量。Nginx weight 实现简单加权轮询,无法精确控制流量比例;K8s 通过副本数比例(如 old:9 replicas + new:1 replica ≈ 10%)实现更精细的金丝雀。

4. 回滚决策

什么时候回滚
  • 发布后立即报错:大量5xx错误 — 不回滚等什么?
  • 性能严重劣化:响应时间翻倍 — 影响用户体验
  • 数据错误:新版本有数据Bug — 回滚+修数据
  • 核心功能不可用:登录/支付不能用 — 立即回滚

💻 K8s 回滚命令

📀 ops
📁 ops/
rollback.sh
deploy.sh
# 查看部署历史 kubectl rollout history deployment/order-service # 回滚到上一个版本 kubectl rollout undo deployment/order-service # 回滚到指定版本 kubectl rollout undo deployment/order-service --to-revision=3 # 查看回滚状态 kubectl rollout status deployment/order-service
面试金句 K8s 滚动更新(Rolling Update)默认保留 10 个 revision 历史(revisionHistoryLimit),每次 kubectl apply 创建一个新 revision。undo 实际上是创建一个"反向"滚动更新,把 Pod 逐步替换回旧镜像。关键前提:镜像 tag 必须不同(不能用 latest),否则 K8s 无法区分版本。

💻 Docker 回滚

📀 ops
📁 ops/
rollback.sh
docker-rollback.sh
# 查看镜像版本 docker images myapp # 回滚:重新运行旧版本镜像 docker stop myapp-container docker rm myapp-container docker run -d --name myapp-container -p 8080:8080 myapp:1.0.0 # 旧版本
面试金句 Docker 回滚 = 重新运行旧版本镜像。与 K8s 的渐进式滚动回滚不同,纯 Docker 环境的回滚会有短暂停机(stop→rm→run 期间服务不可用)。最佳实践:镜像 tag 使用语义化版本号(如 1.2.3),永远不要删旧版本镜像,至少保留最近 3 个可用版本。

5. 日志收集 — 4 大方案对比

📊 日志方案决策表

方案组件适合复杂度
ELKElasticsearch + Logstash + Kibana大中型项目
EFKElasticsearch + Fluentd + KibanaK8s环境
Loki + GrafanaLoki + Promtail + Grafana轻量级首选
云厂商日志阿里云SLS / 腾讯云CLS上云项目最低
💡 怎么选:中小团队 → Loki + Grafana(轻、便宜、一个面板看日志+监控);要全文搜索 → ELK;上云不想运维 → SLS/CLS。→ 详见:日志收集方案详解

💻 Spring Boot 日志配置

📀 order-service
📁 src/main/resources/
logback-spring.xml
application.yml
📁 java/.../config/
LogConfig.java
// logback-spring.xml — JSON格式日志(方便ELK解析) <configuration> <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LogstashEncoder"> <includeMdc>true</includeMdc> // MDC 中的 traceId </encoder> </appender> // 代码中设置 traceId MDC.put("traceId", traceId); // 日志输出: // {"@timestamp":"2024-01-15T10:30:00","level":"INFO", // "traceId":"abc123","logger":"OrderService", // "message":"订单创建成功"} </configuration>
面试金句 JSON 结构化日志是 ELK/Loki 等日志平台高效检索的基础:传统文本日志需要 Grok 正则解析(脆弱且消耗 CPU),JSON 日志可直接被索引为字段。MDC(Mapped Diagnostic Context)是 ThreadLocal 级别的日志上下文,存放 traceId/userId 等链路标识,Logback 自动将 MDC 内容输出为 JSON 字段。

6. 监控告警决策

📊 监控方案决策表

维度工具监控什么告警方式
基础设施Prometheus + GrafanaCPU/内存/磁盘/网络AlertManager → 钉钉/企微
应用性能SkyWalking/Pinpoint接口耗时/错误率/调用链内置告警
日志监控ELK/LokiERROR日志量/关键字ElastAlert/Loki ruler
业务监控自定义Dashboard订单量/支付成功率异常下降告警
容器监控cAdvisor + Prometheus容器CPU/内存/OOMPod重启告警
⚠️ 告警设计原则
  • 分级:P0(电话) / P1(短信) / P2(即时消息) / P3(邮件)
  • 阈值:CPU > 90% 持续 5 分钟 → 告警(避免抖动)
  • 去重:同一告警 5 分钟内不重复发送
  • 升级:P0 告警 10 分钟未处理 → 升级通知主管

7. 优雅停机 — 零中断部署

优雅停机三步走
  1. 停止接收新请求:从注册中心摘除/健康检查设为不健康
  2. 等待现有请求完成:给一个优雅期(如30秒),等正在处理的请求结束
  3. 关闭资源:关闭数据库连接、消息消费者、定时任务

💻 Spring Boot 优雅停机配置

📀 order-service
📁 src/main/resources/
application.yml
📁 k8s/
deployment.yaml
// application.yml server: shutdown: graceful // 开启优雅停机 spring: lifecycle: timeout-per-shutdown-phase: 30s // 最多等30秒 // K8s 配合优雅停机 // Pod 删除时: // 1. kube-proxy 移除 Endpoints(新请求不再来) // 2. 发送 SIGTERM 给容器 // 3. Spring Boot 收到 SIGTERM → 不接收新请求 → 等待现有请求完成 // 4. 30秒后如果还没结束 → SIGKILL 强制杀死 // K8s Pod 配置 spec: terminationGracePeriodSeconds: 45 // K8s给45秒,Spring只要30秒 containers: - name: app lifecycle: preStop: exec: command: ["sh", "-c", "sleep 10"] // 额外等10秒让K8s摘除
面试金句 优雅停机的核心:terminationGracePeriodSeconds(K8s)必须 > timeout-per-shutdown-phase(Spring),否则 Spring 还没处理完请求就被 SIGKILL 强杀。preStop hook 的 sleep 10 是为了解决Endpoints 异步传播延迟——kube-proxy 移除 Endpoints 与 SIGTERM 发送是同时进行的,但上游的连接表更新有延迟,sleep 让旧连接有时间被摘除。

8. 健康检查 — 存活 vs 就绪

📊 存活 vs 就绪决策表

检查失败后果检查内容典型场景
Liveness(存活)重启容器进程是否活着死锁、线程耗尽
Readiness(就绪)摘除流量是否能处理请求依赖不可用、初始化中
Startup(启动)继续等待是否启动完成慢启动应用

💻 Spring Boot Actuator 健康检查

📀 order-service
📁 src/main/resources/
application.yml
📁 java/.../health/
RedisHealthIndicator.java
// application.yml management: endpoint: health: show-details: always probes: enabled: true // 开启 k8s 探针端点 endpoints: web: exposure: include: health,info,prometheus // 自动暴露: // /actuator/health/liveness → K8s livenessProbe // /actuator/health/readiness → K8s readinessProbe // 自定义健康检查 @Component public class RedisHealthIndicator implements HealthIndicator { @Autowired private RedisTemplate<String, String> redis; @Override public Health health() { try { redis.ping(); return Health.up().withDetail("redis", "connected").build(); } catch (Exception e) { return Health.down().withException(e).build(); } } }
面试金句 Spring Boot Actuator HealthIndicator 聚合所有组件健康状态(Redis/DB/Disk…),任一 DOWN 则整体 /actuator/health 返回 DOWN。K8s Probes(probes.enabled=true)将 Liveness 和 Readiness 分离到不同端点,实现精确控制:Readiness DOWN 摘流不重启(依赖临时不可用),Liveness DOWN 重启 Pod(进程死锁/线程耗尽)。

9. 环境管理决策

📊 环境规划决策表

环境用途数据部署频率谁用
DEV(开发)开发调试Mock/假数据每次提交开发
TEST(测试)功能测试脱敏数据每天QA
UAT(预发)验收测试脱敏生产数据发版前产品/客户
PROD(生产)正式上线真实数据按计划发布所有用户
⚠️ 环境管理最佳实践
  • 配置隔离:不同环境不同配置 — 绝对不能连错数据库
  • 数据脱敏:预发环境不能用真实用户数据 — GDPR/隐私合规
  • 权限控制:生产环境只有运维能操作 — 开发不能直连生产库
  • 环境一致性:所有环境用同样的 Docker 镜像 — 只改配置不改代码

🚀 部署运维场景决策 · 全栈资料库

← 返回场景决策