DK-06 部署实践
06 · 部署实践(Deployment Practice)
📅 预计 60 分钟 | ⭐ = 重要知识点 | 📌 中英术语见文末
🛠 本讲示例命令可直接运行,建议结合前五讲反复验证
6.0 先给直觉:从"会跑"到"跑得稳"
前面五讲解决了"怎么把容器跑起来"。真实环境里,容器能不能稳定、安全、可维护地跑,才是重点。这一讲把部署时的四个基本功串起来:
- 环境变量:配置和代码分离,不改代码也能换配置。
- 健康检查:让编排系统知道容器"真的能用"还是"只是活着"。
- 日志管理:出了事查得到,不把磁盘撑爆。
- 镜像优化:小、安全、好维护。
💡 记忆口诀:配置走环境变量,就绪靠健康检查,排错看日志,上线挑小镜像。
6.1 环境变量:配置与代码分离 ⭐
为什么要环境变量
把密码、数据库地址、端口写死在代码或镜像里,会导致两个问题:不安全(镜像是要分发的)和不灵活(换环境就得改代码重建)。
正确做法:代码里只读取环境变量,具体值由运行时注入。
# docker run 直接传单个变量
docker run -d -e DB_HOST=10.0.0.1 -e DB_PASSWORD=secret123 --name app myapp:1.0
# 从文件批量读取变量
docker run -d --env-file .env --name app myapp:1.0
.env 文件格式是简单的 KEY=value 每行一个:
DB_HOST=10.0.0.1
DB_PASSWORD=secret123
APP_PORT=3000
Compose 里的两种注入方式(第 5 讲提过):
services:
app:
image: myapp:1.0
environment: # 直接写,适合少量、非敏感
APP_PORT: "3000"
env_file: # 从文件读,适合多变量
- .env
敏感信息怎么处理 ⭐
- 不要把密钥写进镜像:镜像会被分发到仓库,等于公开。
- 不要写进
.env提交到仓库:.env要进.gitignore。 - 生产环境用 Docker 的 secret 机制、Compose 的
secrets,或平台的密钥管理服务(如云厂商的 Secret 服务)。 - 数据库密码这类值,交付时用环境变量注入,不给默认弱密码。
ENV 与 ARG 的边界(回顾第 4 讲)
ARG只在构建时有效,不会进入运行中的容器。ENV写入镜像,运行时容器里能直接读到。- 需要运行时读取的配置,用
ENV或运行时-e注入,别用ARG。
⚠️ 常见错误
- 把密码写死在 Dockerfile 或源码里:镜像公开 = 密码公开,这是最典型的安全事故来源。
.env被提交进 Git:忘记加.gitignore,密钥跟着仓库泄露。- 想在运行时读
ARG的值:构建参数不进运行时,要用ENV或运行时注入。
6.2 健康检查:让系统知道容器"能用" ⭐
为什么需要健康检查
容器"running"不代表服务"能用"——进程还在,但数据库连接池满了、端口没监听,照样是坏的。编排系统、负载均衡器需要一种机制,知道容器真的就绪。
HEALTHCHECK 指令在 Dockerfile 里声明检查方式:
FROM nginx:1.25
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
含义:每 30 秒执行一次 curl -f http://localhost/,成功返回 0 表示健康,连续 3 次失败标记为 unhealthy。
在 Compose 里也能写:
services:
web:
image: nginx:1.25
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 30s
timeout: 5s
retries: 3
查看健康状态:
docker inspect --format='{{.State.Health.Status}}' web
# 输出:healthy / unhealthy / starting
健康检查的实战价值
- 编排依赖就绪:
depends_on+condition: service_healthy能让 web 等数据库真正就绪再启动,弥补第 5 讲说的"depends_on 只管顺序"的短板。 - 负载均衡摘除:反向代理(如 Nginx、负载均衡器)发现节点
unhealthy就不再转发流量给它。 - 自动重启:配合
restart策略,坏掉的容器能被拉起。
Compose 里配合 depends_on 精确控制启动时机:
services:
web:
image: myapp:1.0
depends_on:
db:
condition: service_healthy # db 健康后才启动 web
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
retries: 5
这样 web 启动时,db 已经真正可连,不再有"顺序对了但连接被拒"的启动竞态。
⚠️ 常见错误
- 检查路径写错:探活地址必须真实反映服务状态,写一个永远返回 200 的路径,等于没检查。
- 只检查进程存活:
test里CMD true之类,容器永远"健康",失去了意义。 - 基础镜像没有
curl:alpine默认不带 curl,HEALTHCHECK 里用它先装好,或用wget。
6.3 日志管理:出了事查得到 ⭐
默认日志行为
容器标准输出(stdout/stderr)就是它的"日志"。默认日志驱动 json-file 会把日志按 JSON 格式存到宿主磁盘,用 docker logs 查看:
docker logs web # 查看全部日志
docker logs -f web # 跟随输出,实时排错
docker logs --tail 100 web # 只看最近 100 行
应用要遵守一条约定:日志写标准输出,别写文件。这样容器重启、日志驱动更换时,日志都不会丢,还能被集中收集。
限制日志大小:防止撑爆磁盘 ⭐
默认情况下 json-file 日志不设上限,一个疯狂打日志的容器几小时就能写满磁盘。部署时必须加限制:
docker run -d \
--log-opt max-size=10m \
--log-opt max-file=3 \
--name web nginx
意思:单个日志文件最大 10MB,保留最近 3 个文件,自动轮转。
Compose 里同样配置:
services:
web:
image: nginx:1.25
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
日志驱动
除默认 json-file 外,常见驱动还有 syslog(送系统日志)、journald(送 systemd)、gelf(送 Graylog 等集中日志平台)。容器本身不存日志,交给驱动的设计,是为了日志可被聚合、检索。
⚠️ 常见错误
- 日志写文件:应用把日志写进容器内文件,容器一删日志全丢,
docker logs也看不到。 - 不限制日志大小:生产事故一大类就是"磁盘被日志写满"。上线就配
max-size/max-file。 - 排错不看日志:容器启动即退出,先
docker logs再调配置,别靠猜。
6.4 镜像优化:小、安全、好维护
从体积入手
- 选小基础镜像:
alpine(几 MB)或distroless(无 shell,更安全)优先,对比 Ubuntu 的几百 MB,传输、启动都更快。 - 多阶段构建(第 4 讲):只把产物拷进最终镜像,把编译器、源码留在构建阶段。
- 合并
RUN:&&连接多条命令,减少层数。 .dockerignore:排除node_modules、.git、测试数据等,缩小构建上下文。
从安全入手 ⭐
- 别用
root跑应用:用USER切到普通用户,降低被入侵后的影响范围。
FROM node:18-alpine
USER node # 以非 root 身份运行
WORKDIR /app
COPY --chown=node:node . .
CMD ["node", "index.js"]
- 用具体版本标签:
FROM node:18-alpine好过FROM node:latest,锁定可复现。 - 定期更新基础镜像:基础镜像的漏洞修复要跟上,别一直用一年前的旧镜像。
- 最小化安装:只装运行需要的东西,少一个包少一个漏洞入口。
⚠️ 常见错误
- 全程用
root跑:容器里的 root 等同宿主机的高权限入口,被攻破后果严重。 - 用
latest标签当生产基础:无法复现,哪天latest变了行为就变了。 - 忽略
.dockerignore:把密钥、.git、依赖缓存拷进镜像,又大又危险。
6.5 常见坑与排查思路
高频翻车点
| 现象 | 常见根因 | 对策 |
|---|---|---|
| 容器秒退 | 主进程崩溃,看 docker logs |
先看日志再调配置 |
| 端口冲突 | 宿主端口已被占用 | docker ps 查占用,换端口 |
| 数据丢失 | 没挂卷就 rm 容器 |
重要数据先建卷再跑 |
| 内存被杀 | 容器内存超限 OOM | docker stats 观察,调 --memory |
| 连不上宿主机服务 | 容器内 localhost 是容器自己 |
用宿主机 IP 或 host-gateway |
| 时区不对 | 容器默认 UTC | 注入 TZ=Asia/Shanghai |
| 日志撑满磁盘 | 日志无上限 | --log-opt max-size 限大小 |
| 启动顺序竞态 | depends_on 只管顺序 | 健康检查 + service_healthy |
| 卷数据丢失 | 忘挂卷就删容器 | 先建卷再跑,定期备份 |
排查三板斧(回顾第 2 讲)
docker ps -a # ① 看状态:running?exited?重启中?
docker logs -f <id> # ② 看日志:报什么错
docker inspect <id> # ③ 看配置:环境变量、挂载、网络对不对
docker stats # 补充:看 CPU/内存是否异常
两个容易忽略的细节
- 容器内访问宿主机服务:容器里的
localhost指容器自己,不是宿主机。访问宿主机服务要用host.docker.internal(Docker Desktop 支持)或--network host。 - 时区:容器默认用 UTC,日志时间和本地对不上时,
-e TZ=Asia/Shanghai注入时区。
从开发到上线的完整流程
把前面所有知识点串成一条标准流程,照着走不会漏环节:
- 写 Dockerfile:小基础镜像、非 root 用户、多阶段构建(第 4、6 讲)。
- 本地构建验证:
docker build -t myapp:1.0 .→docker run -p 3000:3000 myapp:1.0→ 本地跑通。 - 配置与敏感信息分离:代码读环境变量,密钥用 secret / 密钥服务(6.1)。
- 编排整组服务:写
compose.yaml,定义 web + db,挂卷、限日志、加健康检查(第 5、6 讲)。 - 上线运行:
docker compose up -d,docker compose ps确认全部健康。 - 日常运维:
docker compose logs -f看日志,docker system df看磁盘,docker system prune定期清理。 - 数据保障:数据库挂命名卷,关键卷定期用临时容器打包备份。
这条链路里每个环节都是前面某一讲的落点——Docker 不是单点工具,是一整套"构建、交付、运行"的流程。
⚠️ 常见错误
- 容器里用
localhost连宿主机:连的是容器自己,多半 Connection refused,换宿主机地址。 - OOM 只加内存不查原因:先看
docker stats和日志,是内存泄漏还是确实配小了。 - 凭感觉改配置:每改一处,就用
up -d重建并看docker logs验证,别攒一堆改动再排。
📌 双语术语表(本讲)
| 中文 | English | 记忆点 |
|---|---|---|
| 环境变量 | environment variable | 运行时注入配置 |
| 环境变量文件 | env file | --env-file / env_file |
| 健康检查 | healthcheck | 就绪状态检测 |
| 日志驱动 | logging driver | 决定日志去哪 |
| 日志轮转 | log rotation | max-size 限制 |
| 标准输出 | stdout | 容器日志的出口 |
| 多阶段构建 | multi-stage build | 产物瘦身 |
| 运行用户 | user | 避免 root |
| 时区 | timezone | TZ=Asia/Shanghai |
| 内存限制 | memory limit | --memory 防 OOM |
| 就绪条件 | service_healthy | 等依赖真正可用 |
| 打包备份 | backup archive | 临时容器 tar 打包卷 |
⭐ 本讲考点清单
- 配置用环境变量注入,不写死在镜像/代码里
-e KEY=value、--env-file .env、Composeenvironment/env_file- 密钥不进镜像、不进 Git;用 secret / 密钥服务
ENV运行时生效,ARG仅构建时有效HEALTHCHECK:interval / timeout / retries,service_healthy做就绪等待- 日志写 stdout 不写文件;
--log-opt max-size防撑爆磁盘 docker logs -f --tail排错;日志驱动 json-file / syslog / gelf- 镜像优化:alpine/distroless、多阶段、合并 RUN、
.dockerignore - 安全:
USER非 root、锁定版本标签、定期更新 - 排查:
ps -a→logs→inspect;容器内localhost不是宿主机;TZ时区 depends_on+condition: service_healthy解决启动顺序竞态- 从开发到上线标准链路:Dockerfile → build → 本地验证 → compose 编排 → up -d → 运维清理 → 卷备份
- 生产事故高发点:日志无上限撑爆磁盘、忘挂卷丢数据、密钥写进镜像