01. 镜像大小的优化——为什么我的镜像 2GB
大镜像的问题:push/pull 慢、占用磁盘多、启动慢。优化镜像大小是个系统工程:
1. 选对基础镜像——alpine(约 5MB)比 ubuntu(约 70MB)小十几倍。但 alpine 用 musl libc 而不是 glibc,某些 C 扩展可能不兼容。
2. 多阶段构建——构建在第一个阶段(大镜像),最终产物复制到第二个阶段(小镜像,如 alpine)。构建工具不进入最终镜像。
3. 减少层数——把多个 RUN 合并成一个,用 && 连接。每层都是增量,层数少=体积小。
4. 清理包缓存——apt-get 后立刻 rm -rf /var/lib/apt/lists/*。
5. 不要装不需要的东西——推荐用 --no-install-recommends。
6. .dockerignore——把 node_modules、.git、.env 排除,别打进镜像。
dockerfile
# 多阶段构建示例
# Stage 1: 构建
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: 生产
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/main.js"]多阶段构建是镜像瘦身的最佳实践——构建阶段可以很臃肿(装各种构建工具),最终镜像只用 alpine 加产出的 dist。
02. 层缓存优化——加速构建
Docker 的构建是逐层缓存——每层如果没变就用缓存,变了这一层和所有后面的层都重建。所以正确的做法是把不常变的放前面、常变的放后面。
典型的优化:先 COPY package.json 和 lock 文件,然后 RUN npm ci。最后才 COPY 源代码。这样只改代码时,npm install 那层能走缓存,构建快很多。
注意:docker build 的缓存可能过期——如果 package.json 没变但 npm registry 上的版本更新了,缓存还是会用旧版本。可以用 --no-cache 强制重建,或在 CI 里用 --build-arg 传版本信息破坏缓存。
bash
# 差的做法(每次改代码都重新 npm install)
COPY . .
RUN npm ci
# 好的做法(package.json 没变就复用缓存)
COPY package*.json ./
RUN npm ci
COPY . .
# 强制不用缓存
docker build --no-cache -t app .
# .dockerignore
git
node_modules
.env
*.md
dist03. 容器运行时优化
镜像优化完,运行时的优化也重要:
1. 合理设置 restart policy——unless-stopped(Docker 重启/崩溃后自动启动容器),always 也行但手动 stop 后下次 Docker 重启它又活了。
2. 日志管理——不设 limit 的话容器的 stdout/stderr 日志会无限增长,撑满磁盘。用 --log-opt max-size=10m --log-opt max-file=3。
3. 优雅关闭——容器收到 SIGTERM 后要能优雅关闭。CMD 里用 exec 形式(CMD ["node", "app.js"])而不是 shell 形式(CMD node app.js),因为 shell 形式不转发信号。
4. healthcheck——给容器加健康检查,Swarm/Compose 能根据健康状态决定要不要重启容器。
5. 资源不必要的不给——--cpus 和 --memory 合理设置,别给太多也别给太少。
bash
# 日志限制
docker run -d --log-opt max-size=10m --log-opt max-file=3 nginx
# 全局日志限制(/etc/docker/daemon.json)
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
# Healthcheck(Dockerfile 里)
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || exit 1json-file 是默认日志驱动,日志存在 /var/lib/docker/containers/ 下。不设置 rotate 的话几个月能长到几十 GB。
04. 多阶段构建进阶
多阶段构建不只是简单的复制文件,还能做更多:
1. 给阶段起名字:FROM image AS stage-name,后面 COPY --from=stage-name。
2. 多来源:从不同阶段甚至不同镜像复制文件。比如 Go 应用的静态编译——在构建阶段编译成二进制,运行阶段用 scratch(空白镜像)只放二进制。
3. --chown 参数:COPY --from=builder --chown=appuser:appuser /app/dist ./dist。在复制的同时换所有者。
4. 构建参数:用 ARG 传构建时变量,结合 --target 只构建到某个阶段(适合 CI 里分开构建和测试)。
dockerfile
# 多阶段构建——Go 应用从构建到 scratch
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]
# 最终镜像只有 server 二进制,几 MB!
# 只构建到 builder 阶段
docker build --target builder -t app:dev .scratch 是空的,没有 shell、没有包管理器、啥都没有。适合静态编译的 Go/Rust 应用,极致安全和小巧。
05. Docker Compose 优化
docker-compose.yml 也有一些优化点:
1. 利用 depends_on 和 healthcheck 控制启动顺序——数据库就绪后再启动应用。但 depends_on 只等容器启动不等服务就绪,配合 condition: service_healthy 才能真正等。
2. 环境变量用 .env 文件管理,不要写死在 compose 文件里。不同环境各有一个 .env 文件,compose 自动读取。
3. 合理利用 profiles——不是所有服务每次都要启动。给开发用的工具服务加 profiles: ['dev'],docker compose --profile dev up 才启动它们。
4. 限制资源——compose 里也能设 cpus 和 memory limits,防止某个容器吃光资源。
5. network 用自定义网络——多个 compose 项目默认创建各自的网络互不干扰。需要跨项目通信时指定外部网络。
yaml
# docker-compose.yml 优化
get services:
app:
image: myapp
depends_on:
db:
condition: service_healthy
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5depends_on 的 condition: service_healthy 需要 compose v3.4+(或使用 v2.x 格式)。老版本只支持 depends_on 启动顺序不检查健康状态。
知识测验
第 1/5 题正确 0
多阶段构建的主要目的?