151 lines
4.7 KiB
Markdown
151 lines
4.7 KiB
Markdown
|
|
---
|
|||
|
|
tags: [docker, container, dockerfile, buildkit, image-optimization]
|
|||
|
|
create time: 2026-05-18 00:45
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Dockerfile 最佳实践 — 教程
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
Dockerfile 是微服务交付的标准起点。本文档从多阶段构建出发,详解层缓存技巧、BuildKit 高级特性和常见问题排查。更多话题:多架构构建见 [[../01-容器化/02-多架构构建]],镜像安全见 [[../01-容器化/03-镜像安全]]。
|
|||
|
|
|
|||
|
|
## Go 多阶段构建(推荐)
|
|||
|
|
|
|||
|
|
```dockerfile
|
|||
|
|
# ========== 阶段 1: 构建 ==========
|
|||
|
|
FROM golang:1.22-alpine AS builder
|
|||
|
|
|
|||
|
|
RUN apk --no-cache add git ca-certificates
|
|||
|
|
|
|||
|
|
WORKDIR /app
|
|||
|
|
COPY go.mod go.sum ./
|
|||
|
|
RUN go mod download
|
|||
|
|
|
|||
|
|
COPY . .
|
|||
|
|
ARG LDFLAGS="-s -w -extldflags '-static'"
|
|||
|
|
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "$LDFLAGS" -o server .
|
|||
|
|
|
|||
|
|
# ========== 阶段 2: 运行时 ==========
|
|||
|
|
FROM alpine:latest
|
|||
|
|
|
|||
|
|
RUN apk --no-cache add ca-certificates tzdata && \
|
|||
|
|
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
|||
|
|
echo "Asia/Shanghai" > /etc/timezone
|
|||
|
|
|
|||
|
|
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
|||
|
|
USER appuser
|
|||
|
|
|
|||
|
|
WORKDIR /app
|
|||
|
|
COPY --from=builder /app/server .
|
|||
|
|
|
|||
|
|
EXPOSE 8080
|
|||
|
|
|
|||
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|||
|
|
CMD wget -qO- http://localhost:8080/healthz || exit 1
|
|||
|
|
|
|||
|
|
CMD ["./server"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!question] 为什么要用多阶段构建?
|
|||
|
|
>
|
|||
|
|
> 单阶段构建中,编译工具和源码都在最终镜像里——一个 Go 项目的镜像轻松超过 800MB。**多阶段构建**把构建和运行拆成两个独立的镜像层,第二阶段只 COPY 二进制文件,最终镜像缩小到十几 MB。
|
|||
|
|
|
|||
|
|
## 关键优化点速查
|
|||
|
|
|
|||
|
|
| 优化项 | 方法 | 效果 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| **多阶段构建** | 编译和运行分离 | 镜像从 800MB → 15MB |
|
|||
|
|
| **alpine 基础镜像** | 替代 debian/ubuntu | 减小体积 |
|
|||
|
|
| **非 root 运行** | `USER appuser` | 安全合规 |
|
|||
|
|
| **静态链接** | `CGO_ENABLED=0` | 不依赖系统库 |
|
|||
|
|
| **layer cache** | `go.mod` 先 COPY | CI 加速构建 |
|
|||
|
|
| **健康检查** | HEALTHCHECK 指令 | K8s 原生支持 |
|
|||
|
|
|
|||
|
|
## `.dockerignore` — 别忽略的文件
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
.git
|
|||
|
|
.gitignore
|
|||
|
|
*.md
|
|||
|
|
vendor/
|
|||
|
|
tests/
|
|||
|
|
*.log
|
|||
|
|
.DS_Store
|
|||
|
|
.idea/
|
|||
|
|
.vscode/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!tip] 为什么 .dockerignore 很重要?
|
|||
|
|
>
|
|||
|
|
> 如果不排除 `.git` 目录,整个版本历史都会被打包进镜像(增加数百 MB)。如果排除不当,可能遗漏必要的配置文件。建议每个项目根目录都包含此文件。
|
|||
|
|
|
|||
|
|
## 层缓存技巧
|
|||
|
|
|
|||
|
|
### ❌ 差的缓存策略
|
|||
|
|
|
|||
|
|
```dockerfile
|
|||
|
|
# 差:任何文件改动都会让后续所有 layer 失效
|
|||
|
|
COPY . .
|
|||
|
|
RUN go build
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ✅ 好的缓存策略
|
|||
|
|
|
|||
|
|
```dockerfile
|
|||
|
|
# 好:只有 go.mod/go.sum 变化时才重新下载依赖
|
|||
|
|
COPY go.mod go.sum ./
|
|||
|
|
RUN go mod download
|
|||
|
|
COPY . .
|
|||
|
|
RUN go build
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
核心原则:**经常变动的内容靠近 COPY,少变的放前面**。这样当代码频繁变更时,依赖下载和构建步骤仍能命中缓存。
|
|||
|
|
|
|||
|
|
## BuildKit 特性
|
|||
|
|
|
|||
|
|
BuildKit 是新一代构建引擎,默认在 Docker 18.09+ 和所有 Docker Desktop 中启用:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 启用 BuildKit 加速
|
|||
|
|
export DOCKER_BUILDKIT=1
|
|||
|
|
|
|||
|
|
# 利用远程缓存 (需配合 registry)
|
|||
|
|
docker build --cache-from=harbor.example.com/app/cache:latest .
|
|||
|
|
|
|||
|
|
# SSH agent forwarding(拉取私有依赖用)
|
|||
|
|
docker build --ssh default .
|
|||
|
|
|
|||
|
|
# 秘密变量注入(不进 Docker history)
|
|||
|
|
docker build --secret id=token,env=GITHUB_TOKEN .
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```dockerfile
|
|||
|
|
# syntax=docker/dockerfile:1
|
|||
|
|
# ^^^ 必须声明语法版本以启用 BuildKit
|
|||
|
|
|
|||
|
|
FROM golang:1.22-alpine AS builder
|
|||
|
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
|||
|
|
--mount=type=cache,target=/root/.cache/go-build \
|
|||
|
|
go mod download && go build -o server .
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!info] `--mount=type=cache` vs RUN 缓存
|
|||
|
|
>
|
|||
|
|
> Docker 默认的层缓存会在源文件变化时跳过整层;而 `type=cache` 是增量更新本地缓存目录,不受构建上下文变化影响,适合依赖下载和编译缓存。
|
|||
|
|
|
|||
|
|
## 常见问题排查
|
|||
|
|
|
|||
|
|
| 症状 | 原因 | 解决 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `exec: "app": not found` | 多阶段 COPY 路径错误 | 检查绝对路径和文件名拼写 |
|
|||
|
|
| `standard_init_linux.go: exec user process caused: permission denied` | 没有执行权限或缺少换行符 | `chmod +x`, 确保 Linux 换行 |
|
|||
|
|
| `service unavailable` (K8s) | HEALTHCHECK 未就绪 | 增大 `--start-period` |
|
|||
|
|
| 镜像体积异常大 | `.git` 未排除或多层 RUN 未清理 | 检查 `.dockerignore`,合并 RUN 并 `rm -rf` |
|
|||
|
|
| `cannot execute: executable file not found` | 架构不匹配(arm64 → amd64) | 确认 `TARGETARCH` 或手动指定 `--platform` |
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[../01-容器化/02-多架构构建]] — 一次构建全平台镜像
|
|||
|
|
- [[../01-容器化/03-镜像安全]] — Trivy 扫描与 distroless 镜像
|
|||
|
|
- [[../hhs/MS/05-部署运维/02-Kubernetes]] — K8s 以 Pod 为部署单元,镜像来自 Docker
|