DevOps 商业场景训练营
这页把 DevOps 放到真实商业系统交付里学习。目标不是背 Docker、Nginx、Jenkins、Kubernetes 命令,而是能解释代码如何变成线上服务,发布失败怎么定位,服务访问不了从哪一层查,配置和密钥为什么不能乱放,灰度和回滚为什么依赖镜像、配置、数据库变更一起设计。
训练目标
学完这页,你要能做到:
- 画出从 Git 提交到用户访问服务的完整交付链路。
- 解释源码、构建产物、镜像、容器、Pod、Service、Ingress 的区别。
- 写出 Spring Boot 服务的 Dockerfile、Compose、Jenkinsfile、K8s Deployment/Service。
- 解释 Nginx 反向代理、负载均衡、限流、真实 IP 透传。
- 解释 Kubernetes 滚动发布、健康检查、ConfigMap、Secret、资源限制。
- 排查构建失败、镜像失败、Pod 起不来、服务访问不了、发布后接口变慢。
- 设计订单、采集、搜索这类商业系统的发布、灰度、回滚和可观测性。
商业交付总链路
mermaid
flowchart TD
A["开发提交代码"] --> B["Git 仓库"]
B --> C["Jenkins Pipeline"]
C --> D["编译、测试、质量检查"]
D --> E["生成 jar 或 dist"]
E --> F["构建 Docker 镜像"]
F --> G["推送镜像仓库"]
G --> H["更新 K8s Deployment"]
H --> I["Pod 拉镜像并启动"]
I --> J["readinessProbe 通过"]
J --> K["Service 接入流量"]
K --> L["Ingress/Nginx 暴露入口"]
L --> M["用户访问"]
M --> N["日志、指标、链路追踪、告警"]这条链路里每一步都要可追踪:
| 环节 | 要记录什么 | 为什么 |
|---|---|---|
| Git | commit、分支、提交人 | 知道代码来源 |
| 构建 | 构建号、测试结果 | 知道产物是否可信 |
| 镜像 | image tag、digest | 知道线上跑的是什么 |
| 部署 | 环境、命名空间、发布时间 | 出问题能回滚 |
| 运行 | 日志、指标、traceId | 出问题能定位 |
Demo 一:Spring Boot Dockerfile
JDK8 仍然是很多企业项目的主力,所以示例不只基于最新 JDK。
dockerfile
FROM eclipse-temurin:8-jre
WORKDIR /app
COPY target/order-api.jar /app/order-api.jar
ENV JAVA_OPTS="-Xms512m -Xmx512m"
ENV SPRING_PROFILES_ACTIVE="prod"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/order-api.jar"]为什么这样写:
FROM固定运行时,避免服务器 Java 版本漂移。WORKDIR统一应用目录。JAVA_OPTS让堆大小、GC 参数可配置。SPRING_PROFILES_ACTIVE区分环境。- 不把数据库密码写进镜像,密钥要由环境变量、Secret 或配置中心注入。
常见错误:
| 错误 | 后果 |
|---|---|
使用 latest 基础镜像 | 基础镜像变化导致不可复现 |
| 把配置文件和密码打进镜像 | 镜像泄露就等于密钥泄露 |
| 没设置 JVM 内存 | 容器内存限制下容易 OOM |
| 容器内写大量本地日志 | 磁盘被打满 |
Demo 二:本地 Compose 联调
yaml
version: "3.8"
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: order_db
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:7
ports:
- "6379:6379"
order-api:
build: .
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/order_db
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: root
SPRING_REDIS_HOST: redis
depends_on:
- mysql
- redis
volumes:
mysql-data:重点:
- 容器间访问 MySQL 用服务名
mysql,不是127.0.0.1。 depends_on只保证启动顺序,不保证 MySQL 已经可连接。- 应用要配置连接超时和重试。
- 数据要挂 volume,否则容器删除后数据丢失。
Demo 三:Jenkins Pipeline
groovy
pipeline {
agent any
environment {
APP_NAME = 'order-api'
REGISTRY = 'registry.example.com/backend'
IMAGE_TAG = "${env.BUILD_NUMBER}-${env.GIT_COMMIT.take(8)}"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Test') {
steps {
sh 'mvn clean test'
}
}
stage('Package') {
steps {
sh 'mvn package -DskipTests'
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
stage('Build Image') {
steps {
sh 'docker build -t $REGISTRY/$APP_NAME:$IMAGE_TAG .'
}
}
stage('Push Image') {
steps {
sh 'docker push $REGISTRY/$APP_NAME:$IMAGE_TAG'
}
}
stage('Deploy') {
steps {
sh 'kubectl set image deployment/order-api order-api=$REGISTRY/$APP_NAME:$IMAGE_TAG -n prod'
sh 'kubectl rollout status deployment/order-api -n prod --timeout=180s'
}
}
}
}生产原则:
- 镜像 tag 要可追踪,不能只用
latest。 - 凭据放 Jenkins Credentials。
- 测试失败不能继续发布。
- 部署后必须等 rollout 结果。
- 流水线日志不能打印密码、token、私钥。
Demo 四:Kubernetes 部署
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
namespace: prod
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: order-api
template:
metadata:
labels:
app: order-api
spec:
containers:
- name: order-api
image: registry.example.com/backend/order-api:1.0.0
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: order-api-config
- secretRef:
name: order-api-secret
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: order-api
namespace: prod
spec:
selector:
app: order-api
ports:
- port: 80
targetPort: 8080为什么 maxUnavailable: 0?为了滚动发布时尽量保证旧实例不先下线,避免可用实例不足。具体配置要看服务容量,不能所有系统照抄。
为什么要 readinessProbe?因为应用进程启动了不代表能接流量。数据库连接、缓存预热、配置加载还没完成时,Service 不应该把流量转发给它。
Nginx 入口配置
nginx
upstream order_api {
server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
}
server {
listen 80;
server_name api.example.com;
location /api/ {
proxy_pass http://order_api/;
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Request-Id $request_id;
}
}关键点:
proxy_connect_timeout控制连接后端超时。proxy_read_timeout控制等待后端响应超时。X-Forwarded-For用于真实 IP 追踪。X-Request-Id可以和后端 traceId 打通。- Nginx 超时不能无限大,否则会占用连接资源。
灰度发布和回滚
mermaid
flowchart TD
A["新版本构建完成"] --> B["部署少量实例"]
B --> C["只接少量流量"]
C --> D["观察错误率、P99、业务指标"]
D --> E{"指标是否正常"}
E -- "正常" --> F["扩大流量"]
E -- "异常" --> G["停止发布并回滚"]回滚不仅是 kubectl rollout undo。要检查:
- 旧镜像是否还在镜像仓库。
- 配置是否兼容旧代码。
- 数据库变更是否向前兼容。
- MQ 消息格式是否兼容旧消费者。
- 前端静态资源是否和后端接口兼容。
数据库变更推荐:
| 阶段 | 做法 |
|---|---|
| 第一次发布 | 先加字段、加表,不删除旧字段 |
| 代码灰度 | 新旧代码都能跑 |
| 稳定后 | 再清理旧字段和旧逻辑 |
商业场景一:订单系统发布
订单系统发布风险高,因为涉及支付、库存、MQ 和状态机。
发布前检查:
- 数据库迁移是否兼容旧版本。
- MQ 消息字段是否兼容旧消费者。
- 支付回调接口是否保持可用。
- 限流和熔断配置是否合理。
- 监控是否覆盖下单成功率、支付回调失败率、订单卡状态数量。
发布后观察:
mermaid
flowchart TD
A["订单系统发布"] --> B["接口错误率"]
A --> C["P95/P99 延迟"]
A --> D["下单成功率"]
A --> E["支付回调成功率"]
A --> F["MQ 堆积"]
A --> G["数据库慢 SQL"]商业场景二:医疗采集平台发布
采集平台发布要考虑长任务和外部医院接口。
发布策略:
- 调度任务先暂停或降频。
- 正在执行的采集批次要能完成或可恢复。
- 采集网关滚动发布,避免所有连接同时断开。
- 采集配置变更要审计。
- 发布后观察采集成功率、失败批次、MQ 堆积、ES 同步延迟。
生产排查决策树
服务访问不了
mermaid
flowchart TD
A["服务访问不了"] --> B["DNS 是否解析"]
B --> C["Nginx/Ingress 是否收到请求"]
C --> D["Service 是否有 endpoints"]
D --> E["Pod 是否 Ready"]
E --> F["容器是否启动成功"]
F --> G["应用端口是否监听"]
G --> H["依赖 DB/Redis/MQ 是否可用"]Pod 起不来
mermaid
flowchart TD
A["Pod 起不来"] --> B["kubectl describe pod"]
B --> C["ImagePullBackOff 看镜像和凭据"]
B --> D["CrashLoopBackOff 看应用日志"]
B --> E["Pending 看资源和调度"]
B --> F["探针失败看 health endpoint"]发布后接口变慢
mermaid
flowchart TD
A["发布后变慢"] --> B["看新旧版本流量占比"]
B --> C["看 P99 和错误率"]
C --> D["看 Pod CPU/内存/GC"]
D --> E["看 DB/Redis/MQ 依赖"]
E --> F["看配置和线程池变化"]
F --> G["必要时回滚"]磁盘打满
mermaid
flowchart TD
A["磁盘打满"] --> B["容器日志是否过大"]
B --> C["镜像和无用层是否堆积"]
C --> D["应用文件上传或临时文件"]
D --> E["数据库或中间件数据目录"]
E --> F["清理并补日志轮转和告警"]常用排查命令
bash
kubectl get pod -n prod
kubectl describe pod order-api-xxx -n prod
kubectl logs order-api-xxx -n prod --tail=200
kubectl get events -n prod --sort-by=.metadata.creationTimestamp
kubectl get endpoints order-api -n prod
kubectl rollout history deployment/order-api -n prod
kubectl rollout undo deployment/order-api -n prod
kubectl top pod -n prodDocker:
bash
docker ps -a
docker logs order-api --tail=200
docker inspect order-api
docker exec -it order-api sh
docker system dfNginx:
bash
nginx -t
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
curl -H "Host: api.example.com" http://127.0.0.1/api/health面试标准回答
代码到上线全过程怎么说
text
开发提交代码到 Git 后,CI 流水线拉取代码,执行编译、测试和打包,生成 jar 或前端 dist。然后构建 Docker 镜像并推送到镜像仓库。部署阶段更新 Kubernetes Deployment,Pod 拉取镜像启动,通过 readinessProbe 后加入 Service,最终由 Ingress 或 Nginx 对外暴露。上线后通过日志、指标和链路追踪观察错误率、延迟和业务指标,异常时停止发布或回滚。服务访问不了怎么排查
text
我会先确认请求到达哪一层:DNS 是否解析,Nginx 或 Ingress 是否收到请求,Service 是否有 endpoints,Pod 是否 Ready,容器是否启动成功,应用端口是否监听,最后看依赖的数据库、Redis、MQ 是否可用。不要一开始只看应用日志,因为访问不了可能发生在入口、网络、服务发现、探针或容器启动任意一层。为什么发布要有健康检查和回滚
text
进程启动不代表服务能接流量,数据库连接、配置加载、缓存预热失败都可能导致应用不可用。readinessProbe 可以控制是否接入流量,livenessProbe 可以发现进程假死。回滚是为了在新版本错误率、延迟或业务指标异常时快速恢复,但回滚必须依赖镜像、配置、数据库变更和消息格式兼容。