容器与 CI/CD:让「在我电脑上能跑」不再是问题
讲清 Docker 镜像与容器如何保证环境一致、Dockerfile 怎么分层、compose 如何编排多服务,以及持续集成/交付/部署流水线如何把构建测试发布自动化,让每次上线都可重复、可回滚
- 理解容器解决的根本问题,分清镜像、容器、仓库三个概念
- 看懂 Dockerfile 的分层思想与常见指令,知道镜像为什么要瘦身
- 会用 docker-compose 编排「应用+数据库+缓存」这类多服务
- 理解 CI/CD 流水线各阶段,能设计一条从提交到部署的自动化链路
手工部署的尽头是自动化
当项目只有一个、部署几个月一次,手动 scp、手动重启还能应付;一旦服务变多、发布变频繁,「在我电脑上是好的、一上服务器就缺依赖」「上次是怎么发的来着」会反复发生。容器和 CI/CD 就是为解决「环境一致」和「发布可重复」而生的。这一课讲清原理与最小实践,你不一定要立刻全用,但必须看懂现代团队的部署语言。
容器:把应用和它的整个运行环境打包在一起
虚拟机虚拟的是整台机器(含完整操作系统,重、启动慢);容器共享宿主机内核,只把应用和它依赖的库、运行时、配置打包,进程级隔离,秒级启动、体积小。核心三概念:镜像(Image,只读模板,含运行所需一切)、容器(Container,镜像跑起来的实例,可起多个)、仓库(Registry,存放和分发镜像,如公有镜像仓库或自建)。一次构建成镜像,在哪台机器跑都一致,这就是「不可变基础设施」——不靠手动登录服务器改环境,而是重新构建、整体替换。
Dockerfile:分层构建与缓存
# 一个 Node 后端的精简 Dockerfile(示意)
FROM node:20-slim # 基础镜像
WORKDIR /app
COPY package*.json ./ # 先拷依赖清单
RUN npm ci --omit=dev # 装依赖(这层有缓存,依赖没变就不重跑)
COPY . . # 再拷源码(源码常变,放后面提高缓存命中)
EXPOSE 5180
USER node # 不用 root 运行,更安全
CMD ["node", "server.js"] # 容器启动命令Dockerfile 每条指令生成一层,层可缓存。最佳实践是「变化少的放前面、变化频繁的放后面」:依赖清单比源码稳定,先 COPY 依赖、装依赖,再拷源码,改代码时依赖层直接命中缓存、构建飞快。生产镜像尽量小(用 slim/alpine 基础镜像、多阶段构建丢弃编译期依赖)、用非 root 用户运行、不要把密钥烤进镜像(运行时用环境变量注入,呼应 Stage15-m7)。
前端项目常在一个阶段装全依赖、编译构建,再把产物拷到一个干净的运行阶段,最终镜像不含 node_modules 里的构建工具,体积小、攻击面小。朋友好学目前是在本地构建 Next.js 产物再上传服务器,本质思想一致——构建环境和运行环境分离,服务器不必扛构建。
docker-compose:一台机器编排多服务
真实应用往往是多个进程协作:Web 后端 + 数据库 + 缓存 + 反向代理。docker-compose 用一个 yaml 声明这些服务、镜像、端口、环境变量、数据卷和它们之间的依赖,一条命令全部拉起。
# docker-compose.yml(示意)
services:
db:
image: postgres:16
volumes: [ "dbdata:/var/lib/postgresql/data" ] # 数据持久化到卷
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # 密钥用环境变量,不写死
app:
build: .
ports: [ "127.0.0.1:5180:5180" ] # 只绑本机,交给前面的Nginx
depends_on: [ db ]
volumes:
dbdata:容器是可随时销毁重建的,写在容器内部的数据一重建就没。数据库、上传文件等必须挂载到「数据卷(volume)」或宿主机目录持久化,并且备份的是这些卷,而不是容器本身。记住:容器无状态、状态进卷/外部数据库。
CI/CD:把构建、测试、发布变成自动流水线
CI(持续集成):开发者一提交代码,就自动跑检查(lint、类型检查、自动化测试),尽早发现问题;CD(持续交付/部署):自动构建出可发布制品,并进一步自动部署到测试或生产。一条典型流水线:
代码推送到仓库
-> ① 安装依赖、lint、类型检查(tsc --noEmit)
-> ② 跑自动化测试
-> ③ 构建产物(Next build / 打镜像 / 打 APK)
-> ④ 产出「制品」并打版本号(镜像/压缩包/安装包,不可变、可追溯)
-> ⑤ 推到制品库/镜像仓库
-> ⑥ 按策略部署:测试环境自动、生产手动确认或灰度
-> ⑦ 部署后健康检查,失败自动回滚关键理念:制品只构建一次、在各环境间流转(靠环境变量区分环境),而不是每个环境重新构建;每次发布都有唯一版本号和对应制品,出问题能精确回滚到任一历史版本。朋友好学现在的「本地构建→打包→上传→服务器脚本切换版本目录」其实就是一条半手工的 CD 流水线(用时间戳版本目录 + current 软链接实现原子切换与回滚),未来可以把它接到代码仓库的自动化流水线里,做到一键甚至提交即发布。
独立开发者的渐进式自动化路线(选学)
不必一步到位上全套云原生,按收益分阶段:先写一个能重复执行的部署脚本,消灭「凭记忆手敲命令」;再把类型检查、构建、打包脚本化并在本地一键跑;接入代码仓库的 CI,让每次提交自动 tsc/build;最后才做自动部署与容器化。每一步都让你少一类手工错误。自动化的目标不是炫技,是让发布变得无聊——无聊到不会出错。
下列关于 Docker 镜像分层缓存的 Dockerfile 编写方式,构建效率更高的是?
本节小结
容器共享主机内核、把应用与运行环境打包,镜像=只读模板、容器=运行实例、仓库=分发处,实现一次构建到处运行的不可变基础设施。Dockerfile 分层构建、变化少的放前面利用缓存、slim/多阶段瘦身、非root运行、密钥运行时注入不烤进镜像;compose 用 yaml 编排多服务、数据必须挂卷持久化。CI 自动 lint/tsc/test,CD 自动构建制品、打不可变版本、推库、按策略部署并健康检查/回滚;制品只构建一次靠环境变量流转。独立开发者可从可重复部署脚本→本地一键构建→CI→自动部署/容器化渐进。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
把会话、上传文件、定时任务状态从应用进程里挪到 Redis、对象存储或数据库后,任何一台应用实例都能处理任何请求,扩容就是多开几个实例挂到负载均衡后面。反过来,只要状态留在某台机器的内存里,它就既无法横向扩容,也无法滚动更新(一重启用户就掉线)。面试答「如何支撑更高并发」,先讲无状态化,再谈加机器和缓存。