DevOps 从零到精通验收清单
这一页不是命令速查,而是 DevOps 的学习验收表。目标是让零基础同学能沿着“代码如何变成线上服务”这条主线,把 Docker、Nginx、Jenkins、Kubernetes、配置、密钥、发布、回滚、监控和排障串起来。
DevOps 真正要解决的问题不是“会不会敲 docker 命令”,而是:
- 代码能不能稳定构建。
- 制品能不能追踪来源。
- 环境能不能一致复现。
- 配置和密钥能不能隔离。
- 发布能不能灰度、观察和回滚。
- 出故障能不能快速知道坏在哪一层。
最终学习目标
学完 DevOps 模块,至少要能做到:
| 能力 | 合格标准 | 不合格表现 |
|---|---|---|
| 交付链路 | 能画出 Git 到用户访问的全过程 | 只知道把 jar 复制到服务器 |
| Docker | 能解释镜像、容器、层、网络、数据卷、日志 | 把容器当虚拟机 |
| Nginx | 能解释反向代理、负载均衡、限流、HTTPS、真实 IP | 502/504 分不清 |
| Jenkins | 能写 Pipeline,知道凭据、制品、镜像 tag、健康检查 | 只会点构建按钮 |
| Kubernetes | 能解释 Pod、Deployment、Service、Ingress、ConfigMap、Secret、Probe、Volume | Pod 访问不了只会看应用日志 |
| 发布回滚 | 能设计滚动发布、灰度、停止放量、回滚和数据库兼容 | 以为回滚只是重启旧代码 |
| 可观测性 | 能用日志、指标、链路追踪定位问题 | 用户说慢只能猜 |
| 生产排查 | 能按入口、网络、服务、容器、应用、依赖、资源逐层查 | 一上来乱改配置 |
总学习路线
flowchart TD
A["理解交付链路"] --> B["Docker 镜像和容器"]
B --> C["Dockerfile 和 Compose"]
C --> D["Nginx 入口代理"]
D --> E["Jenkins Pipeline"]
E --> F["Kubernetes 工作负载"]
F --> G["配置、密钥、存储、探针"]
G --> H["灰度、回滚、扩缩容"]
H --> I["日志、指标、链路追踪"]
I --> J["生产故障排查"]学习时不要把工具割裂开。Docker 解决“应用和运行环境一起交付”,Nginx 解决“入口流量如何进入服务”,Jenkins 解决“构建和发布流程自动化”,Kubernetes 解决“多个容器如何在集群中稳定运行”,监控告警解决“上线后怎么知道系统是否健康”。
阶段一:DevOps 到底解决什么
是什么
DevOps 是把开发、测试、构建、制品、部署、运行、监控、告警、回滚和复盘连成闭环的工程体系。
为什么需要
传统人工发布常见问题:
| 问题 | 例子 | 后果 |
|---|---|---|
| 环境漂移 | 开发机 JDK8,服务器 JDK17 | 本地正常,线上启动失败 |
| 人工操作 | 手动上传 jar、手动改配置 | 容易漏步骤、难审计 |
| 制品不可追踪 | 不知道线上 jar 来自哪个 commit | 出事故无法定位版本 |
| 配置混乱 | 测试库地址发布到生产 | 数据污染或服务不可用 |
| 无回滚能力 | 只有当前版本,没有旧镜像 | 故障恢复慢 |
| 无观测能力 | 没有日志聚合、指标、告警 | 用户反馈后才知道坏了 |
全过程原理
flowchart TD
A["开发提交代码"] --> B["Git 保存 commit"]
B --> C["CI 拉取指定版本"]
C --> D["编译、测试、质量检查"]
D --> E["生成可运行产物"]
E --> F["构建不可变镜像"]
F --> G["推送镜像仓库"]
G --> H["CD 更新目标环境"]
H --> I["实例启动并健康检查"]
I --> J["入口接入流量"]
J --> K["持续观察日志和指标"]
K --> L{"是否异常"}
L -- "正常" --> M["完成发布"]
L -- "异常" --> N["停止放量或回滚"]这条链路的关键是“可重复、可追踪、可回滚”。如果同一个 commit 每次构建出的东西不同,或者线上运行的镜像不知道来自哪里,就无法保证生产稳定。
阶段二:Docker 镜像和容器
零基础第一次操作前,先完成 Docker第一次运行:从CLI到容器生命周期 的整套实验。要求不是“命令执行成功”,而是能解释每一步由CLI、daemon、Registry、镜像、容器主进程、端口发布、日志驱动、可写层和Volume中的哪个对象完成。
在学习镜像和容器前,必须先通过 Docker安装、权限与升级 建立可验证运行环境:
- 解释 CLI、dockerd、containerd、shim、runc、Buildx、Compose 的安装来源和职责。
- 识别 OS、Kernel、CPU 架构、cgroup、文件系统、空间、inode、DNS、时间和代理。
- 说明 APT/RPM 仓库签名、keyring、
signed-by和发行版代号为什么不能跳过。 - 使用 systemd 检查 service、socket、unit参数和journal,区分 daemon-reload 与 restart。
- 解释 rootful socket、docker组近似root权限和普通业务容器挂载socket的风险。
- 说明 rootless 的User Namespace、用户级systemd、网络、端口、cgroup和存储边界。
- 规划 data-root、overlay2、日志轮转、地址池、代理、Registry与私有CA。
- 通过 Docker daemon配置原理、安全变更与故障回滚 区分CLI配置、daemon配置和容器HostConfig,证明systemd unit、drop-in、flags和daemon.json的加载关系。
- 解释daemon-reload与restart的区别,使用JSON解析、目标版本
dockerd --validate、status和journal定位语法错误与重复参数。 - 证明daemon日志默认值只影响新建容器,设计data-root迁移、地址池冲突治理、代理NO_PROXY、Registry CA和live-restore演练。
- 在不删除Docker Root Dir的前提下制造一次daemon启动失败,完成金丝雀、分层验证和配置回滚。
- 分别验证 hello-world、端口发布、自定义网络DNS、Volume持久化、资源限制、Buildx和Compose。
- 写出Docker Engine金丝雀升级、回滚和卸载保留数据的方案。
核心概念
| 概念 | 通俗理解 | 原理重点 |
|---|---|---|
| Image | 应用运行模板 | 只读分层文件系统 |
| Container | 镜像运行后的实例 | 宿主机上的隔离进程 |
| Layer | 镜像的一层 | 构建缓存和复用单位 |
| Registry | 镜像仓库 | 保存和分发镜像 |
| Volume | 数据卷 | 容器外持久化数据 |
| Network | 容器网络 | 容器之间和外部通信 |
镜像和容器怎么工作
flowchart TD
A["Dockerfile"] --> B["docker build"]
B --> C["镜像层"]
C --> D["只读 image"]
D --> E["docker run"]
E --> F["可写容器层"]
F --> G["容器进程"]
G --> H["namespace 隔离"]
G --> I["cgroup 限制资源"]容器不是一台完整虚拟机。容器里的 Java 进程本质上仍运行在宿主机内核上,只是 Docker 使用 namespace 隔离进程、网络、挂载点等视图,使用 cgroup 限制 CPU 和内存。
不这样会怎样
如果不用镜像而是在服务器手动安装环境:
- 每台服务器安装步骤可能不一致。
- JDK、字体、时区、系统库版本可能漂移。
- 回滚时不知道旧环境是什么样。
- 扩容新机器时重复手工操作,错误概率高。
Demo:Spring Boot JDK8 镜像
FROM eclipse-temurin:8-jre
WORKDIR /app
COPY target/order-api.jar /app/order-api.jar
ENV JAVA_OPTS="-Xms512m -Xmx512m -XX:+UseG1GC"
ENV SPRING_PROFILES_ACTIVE="prod"
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/order-api.jar"]解释:
| 配置 | 为什么 |
|---|---|
| 固定基础镜像 | 避免 latest 漂移 |
WORKDIR | 统一应用目录 |
JAVA_OPTS | JVM 参数由环境控制 |
SPRING_PROFILES_ACTIVE | 区分环境配置 |
| 日志输出标准输出 | 方便容器日志采集 |
常见坑
| 现象 | 原因 | 排查 |
|---|---|---|
| 容器立刻退出 | 启动命令错误或应用启动失败 | docker logs |
| 访问不到端口 | 没有 -p 映射或应用没监听 | docker ps、netstat |
| 容器内连不上 MySQL | 写了 localhost,实际指向容器自己 | 改成服务名或真实地址 |
| 数据丢失 | 没有挂载 volume | docker inspect |
| 磁盘打满 | 日志、镜像、临时文件堆积 | docker system df |
容器信息不能停在记住命令名。必须能够从 Docker容器信息与生产诊断 完成这些验收:
- 从
docker ps -a区分 Created、Running、Restarting、Exited 和健康状态。 - 从
inspect的 State、Config、HostConfig、NetworkSettings、Mounts 解释最终运行配置。 - 证明端口是 EXPOSE 还是实际 PortBindings,并确认应用监听地址。
- 区分 stdout/stderr 日志与容器内文件日志,说明日志驱动和轮转。
- 区分退出码137、OOMKilled、Java Heap OOM和容器总内存超限。
- 用 stats、top、events 和历史监控组成事故时间线。
Docker 命令不能靠复制速查表。必须通过 Docker命令从对象模型到生产操作 完成这些验收:
- 解释 context、image、container、network、volume、builder 和 Compose project 分别是什么对象。
- 从参数位置拆解
docker run,说明镜像名前后的选项为什么语义不同。 - 区分 Tag、Digest、Image ID,证明 restart 不会自动切换到本地新镜像。
- 分别演示 create/start/run、stop/kill/restart、exec/attach 的状态和进程差异。
- 使用 filter、format、label 精确定位目标,避免依赖截断 ID 或无条件批量操作。
- 使用 inspect、logs、stats、top、exec、cp、port、diff 和 events,并说明每条命令的证据边界。
- 区分 save/load 与 export/import,解释为什么后者不是完整镜像或数据库备份。
- 创建并检查自定义网络和命名卷,说明删除容器、镜像和 Volume 各自影响什么。
- 使用
system df -v分类空间,在清理前确认对象引用、业务归属、备份与恢复,不执行无条件全局 prune。
还必须通过 Docker容器运行底层原理 解释:
- Docker CLI、daemon、containerd、shim、runc 和 Linux 内核的调用链。
- namespace 负责隔离视图,cgroup 负责资源限制与统计。
- 镜像只读层、容器可写层、OverlayFS、Copy-on-Write 和 whiteout。
docker run从镜像解析到 Entrypoint 进程启动的完整步骤。- PID 1、Shell form、Exec form、SIGTERM、SIGKILL 和优雅停机。
- Capabilities、seccomp、非root、rootless、privileged 和 Docker Socket 风险。
- 老版本 JDK 8 与现代 JDK 对容器 CPU/内存感知的差异。
Java服务还必须通过 Spring Boot容器化运行原理 完成这些验收:
- 画出源码、Jar、镜像、容器、JVM、Spring ApplicationContext、内嵌Tomcat和流量接入全过程。
- 区分Spring Boot 2.7/JDK 8和Spring Boot 3/JDK 17版本边界,不能只替换基础镜像。
- 证明Exec form下JVM成为PID 1,并观察SIGTERM、Shutdown Hook和Spring优雅停机。
- 为容器总内存分别预算Heap、Metaspace、Direct Memory、线程栈、Code Cache和native,并解释Xmx为何不能等于限制。
- 在目标JDK 8镜像中检查完整8u版本、UseContainerSupport、Heap最终值和availableProcessors。
- 从Docker Env、Profile、外部配置、命令行参数和配置中心追踪Spring最终PropertySource。
- 区分liveness、readiness、startup和Docker health,说明错误探针如何制造重启风暴。
- 以非root运行,并验证日志、临时目录、外部配置、TrustStore和Dump挂载权限。
- 从容器、JVM、Tomcat、连接池和业务指标完成生产观测与故障定位。
- 完成先摘流、SIGTERM、等待在途请求、关闭消费者和容器退出的发布实验。
阶段三:Dockerfile 构建和优化
构建过程
flowchart TD
A["读取 Dockerfile"] --> B["拉取基础镜像"]
B --> C["执行指令生成层"]
C --> D["命中缓存则复用"]
D --> E["生成 image id"]
E --> F["打 tag"]
F --> G["推送 registry"]Dockerfile 的顺序会影响缓存。变化少的步骤放前面,变化多的代码复制放后面,可以减少重复构建。
生产写法原则
| 原则 | 原因 |
|---|---|
| 基础镜像固定版本 | 构建结果可复现 |
| 不把密码打进镜像 | 镜像可能被多人拉取 |
| 不使用 root 运行应用 | 降低容器逃逸后的风险 |
使用 .dockerignore | 减少上下文体积 |
| 镜像 tag 包含版本或 commit | 方便回滚和审计 |
| 设置 JVM 容器参数 | 避免容器内存限制下 OOM |
Demo:带非 root 用户的镜像
FROM eclipse-temurin:8-jre
RUN addgroup --system app && adduser --system --ingroup app app
WORKDIR /app
COPY target/order-api.jar /app/order-api.jar
RUN chown -R app:app /app
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/order-api.jar"]如果容器使用 root 运行,一旦应用存在文件写入漏洞或远程命令执行漏洞,攻击面会明显变大。
仅能写出上面的最小示例还不算掌握 Dockerfile。必须通过 Dockerfile 从构建原理到生产镜像 完成这些验收:
- 解释构建上下文为什么是构建输入边界,以及
.dockerignore如何同时影响性能、缓存和敏感信息风险。 - 区分文件系统层与镜像配置,说明内容摘要、父步骤和输入变化如何决定缓存命中。
- 从源码逐步推导
ENTRYPOINT、CMD与docker run参数的最终合并结果。 - 解释 Shell form、Exec form、PID 1、SIGTERM 和
exec入口脚本之间的关系。 - 使用多阶段构建分别生成 JDK 8 与 JDK 17 的运行镜像,说明为什么不能只考虑最终镜像体积。
- 解释为什么
ARG、ENV和“先复制后删除”都不能安全保存密码,并能使用 BuildKit Secret Mount。 - 以固定 UID/GID 的非 root 用户运行应用,并定位
COPY --chown、Volume 和宿主机权限问题。 - 验证具体 JDK 8 更新版本是否识别 cgroup 限制,说明为什么
Xmx不能等于容器内存上限。 - 从
docker history、image inspect、构建日志和运行时inspect定位镜像过大、缓存失效和启动失败。 - 说明镜像 tag、digest、commit 标签、SBOM、漏洞扫描和同一制品跨环境晋级的关系。
阶段四:Compose、网络、数据卷、日志
Compose 适合本地开发、联调、测试环境或者小规模单机环境。它能把 MySQL、Redis、应用服务放在同一个声明式文件里启动。
flowchart TD
A["Compose Project"] --> B["order-api服务"]
A --> C["mysql服务"]
A --> D["redis服务"]
B --> E["通过服务名mysql和redis访问"]
C --> F["Named Volume保存数据库数据"]
C --> G["Secret文件提供初始化密码"]
C --> H["Healthcheck表达初始化状态"]完整可运行示例不要使用明文root密码,也不要默认把数据库端口暴露到所有宿主机接口。请直接完成 Compose商业栈Demo 和 MySQL容器化Demo,其中包含Secret、健康检查、命名卷、资源限制和日志轮转。
关键原理:
- 同一个 Compose 网络中,服务名可以作为 DNS 名称。
depends_on只控制启动顺序,不保证依赖已经可用。- 数据库必须挂 volume,否则删除容器就会丢数据。
- 容器日志默认写到 Docker 管理的日志文件,需要设置日志轮转。
Compose 不能只会 up -d 和 down。必须通过 Docker Compose 从项目模型到商业编排 完成这些验收:
- 解释 Compose CLI 如何把变量插值、文件合并后的项目模型转换为 Docker Engine 的容器、网络和 Volume。
- 区分 Project、Service、Container、服务名 DNS 和项目资源前缀,说明改变项目名为什么会出现另一组数据卷。
- 分别演示
up、create、start、restart、run、exec、stop、down对对象和配置的影响。 - 区分项目
.env的 Compose 插值和 serviceenv_file的容器环境注入,并用config、inspect证明最终值。 - 使用 healthcheck 与
service_healthy控制初始依赖,同时解释应用为什么仍需连接超时、退避重试、熔断和降级。 - 解释
ports、expose、容器端口、宿主机端口以及服务内访问之间的关系。 - 使用命名卷、Bind Mount、external volume,并说明
down、down -v、项目名变化和挂载覆盖的后果。 - 使用多文件覆盖、Profile 和 Secret,解释最终配置合并与本地 Secret 的安全边界。
- 从
config、ps、logs、top、inspect、network inspect、volume inspect定位完整故障链路。 - 根据单机故障域、跨节点调度、滚动发布和存储需求判断 Compose 与 Kubernetes 的选型边界。
MySQL 作为有状态服务还必须通过 MySQL容器化与数据安全 完成这些验收:
- 画出Entrypoint对空数据目录的初始化、临时mysqld、账号创建、init脚本和正式mysqld过程。
- 证明MYSQL_ROOT_PASSWORD、MYSQL_DATABASE和init脚本不会对已有Volume重新执行。
- 使用Secret文件、普通业务账号、只读配置、命名卷和最小网络暴露,禁止privileged与明文弱密码。
- 从Buffer Pool、连接线程、sort/join Buffer、临时表和全局结构预算cgroup总内存。
- 解释Volume独立生命周期与备份的区别,以及为什么不能tar运行中的数据目录。
- 完成mysqldump一致性备份、隔离Volume恢复、校验、加密、保留和恢复耗时记录。
- 说明全量备份、binlog、PITR、RPO和RTO之间的关系。
- 制定MySQL补丁和大版本升级、兼容验证、未升级副本及失败回退方案。
- 定位Restarting、Access denied、Too many connections、OOMKilled、磁盘满和崩溃恢复慢。
Redis 作为内存型有状态服务还必须通过 Redis容器化与持久化安全 完成这些验收:
- 画出镜像Entrypoint、
redis-server、redis.conf、ACL文件、PID 1和Docker停止信号之间的启动与停止链路。 - 保持protected mode,通过内部网络和Redis 6+ ACL限制用户、Key Pattern及命令权限;密码使用Secret,不公开宿主机6379。
- 把
/data挂到命名卷,分别说明RDB、AOF everysec、混合持久化的数据丢失窗口、恢复过程和文件布局。 - 解释BGSAVE与AOF rewrite为什么需要fork,页表复制、Copy-on-Write、写入速率和磁盘IO如何造成内存峰值及延迟抖动。
- 区分
used_memory、used_memory_rss、maxmemory和cgroup内存上限,为allocator碎片、客户端/复制Buffer、Module和fork峰值预留空间。 - 检查宿主机overcommit、THP、文件描述符、磁盘空间和IO;说明哪些配置是宿主机全局配置,为什么不能靠privileged容器随意修改。
- 完成RDB/AOF一致备份、校验、加密、保留和空Volume隔离恢复,说明Volume为什么不等于备份。
- 比较单容器restart、主从、Sentinel和Cluster在副本、自动切换、分片、客户端和跨故障域方面的边界。
- 使用
INFO、SLOWLOG、LATENCY、commandstats、Docker stats/logs/inspect定位NOAUTH、NOPERM、OOM、持久化失败、数据丢失和延迟抖动。
Nginx作为容器入口还必须通过 Nginx容器化、反向代理与生产安全 完成这些验收:
- 画出Entrypoint、
daemon off、master/worker、PID 1、停止信号和Docker端口发布的完整链路。 - 使用只读配置和证书挂载,并用inspect Mounts、
nginx -t和nginx -T证明容器最终读取的内容。 - 解释localhost、Compose服务名、容器IP、宿主机端口和Docker DNS的边界,禁止写死后端容器IP。
- 证明
proxy_pass尾部斜杠对上游URI的影响,并正确传递Host、真实IP、协议和WebSocket Upgrade头。 - 区分reload、restart和recreate,验证新旧worker平滑过渡、长连接与优雅退出。
- 让access/error log进入stdout/stderr并配置日志轮转,说明挂载日志目录为什么可能遮住官方镜像符号链接。
- 设计非root、只读根文件系统、tmpfs和最小Capability方案,不使用privileged或
chmod 777掩盖权限错误。 - 按DNS、端口发布、Nginx监听、服务名解析、上游TCP、应用与依赖逐层定位403、404、502、504和TLS错误。
网络不能只会执行 docker network create。必须通过 Docker 网络底层原理 完成这些验收:
- 画出 Network Namespace、容器 eth0、veth pair、Linux bridge 和宿主机物理网卡的关系。
- 解释同网络服务名为什么能解析,以及默认 bridge 和自定义 bridge 在 DNS 方面的区别。
- 分别画出容器访问外网的 SNAT 路径和外部访问
-p发布端口的 DNAT 路径。 - 区分
Connection refused、超时、DNS 失败、TLS/HTTP 错误,说明每类错误处在哪一层。 - 解释
localhost、0.0.0.0、容器 IP、宿主机 IP、服务名各自表示什么。 - 能说明 MTU、iptables/nftables、host、macvlan、overlay 的适用边界,并完成最小抓包定位。
数据卷不能只记住 -v。必须通过 Docker 存储挂载与数据生命周期 完成这些验收:
- 区分镜像只读层、容器可写层、Named Volume、Bind Mount 和 tmpfs 的生命周期。
- 解释 OverlayFS 可写层为什么不适合持久数据库,以及挂载为什么会遮住镜像内原文件。
- 能定位 UID/GID、只读挂载、SELinux 标签和 Docker Desktop 路径造成的权限问题。
- 解释
docker compose down、down -v、volume rm、volume prune对数据的不同影响。 - 说明 Volume 为什么不是备份,为什么不能随意复制运行中数据库数据目录。
- 根据 RPO/RTO 设计逻辑备份、独立副本、保留策略、校验和恢复演练。
阶段五:Nginx 入口层
在学习配置指令前,必须先完成 Nginx进程模型、事件循环与高并发原理:能够解释Master与Worker、非阻塞socket、epoll就绪事件、连接状态机、客户端与upstream双连接、FD限制、keep-alive、Buffer和reload。只会背“异步非阻塞”不算通过。
配置阶段必须完成 Nginx配置解析、虚拟主机与请求匹配全过程:能够从listen、SNI、Host、server_name一路推导到规范化URI和location,分别验证rewrite last/break、root/alias、try_files内部重定向、proxy_pass URI替换、可信代理Header和404/403/502/504排查。
负载均衡阶段必须完成 Nginx负载均衡、健康判断与重试全过程:能够比较加权轮询、least_conn、ip_hash和consistent hash,区分被动失败与主动健康,解释proxy_next_upstream、非幂等重复执行、每Worker keepalive、DNS更新、慢节点P99和重试风暴。
限流阶段必须完成 Nginx限流、排队与拒绝全过程:能够逐请求推导rate、burst、nodelay和delay时间线,区分limit_req与limit_conn,解释可信真实IP、NAT误伤、zone容量、多个Nginx实例额度放大、全局限流和登录/短信/搜索/上传策略。
可观测性阶段必须完成 Nginx日志、分段耗时与生产故障取证:能够用Request ID和多值upstream变量还原重试链,区分request/connect/header/response时间,定位499/502/504、CPU、连接、FD、磁盘和TLS问题。
缓存阶段必须完成 Nginx代理缓存、过期与一致性全过程:能够设计安全cache key,区分TTL与inactive、HIT/MISS/STALE/BYPASS,解释cache lock、stale、多实例冷缓存、磁盘容量和用户数据串读风险。
Nginx 做什么
Nginx 常见职责:
- 反向代理:隐藏后端真实地址。
- 负载均衡:把流量分给多个实例。
- 静态资源:直接返回 HTML、JS、CSS、图片。
- HTTPS 终止:处理证书和 TLS 握手。
- 限流:保护后端服务。
- 缓存:降低后端压力。
- 真实 IP 透传:让后端知道客户端来源。
请求转发流程
flowchart TD
A["用户请求域名"] --> B["DNS 解析到入口 IP"]
B --> C["Nginx 接收连接"]
C --> D["匹配 server_name"]
D --> E["匹配 location"]
E --> F{"静态还是代理"}
F -- "静态资源" --> G["读取本地文件"]
F -- "API" --> H["选择 upstream"]
H --> I["转发后端服务"]
I --> J["返回响应给用户"]Demo:反向代理和负载均衡
upstream order_api {
least_conn;
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;
}
}502 和 504 怎么区分
| 状态 | 含义 | 常见原因 |
|---|---|---|
| 502 | Nginx 无法从后端拿到正常响应 | 后端挂了、端口不通、协议错误、连接被拒绝 |
| 504 | Nginx 等后端响应超时 | 后端慢 SQL、线程池满、外部接口慢、超时配置太短 |
排查时先看 Nginx error log,再 curl upstream 地址,再看后端日志和依赖。
阶段六:Jenkins Pipeline
在编写Jenkinsfile前,先完成 Jenkins Controller、Agent与构建执行全过程:能够从Webhook推导到Queue、Label、Node、Agent、Executor和Workspace,解释workspace@2、JDK运行时与构建工具链、stash/archive/制品仓库、JENKINS_HOME和安全隔离。
Pipeline阶段必须完成 Jenkins Pipeline CPS、并发与可靠发布全过程:能够解释CPS、Continuation、暂停点、Controller重启恢复、@NonCPS与序列化,正确使用agent none、input、timeout、retry、parallel、matrix、milestone、lock和Credentials,并推广同一镜像Digest。
生产发布阶段必须完成 Jenkins商业发布、灰度与回滚全过程:能够追踪Commit、Digest、配置和数据库版本,使用Expand/Migrate/Contract和消息兼容支持回滚,设计滚动/蓝绿/金丝雀、版本技术与业务指标、自动停止、多对象回滚、前滚和副作用补偿。
共享库阶段必须完成 Jenkins Shared Library加载、信任与版本治理:能够解释 vars、src、resources 的加载边界,区分 @Library 与运行期 library,说明 Trusted/Untrusted、CPS、无状态API和Shell注入原理,并能设计语义版本、测试金字塔、金丝雀升级与回退流程。
Jenkins 解决什么
Jenkins 把发布步骤从“人工点击和手工命令”变成“代码化流水线”。流水线的价值不只是自动化,更重要的是可审计、可重复、可回滚。
Pipeline 执行流程
flowchart TD
A["触发构建"] --> B["拉取代码"]
B --> C["编译"]
C --> D["单元测试"]
D --> E["打包制品"]
E --> F["构建镜像"]
F --> G["推送镜像仓库"]
G --> H["部署目标环境"]
H --> I["健康检查"]
I --> J{"是否成功"}
J -- "成功" --> K["通知完成"]
J -- "失败" --> L["停止发布或回滚"]Demo:Jenkinsfile
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'
}
}
}
}凭据和制品
| 内容 | 正确做法 | 错误做法 |
|---|---|---|
| 镜像仓库密码 | Jenkins Credentials | 写在 Jenkinsfile |
| SSH 私钥 | 凭据绑定 | 放代码仓库 |
| jar 包 | archiveArtifacts 归档 | 构建完就丢 |
| 镜像 tag | 构建号 + commit | 永远 latest |
| 发布日志 | 保留流水线日志 | 人工命令无记录 |
阶段七:Kubernetes 核心对象
零基础学习者应先完成 Kubernetes从零学习路线与第一个应用:能够从镜像、容器、Pod一路解释到Deployment、Service和Ingress;能够部署最小应用,沿着API Server、Controller、Scheduler、kubelet、CRI和CNI说明创建过程;能够用OwnerReference、EndpointSlice、Condition、Event和日志证明每个环节,而不是只看到Running就认定上线成功。
工作负载阶段必须完成 Kubernetes工作负载、滚动更新与任务调度:能够从Deployment追踪到新旧ReplicaSet和Pod,计算maxSurge/maxUnavailable,解释Readiness、minReadySeconds、ProgressDeadline和优雅终止;能够区分StatefulSet、DaemonSet、Job、CronJob的控制语义,并设计批任务幂等、重试、Deadline与错过调度处理。
网络阶段必须完成 Kubernetes Pod网络、Service、DNS与NetworkPolicy:能够解释CNI如何建立Pod网络,追踪Selector到EndpointSlice,区分port/targetPort/nodePort,说明ClusterIP、iptables/IPVS/eBPF、Conntrack和长连接负载边界;能够排查CoreDNS/ndots/缓存,设计双向最小NetworkPolicy并定位同节点、跨节点和MTU故障。
七层入口阶段必须完成 Kubernetes Ingress、TLS与七层流量排障:能够区分Ingress、IngressClass、Controller、Controller Service和外部LB,解释Reconcile、Host/SNI/PathType、Rewrite和TLS终止;能够从真实握手、Controller上游日志、EndpointSlice和Trace区分404、502、503、504,并治理可信源IP、危险Annotation和多租户Host冲突。
配置与密钥阶段必须完成 Kubernetes ConfigMap、Secret与配置轮换全过程:能够解释API到etcd、kubelet和容器的注入链路,区分env快照、Atomic Writer投射卷和subPath,设计版本化配置与Checksum发布;能够说明Base64/静态加密/KMS边界、审计间接Secret权限,并完成数据库凭据和TLS证书零停机轮换。
探针阶段必须完成 Kubernetes Startup、Readiness、Liveness探针全过程:能够解释kubelet执行和阈值状态机,说明Startup门控、Readiness到EndpointSlice传播和Liveness容器重启;能够比较HTTP/TCP/gRPC/Exec边界,为Spring Boot设计不依赖共享数据库的健康契约,并排查误杀、重启风暴、全部摘流和假健康。
存储阶段必须完成 Kubernetes PV、PVC、StorageClass与CSI存储全过程:能够解释静态/动态供应、Immediate/WFFC拓扑调度和CSI Create/Attach/Stage/Publish,准确区分RWO/RWX/RWOP与Filesystem/Block;能够治理Reclaim、扩容、快照/备份/PITR,并排查PVC Pending、Multi-Attach、FailedAttach、FailedMount和Node故障恢复。
Helm阶段必须完成 Helm Chart、模板渲染与生产发布全过程:能够解释Chart/Release/Revision、Values优先级与Map/List合并、Go Template作用域和Release Secret;能够区分wait/atomic/cleanup/force,治理Hook数据库迁移、CRD特殊生命周期、依赖锁和OCI供应链,并排查pending、ownership、Hook和Rollback失败。
对象关系
flowchart TD
A["Deployment"] --> B["ReplicaSet"]
B --> C["Pod 副本"]
D["Service"] --> C
E["Ingress"] --> D
F["ConfigMap"] --> C
G["Secret"] --> C
H["Volume"] --> C
I["Probe"] --> C每个对象解决什么
| 对象 | 作用 | 不理解会怎样 |
|---|---|---|
| Pod | 最小调度单位 | 误以为容器直接被 Service 管 |
| Deployment | 声明期望副本和发布策略 | 手动创建 Pod 无法滚动发布 |
| ReplicaSet | 维持某版本副本数 | 不知道旧版本为什么还存在 |
| Service | 稳定服务发现和负载均衡 | Pod IP 变化后调用失败 |
| Ingress | 集群外 HTTP 入口 | 外部流量不知道怎么进集群 |
| ConfigMap | 普通配置 | 配置硬编码进镜像 |
| Secret | 敏感配置 | 密码散落在代码和镜像 |
| Probe | 健康检查 | 不健康实例继续接流量 |
| Volume | 持久化或共享文件 | Pod 重建后数据丢失 |
Demo:Deployment + Service
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
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阶段八:配置、密钥、存储、探针
ConfigMap 和 Secret
ConfigMap 保存普通配置,Secret 保存敏感配置。Secret 默认只是 base64 编码,不等于绝对安全,生产还要结合 RBAC、etcd 加密、密钥轮换和审计。
apiVersion: v1
kind: ConfigMap
metadata:
name: order-api-config
data:
SPRING_PROFILES_ACTIVE: "prod"
LOG_LEVEL: "INFO"
---
apiVersion: v1
kind: Secret
metadata:
name: order-api-secret
type: Opaque
stringData:
SPRING_DATASOURCE_PASSWORD: "change-me"readiness 和 liveness
flowchart TD
A["Pod 启动"] --> B["容器进程启动"]
B --> C["liveness 检查"]
B --> D["readiness 检查"]
C -- "失败" --> E["重启容器"]
D -- "失败" --> F["从 Service endpoints 移除"]
D -- "成功" --> G["接收流量"]| 探针 | 代表什么 | 失败后果 |
|---|---|---|
| readinessProbe | 是否准备好接流量 | Service 不转发给它 |
| livenessProbe | 进程是否还活着 | kubelet 重启容器 |
| startupProbe | 慢启动保护 | 成功前不执行其他探针 |
慢启动服务应该使用 startupProbe 或把 liveness 初始延迟调大,否则应用还在初始化就会被反复杀掉。
阶段九:灰度发布、回滚、扩缩容
滚动发布流程
flowchart TD
A["创建新 ReplicaSet"] --> B["启动少量新 Pod"]
B --> C["新 Pod readiness 通过"]
C --> D["减少旧 Pod"]
D --> E["继续替换"]
E --> F{"全部完成"}
F -- "是" --> G["发布成功"]
F -- "异常" --> H["暂停或回滚"]回滚为什么不是万能的
代码能回滚,不代表系统一定能回滚。真正的回滚条件包括:
- 旧镜像仍在镜像仓库。
- 旧配置仍可用。
- 数据库表结构兼容旧代码。
- MQ 消息格式兼容旧消费者。
- 前端静态资源和后端接口兼容。
- 外部接口协议没有单向变更。
扩缩容不是万能药
接口慢时不能立刻扩容,要先判断瓶颈:
| 瓶颈 | 盲目扩容后果 |
|---|---|
| 数据库慢 SQL | 更多实例产生更多 DB 压力 |
| Redis 热 key | 更多实例同时打同一个 key |
| MQ 消费慢 | 消费者扩容受分区数或顺序性限制 |
| 外部接口慢 | 更多并发打爆外部接口 |
| 线程池配置小 | 实例增多前单实例仍卡住 |
阶段十:可观测性和生产排查
三类证据
| 类型 | 回答什么问题 | 典型工具 |
|---|---|---|
| 日志 | 某次请求具体发生了什么 | ELK、Loki |
| 指标 | 系统整体是否健康 | Prometheus、Grafana |
| 链路追踪 | 请求经过哪些服务 | SkyWalking、Jaeger、Zipkin |
flowchart TD
A["用户请求"] --> B["网关生成 traceId"]
B --> C["订单服务"]
C --> D["库存服务"]
C --> E["支付服务"]
C --> F["日志、指标、链路上报"]
F --> G["告警和看板"]故障排查总流程
flowchart TD
A["线上异常"] --> B{"表现是什么"}
B -- "访问不了" --> C["DNS、Nginx、Ingress"]
B -- "接口 5xx" --> D["应用日志、异常栈"]
B -- "变慢" --> E["P99、线程池、DB、Redis、MQ"]
B -- "Pod 异常" --> F["describe、events、logs"]
B -- "发布失败" --> G["流水线、镜像、权限、探针"]
C --> H["确认请求到达哪一层"]
D --> I["用 traceId 串完整链路"]
E --> J["定位瓶颈再决定扩容或回滚"]
F --> K["区分调度、拉镜像、启动、探针"]
G --> L["停止发布并保留现场"]常用命令
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 status deployment/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 ps -a
docker logs order-api --tail=200
docker inspect order-api
docker exec -it order-api sh
docker system dfDocker 排障不能停在记住以上命令。必须通过 Docker生产故障排查Runbook 完成这些验收:
- 先区分容器运行中、已退出/重启中、已删除/宿主机失联,说明每种现场还能获得哪些证据。
- 在任何重启、重建和清理前,保存容器State、时间戳日志、events、镜像身份、端口、网络、Mounts和资源限制。
- 从Docker context、daemon、镜像、创建、主进程、网络、存储、应用到依赖逐层缩小故障域。
- 对Created、Running、Restarting、Exited、Unhealthy分别给出下一步检查和判定依据。
- 从客户端、宿主机防火墙、端口发布、容器IP、监听地址到HTTP业务完成访问失败定位。
- 按目标配置、服务名DNS、共同网络、TCP、TLS、认证和协议完成依赖连接失败定位。
- 区分Exit 137、OOMKilled、Java Heap OOM、容器总内存OOM和宿主机OOM,并说明信息从哪里获取。
- 从CPU quota/throttling、JVM线程、GC、流量和重试风暴定位CPU高与接口慢。
- 使用
df -h、df -i、system df -v、LogConfig和deleted-open文件定位磁盘占满,禁止盲目全局prune。 - 从最终Mounts、数字UID/GID、只读rootfs、SELinux和项目名变化定位权限与数据问题。
- 写出包含业务影响、时间线、直接原因、根因、触发条件、扩大因素、长期修复和验证的事故复盘,不能以“重启恢复”结案。
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商业常用场景
场景一:订单系统发布
订单系统要关注下单、库存、支付、MQ 和状态机。
发布前:
- 检查数据库变更是否兼容旧代码。
- 检查 MQ 消息字段是否兼容旧消费者。
- 检查支付回调地址是否保持稳定。
- 确认限流、熔断、线程池、连接池配置。
- 准备下单成功率、支付成功率、订单卡状态数量看板。
发布后:
- 观察接口错误率和 P99。
- 观察 MQ 堆积和消费失败。
- 观察数据库慢 SQL。
- 观察支付回调失败率。
- 异常时停止放量或回滚。
场景二:医疗数据采集平台发布
医疗数据采集平台常见链路是设备接入、数据清洗、异步入库、文件归档、ES 检索、资产管理。
发布策略:
- 采集网关滚动发布,避免所有设备同时断连。
- 正在执行的采集任务要能恢复或补偿。
- 采集配置变更必须审计。
- MQ 堆积、失败批次、ES 同步延迟要告警。
- 原始报文和清洗结果要能追踪。
场景三:搜索服务发布
搜索服务通常涉及 MySQL、MQ、ES、缓存。
重点:
- 索引 Mapping 变更要用新索引和别名切换。
- ES 同步失败要有补偿任务。
- 查询接口要观察 P99 和慢查询。
- 发布前要确认新旧索引兼容。
- 回滚时要确认别名指向和代码版本一致。
面试标准回答
DevOps 是什么
DevOps 是把开发、测试、构建、制品、部署、运行、监控、告警和反馈连接起来的工程体系。它的目标不是单纯自动发布,而是让软件交付可重复、可追踪、可回滚、可观测,减少环境差异和人工操作风险。代码到上线全过程
开发提交代码到 Git 后,CI 流水线拉取指定 commit,执行编译、测试和打包,生成 jar 或 dist。然后构建 Docker 镜像并打上构建号和 commit tag,推送到镜像仓库。CD 阶段更新 Kubernetes Deployment,新 Pod 拉取镜像启动,通过 readinessProbe 后加入 Service,再由 Ingress 或 Nginx 对外暴露。上线后观察日志、指标、链路追踪和业务指标,异常时停止放量或回滚。Docker 镜像和容器区别
镜像是只读模板,由多层文件系统组成;容器是镜像运行后的进程实例,会在只读镜像层之上增加可写层。一个镜像可以启动多个容器。容器不是完整虚拟机,它共享宿主机内核,通过 namespace 做隔离,通过 cgroup 做资源限制。Nginx 502 和 504 区别
502 通常表示 Nginx 连接后端失败或后端返回了异常网关响应,常见原因是后端挂了、端口不通、upstream 配错。504 表示 Nginx 等待后端响应超时,常见原因是后端慢 SQL、线程池耗尽、外部接口慢或超时配置不合理。排查时先看 Nginx error log,再验证 upstream,再看后端日志和依赖。Kubernetes 中 Pod、Deployment、Service、Ingress 的关系
Pod 是最小调度单位,应用容器运行在 Pod 中;Deployment 声明期望副本数和发布策略,通过 ReplicaSet 维持 Pod 副本;Service 通过标签选择一组 Pod,提供稳定访问入口和负载均衡;Ingress 把集群外 HTTP 请求路由到 Service。用户请求通常经过 Ingress,再到 Service,最后转发到 Ready 的 Pod。readinessProbe 和 livenessProbe 区别
readinessProbe 表示实例是否准备好接流量,失败时 Service 不再把请求转发给它;livenessProbe 表示进程是否还活着,失败时 kubelet 会重启容器。慢启动服务如果 liveness 配太激进,会出现还没启动完成就被反复杀掉的 CrashLoopBackOff。学懂验收问题
如果下面问题答不上来,说明还没有真正学懂:
- 为什么生产发布不能只上传 jar?
- 镜像为什么要固定 tag,不能一直用 latest?
- 容器为什么不是虚拟机?
- 为什么容器内访问宿主机的
localhost经常不对? - Nginx 502 和 504 分别从哪几层排查?
- Jenkins Credentials 为什么不能用环境变量明文替代?
- Pod Ready 但服务仍访问失败有哪些原因?
- readinessProbe 和 livenessProbe 配错会造成什么事故?
- 为什么数据库变更可能让代码回滚失效?
- 接口变慢时为什么不能立刻扩容?
