Docker / 命令文档
Docker 镜像核心(面试向)
概述
本篇是「Docker 面试核心」系列第四篇,讲镜像体系:分层与 Layer、Tag 与 Digest、构建缓存、Base Image 选型、镜像瘦身、多阶段构建与私有仓库。镜像题的本质是「分层 + 内容寻址」八个字。
所有命令与输出均在 Docker 26.1.4(overlay2)实测。
镜像分层与 Layer
镜像是一组只读层的堆叠,容器在其顶部挂一个可写层。每层只存与父层的差异(overlay2 的 lowerdir/upperdir 机制)。
$ docker history busybox:latest --format "{{.CreatedBy}}\t{{.Size}}"
BusyBox 1.38.0 (glibc), Debian 13 4.45MB
$ docker images --format "{{.Repository}}:{{.Tag}} {{.Size}}"
kb-multi:latest 4.45MB
busybox:latest 4.45MB
alpine:3.19 7.4MB面试要点:
- 分层的价值:共享(busybox 与 kb-multi 共用同一基础层,磁盘只存一份)、缓存(构建复用)、传输(pull/push 只传本地没有的层)
- 容器可写层是 copy-on-write:改文件 = 从下层复制到可写层再改;删除下层文件只是在可写层打「删除标记」,磁盘不会变小
- 查看每层来源与大小用
docker history,排查「镜像为什么这么大」的第一入口
Tag 与 Digest
Tag 是可变的人类友好指针(可被重新 push 覆盖);Digest 是内容的 sha256(不可变),生产拉取要能落到 Digest。
$ docker images --digests --format "table {{.Repository}}:{{.Tag}}\t{{.Digest}}\t{{.Size}}"
REPOSITORY:TAG DIGEST SIZE
busybox:latest sha256:dc2d74b28e4cf8984fa52af1f39bc7c3d9c73760b41a74d629f5d11b1ab28616 4.45MB
alpine:3.19 sha256:6baf43584bcb78f2e5847d1de515f23499913ac9f12bdf834811a3145eb11ca1 7.4MB
# 同一镜像可打多个 Tag,都指向同一镜像 ID;删一个 Tag 不删数据
$ docker tag busybox:latest busybox:kb-demo && docker rmi busybox:kb-demo
tag 删除后真身仍在: 1 个镜像 ID面试要点:
latest只是默认 Tag 不代表最新版本,且可被覆盖 —— 生产禁止依赖 latest 做部署(不可复现、无法回滚定位)- Digest 引用格式
repo@sha256:...,同一次构建产物永远一致,适合审计与回滚 - Tag 与 Digest 的关系:Tag 指向 manifest,manifest 记录 digest 与层清单;重新 push 同 Tag 会让本地旧 Tag 的 digest 校验失败
构建缓存 Cache
构建是逐层缓存的:父层不变 + 指令不变 + COPY 源不变 + ARG 值不变 → 直接 CACHED。输入一变,该层及之后全部重建(实测见 Dockerfile 篇)。
$ docker build -t kb-test:1.0 /tmp/kb_build 2>&1 | grep -c CACHED 3
面试要点:
- 缓存命中三要素:父层 ID、指令文本、(COPY/ADD 的)文件内容校验和
- 工程实践:基础镜像/系统依赖层在前,应用代码层在后;先 COPY 依赖清单装依赖,再 COPY 代码
- 缓存不是免费的:RUN 里下载的包在缓存层里照样占磁盘;
docker builder prune清构建缓存
Base Image 选择
| 基础镜像 | 大小 | 特点 | 适用 |
|---|---|---|---|
| alpine | ~7.4MB | musl libc,包管理 apk | 追求极小体积,注意 musl 兼容性 |
| busybox | ~4.45MB | 只有 BusyBox 工具集 | 极简工具容器、调试 |
| debian-slim | ~75MB | glibc,兼容性最好 | Python/C 等依赖 glibc 的应用 |
| distroless | ~20MB | 无 shell 无包管理器 | 生产安全基线,攻击面最小 |
面试要点:
- alpine 的经典坑:musl 与 glibc 的 DNS 解析行为差异、C 扩展需重编译;Python 项目常从 alpine 迁回 slim
- 选型公式:兼容性优先选 slim/distroless,体积极端敏感才上 alpine
FROM scratch+ 静态编译二进制是体积与安全的双重最优解(Go 生态标配)
镜像大小优化
四个手段,按收益排序:小基础镜像、多阶段构建、合层清理、.dockerignore。
# 反面教材:两层各留 100MB 安装缓存
RUN apt-get update && apt-get install -y big-pkg
RUN apt-get clean # 没用!删除发生在新的一层
# 正确:同层内完成安装与清理
RUN apt-get update && apt-get install -y --no-install-recommends big-pkg \
&& rm -rf /var/lib/apt/lists/*面试要点:
- 「删除文件不会缩小镜像」是层模型的推论,也是高频考题;清理必须与产生垃圾的安装写在同一条 RUN
--no-install-recommends(apt)/--no-cache(apk)/ pip 无缓存参数配合使用- 构建上下文过大(node_modules 被打包)会拖慢 build,
.dockerignore是第一道优化
多阶段构建
把「编译环境」和「运行环境」拆成两个 FROM:构建阶段产生的工具链、源码、缓存不会进入最终镜像,只 COPY 走产物。
FROM alpine:3.19 AS build RUN echo "artifact-v1" > /tmp/artifact.txt FROM busybox:latest COPY --from=build /tmp/artifact.txt /artifact.txt CMD ["cat", "/artifact.txt"]
$ docker build -t kb-multi /tmp/kb_multi 2>&1 | grep "writing image"
#9 writing image sha256:58ad04c4... done
$ docker run --rm kb-multi
artifact-v1
$ docker images kb-multi --format "{{.Size}}"
4.45MB # 最终镜像只含 busybox + 产物,alpine 构建层全部留在构建阶段面试要点:
- 收益场景:C/Go 编译链、Java 的 JDK 构建层(几百 MB)不进运行镜像,最终镜像只带 JRE 与 jar
COPY --from=build是层间复制;--target可只构建到某个阶段(调试用)- 多阶段不影响缓存逻辑,各阶段独立缓存;命名阶段(AS build)便于引用
私有镜像仓库
自建 Registry(registry:2 官方镜像)实现 push/pull 内网分发,实测全流程:
# 1) 起私有仓(生产可用 Harbor 获得权限管理与扫描)
$ docker run -d --name kb-registry -p 5000:5000 registry:2
# 2) 打 Tag(地址 = registry/仓库:tag)并推送
$ docker tag alpine:3.19 localhost:5000/alpine:kb
$ docker push localhost:5000/alpine:kb
kb: digest: sha256:7dc2e94aced06294d5d6d11c91154d4ea696b13a184e9f8b341535257be69e02 size: 527
# 3) 删除本地后从私有仓拉回
$ docker rmi localhost:5000/alpine:kb && docker pull localhost:5000/alpine:kb
localhost:5000/alpine:kb
# 4) 查询仓库清单(Registry V2 API)
$ curl -s http://127.0.0.1:5000/v2/_catalog
{"repositories":["alpine"]}面试要点:
- localhost/127.x 上的 Registry 默认按非安全仓处理(HTTP 可用);非回环地址需要
insecure-registries配置或配 TLS 证书 - 私有仓的价值:内网分发快、版本自主可控、配合 CI 实现镜像晋级(dev → staging → prod)
- Harbor = registry + RBAC + 镜像扫描 + 复制同步,企业标准答案;直连仓库拉取慢时配 registry-mirrors
高频面试题速答
| 问题 | 一句话答案 |
|---|---|
| 镜像为什么要分层 | 层可共享、可缓存、可增量传输,是 Docker 存储与分发效率的基础 |
| Tag 和 Digest 区别 | Tag 可变可覆盖,Digest 是内容 sha256 不可变,生产固定版本用 Digest |
| 为什么删文件镜像不变小 | 只读层不可修改,删除只是可写层里的标记,须同层安装+清理或重建 |
| 容器写文件会影响镜像吗 | 不会,写入发生在容器可写层(copy-on-write),镜像始终只读 |
| 多阶段构建解决什么问题 | 编译工具链与产物分离,最终镜像只含运行必需物,体积与攻击面双降 |
| latest Tag 有什么风险 | 可变且语义模糊,重拉可能变镜像,导致不可复现部署与难定位回滚 |
| 私有仓库怎么搭 | registry:2 一行起服务,Harbor 加权限与扫描;非回环地址需 TLS 或 insecure-registries |
| 镜像瘦身四板斧 | 小基础镜像、多阶段构建、同层安装+清理、.dockerignore 减上下文 |