Docker 容器化
运维视角的 Docker 手册:镜像怎么构建得小且安全、数据与网络怎么管理、多容器怎么编排、镜像怎么分发。容器原理(namespace/cgroup/UnionFS)见后端·容器与部署 的分工页面。示例语境:Docker Engine 27 + Docker Compose v2,命令需在真实环境验证。
先分清四个概念
- 镜像(Image):只读模板,由分层(layer)叠加;改一层会新增层,历史层仍在;
- 容器(Container):镜像的运行实例,可写层在其上;
- 仓库(Registry):存放与分发镜像(Docker Hub、Harbor、云镜像仓库);
- 引擎与守护进程:
dockerd+containerd承担容器生命周期,dockerCLI 只是客户端。
bash
docker version # 分 Server 与 Client 两行,连不上先看这里
docker info # 引擎运行状态与资源Dockerfile:构建的纪律
层级最小化与缓存友好的顺序:变化越频繁的指令越靠后(apt install 在前、COPY 代码 在后)。
dockerfile
# 多阶段构建:builder 阶段含编译工具,最终镜像只要产物
FROM golang:1.22 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server .
FROM gcr.io/distroless/base-debian12
WORKDIR /app
COPY --from=builder /app/server .
USER nonroot
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD ["/app/server", "health"]
ENTRYPOINT ["/app/server"]构建实践速查:
- 永远有
.dockerignore(排除.git、node_modules、*.log),避免把大文件送进构建上下文; RUN把包安装与缓存清理放同一层:apt-get install ... && rm -rf /var/lib/apt/lists/*;- 跑非 root 用户(镜像内置
USER,不要依赖启动后su); EXPOSE只是文档,真正映射在-p/ compose 里做;HEALTHCHECK给编排器(K8s 会替代它)提供就绪依据;- 一个容器一个主进程,
ENTRYPOINT定主程序、CMD给默认参数,前后台混跑会让容器秒退。
数据卷:容器删了数据不能没
| 类型 | 场景 | 生命周期 |
|---|---|---|
| Named volume | 数据库、应用状态 | 由 Docker 管理,docker volume rm 才删 |
| Bind mount | 挂载本机配置/开发目录 | 随宿主机文件存活 |
| tmpfs | 临时缓存、敏感小文件 | 容器停止即清空 |
bash
docker volume create pgdata
docker run -d --name postgres \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD_FILE=/run/secrets/pg_pw \
postgres:16纪律:容器可随时重建(docker rm -f 无负担),因为数据都在卷里;Bind mount 目录权限要以容器内用户视角检查,否则常见的「Permission denied」来自属主不匹配。
网络:默认桥接之外要知道的
bash
docker network ls
docker run -p 8080:80 --name web nginx # 端口映射 宿主:容器
docker network create app-net # 自定义网络(自动 DNS 解析)- 同自定义网络下的容器可用服务名互相访问(内置 DNS),跨主机网络是 K8s/overlay 的领域;
- 不要用
--network host图省事绕过端口隔离,安全与可移植性都丢; - 容器里看到
127.0.0.1是容器自己,连宿主服务要用宿主机地址或host-gateway。
资源限制、清理与运行纪律
bash
docker run -d --name api --memory 512m --cpus 1.5 image:tag
docker stats # 实时资源占用
docker system df # 看镜像/容器/卷各占多少
docker system prune -a -f # 清理悬空镜像与停止容器(-a 连未使用镜像也删,慎用)
docker logs --tail 200 api # 看容器日志(容器内 stdout/stderr)- 不加限制的容器可能吃光宿主机内存触发 OOM-kill 波及无关进程;
- 日志默认到 stdout 由 Docker 收集,长时间运行要配
--log-driver与轮转,否则磁盘被/var/lib/docker撑爆。
Compose:多容器的声明式入口
yaml
services:
app:
build: .
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DB_HOST: db
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 5
volumes:
pgdata:bash
docker compose up -d # 起服务(-d 后台)
docker compose ps # 健康与状态
docker compose logs -f app
docker compose down -v # 停止并连卷一起删(-v 慎用)注意 depends_on 默认只保证启动顺序、不保证就绪,依赖健康检查(service_healthy)才是可靠姿势。
镜像仓库与供应链安全
- 基础镜像锁版本、尽量用带 digest 的引用;构建产物用不可变 tag(
git-<sha>/ 构建号),弃用latest; - 内网/私有镜像加签名(cosign)并在部署侧校验,避免供应链投毒;
- 发布前做镜像漏洞扫描(trivy 等),高危漏洞进发布门禁;
- 推送凭证用凭据管理器/CI secret,不写进 Dockerfile 与 shell 历史。
bash
docker tag app:git-abc123 registry.example.com/team/app:git-abc123
docker push registry.example.com/team/app:git-abc123常见坑速查
| 坑 | 现象 | 解法 |
|---|---|---|
| 容器秒退 | 前台进程没跑 | 主进程前台的镜像设计,查 docker logs |
| 重启后容器没了 | 没配 restart/自启 | compose restart 或 --restart unless-stopped |
| 挂载目录 Permission denied | 属主与容器内用户不符 | 以容器用户视角改属主或用匹配的 uid |
| 磁盘被 Docker 撑爆 | 容器日志/镜像越积越大 | 日志轮转驱动 + system prune 定期清理 |
| 不设资源限制 | 宿主机 OOM | --memory/--cpus 按需设置 |
| 依赖库没起来就启动 | 应用连库失败 | 健康检查 + condition: service_healthy |
.dockerignore 缺失 | 构建上下文巨大、泄漏文件 | 补 .dockerignore |
| 镜像 tag 用 latest | 生产拉到意外版本 | 不可变 tag + digest |
检查清单
- [ ] Dockerfile 有多阶段构建、非 root 用户、健康检查与合理层顺序;
- [ ] 数据写入 named volume / bind mount,容器可随时重建;
- [ ] 端口仅按需映射,应用走自定义网络内服务名互通;
- [ ] 容器都配置资源限制与日志轮转,无无限增长项;
- [ ] 镜像使用不可变 tag,发布前完成漏洞扫描与签名校验;
- [ ] 密钥经 secret 注入,未明文写入任何构建或运行配置。