构建缓存技巧

构建缓存技巧

为什么缓存如此重要

优化 Docker 构建缓存可以大幅减少构建时间,在生产环境中,好的缓存策略能将构建时间从几十分钟缩短到几分钟。

核心原则

1. 指令顺序优化

Dockerfile 中指令的顺序直接影响缓存命中率:

# ❌ 低效:先复制所有代码,再安装依赖
FROM node:18
WORKDIR /app
COPY . .
RUN npm ci      # 代码变了就要重新安装

# ✅ 高效:先复制依赖文件,安装依赖,再复制代码
FROM node:18
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci            # 只有依赖文件变化才重装
COPY . .
RUN npm run build

2. 合并 RUN 指令

# ❌ 多个 RUN 指令,每层都有缓存
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim

# ✅ 合并为一个 RUN 层
RUN apt-get update && \
    apt-get install -y curl vim && \
    rm -rf /var/lib/apt/lists/*

3. 使用 .dockerignore

# .dockerignore - 减少上下文大小
node_modules
.git
*.log
.env
dist
.cache
.DS_Store

BuildKit 缓存挂载

–mount=type=cache

# syntax=docker/dockerfile:1
FROM golang:1.21

# Go 模块缓存
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download

# Go 构建缓存
RUN --mount=type=cache,target=/root/.cache/go-build \
    go build -o /app/server
FROM node:18

# npm 缓存
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# 如果使用 pnpm
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
    pnpm install

–mount=type=bind(只读缓存)

# 只读挂载构建时的依赖
RUN --mount=type=bind,source=package.json,target=package.json \
    --mount=type=bind,source=package-lock.json,target=package-lock.json \
    npm ci

CI 中的缓存策略

GitHub Actions

- name: Build with cache
  uses: docker/build-push-action@v5
  with:
    push: true
    cache-from: type=gha
    cache-to: type=gha,mode=max

GitLab CI

build:
  script:
    - docker buildx build
        --cache-from type=registry,ref=$CI_REGISTRY_IMAGE:cache
        --cache-to type=registry,ref=$CI_REGISTRY_IMAGE:cache,mode=max
        -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
        --push .

自建 CI

# 使用 registry 缓存
docker build \
  --cache-from registry.example.com/myapp:cache \
  --build-arg BUILDKIT_INLINE_CACHE=1 \
  -t myapp:latest .

# 推送缓存
docker push myapp:cache

多阶段构建缓存

# 合理拆分阶段,最大化缓存利用
FROM node:18 AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

FROM node:18 AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM nginx:alpine AS production
COPY --from=build /app/dist /usr/share/nginx/html

缓存失效监控

# 检查缓存使用情况
DOCKER_BUILDKIT=1 docker build --progress=plain -t myapp . 2>&1 | grep "CACHED"

# 检查镜像层
docker history myapp:latest

# 分析哪层缓存未命中
docker build --no-cache-filter=build -t myapp .

高级技巧

条件性缓存

# 只有 package.json 变化时重建依赖层
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \
    yarn install --frozen-lockfile

# 利用 BuildKit 的 --mount 实现更精细的缓存
RUN --mount=type=cache,target=/app/node_modules \
    --mount=type=bind,source=package.json,target=package.json \
    npm install

分层缓存失效

# 让 CI 可以控制缓存层
docker build \
  --build-arg CACHE_BUST=$(date +%s) \
  --build-arg GIT_COMMIT=$CI_COMMIT_SHORT_SHA \
  -t myapp .

面试要点

  1. 缓存的核心是”尽量不重复做已经做过的编译”
  2. Dockerfile 指令顺序对缓存命中率影响最大
  3. BuildKit 的 --mount=type=cache 是最强大的缓存工具
  4. CI 中使用 registry 或 GHA 缓存可跨构建共享缓存
  5. 多阶段构建天然支持缓存分离

面试官常问:一个 Dockerfile 中的缓存是怎么工作的?什么情况下缓存会失效?

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容