2、Docker镜像管理:镜像分层原理、Dockerfile编写、镜像构建与优化、镜像仓库使用
好,咱们今天聊聊Docker镜像管理。说实话,这是容器化最核心的一块。你想想看,没有镜像,容器就是个空壳子。我刚开始接触Docker时,也被镜像的分层机制搞得有点懵,但搞懂了之后,你会发现它设计得非常巧妙。
2.1 镜像分层原理
Docker镜像不是一个大文件,而是由一层层只读文件系统叠加起来的。每一层代表一个Dockerfile指令。比如你写FROM ubuntu:20.04,这就是基础层;再写RUN apt-get update,又加一层。
为什么要分层?说白了就是为了复用和节省空间。多个镜像可以共享相同的基础层,你本地拉取10个基于Ubuntu的镜像,底层只存一份。我在项目中遇到过,团队里有人每个微服务都从零构建,结果磁盘爆了。后来统一用公司的基础镜像,空间直接省了70%。
核心概念:镜像层是只读的,容器启动时会在最上层加一个可写层。所有写操作都发生在可写层,这就是「写时复制」机制。
嗯,这里要注意:如果你修改了容器里的文件,其实是在可写层创建了一个副本,底层镜像文件纹丝不动。这也是为什么容器删除后,所有修改都丢了——除非你提交成新镜像。
2.2 Dockerfile编写
Dockerfile就是镜像的构建说明书。我习惯把它写得像菜谱一样清晰。下面是一个典型的Node.js应用Dockerfile:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
这里用了多阶段构建,为什么?因为第一阶段只需要编译工具,第二阶段只保留运行产物。我曾经见过有人把整个Golang编译环境塞进生产镜像,结果镜像1.2GB。用多阶段构建后,直接降到80MB。
我的习惯:每个Dockerfile尽量只做一件事。把RUN指令合并,减少层数。把变化最频繁的指令放在最后,充分利用缓存。
避坑指南:我曾经在Dockerfile里写RUN apt-get upgrade,结果每次构建都拉新包,缓存完全失效。后来改成固定版本号,构建速度提升了3倍。
2.3 镜像构建与优化
构建镜像用docker build命令。但优化才是重头戏。我总结了几条铁律:
- 选择最小基础镜像:alpine、slim版本优先。比如Python镜像,slim版比full版小300MB。
- 清理临时文件:在同一个RUN指令里删除apt缓存、临时下载包。比如
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* - 利用.dockerignore:排除node_modules、.git等无用文件。我见过有人把整个项目目录拷进去,构建了5分钟才发现问题。
- 合理使用缓存:把不变的层放在前面,变的层放在后面。比如先COPY package.json,再COPY源代码。
| 优化项 | 优化前 | 优化后 | 效果 |
|---|---|---|---|
| 基础镜像 | node:18 | node:18-alpine | 缩小约300MB |
| 多阶段构建 | 单阶段1.2GB | 两阶段80MB | 缩小93% |
| 清理缓存 | 未清理 | 清理apt缓存 | 缩小约50MB |
注意:不要为了优化而优化。如果镜像只有100MB,非要折腾到90MB,投入产出比太低。我一般把目标定在200MB以下,超过这个值才考虑优化。
2.4 镜像仓库使用
镜像构建好了,得有个地方存。Docker Hub是公共仓库,Harbor是企业级私有仓库。
Docker Hub:适合个人项目或开源项目。拉取镜像很简单:docker pull nginx:latest。但要注意,匿名用户有拉取频率限制。我团队之前CI/CD频繁拉镜像,结果被限速了。后来配置了Docker Hub的认证,问题解决。
Harbor:企业级私有仓库,支持权限控制、镜像扫描、复制等功能。部署Harbor需要docker-compose或Helm。我习惯用Helm部署到K8s集群里:
helm repo add harbor https://helm.goharbor.io
helm install my-harbor harbor/harbor \
--set expose.type=nodePort \
--set persistence.enabled=true
推送到Harbor的步骤:
- 先打标签:
docker tag myapp:latest harbor.example.com/library/myapp:1.0 - 再推送:
docker push harbor.example.com/library/myapp:1.0
避坑指南:我曾经在Harbor里忘记配置垃圾回收,结果存储空间被废弃镜像占满。建议设置自动清理策略,比如保留最近10个版本。
嗯,最后说一句:镜像管理看似简单,但坑不少。分层原理搞懂了,Dockerfile写规范了,仓库用顺手了,你的容器化之路就稳了。下一章咱们聊聊容器网络,那才是真正让人头疼的地方。