2. Docker镜像管理:镜像的获取与查看、镜像的构建(Dockerfile)、镜像的存储与分发、镜像的优化技巧
聊到Docker,镜像管理绝对是日常打交道最多的部分。我刚开始接触容器化那会儿,总觉得镜像就是个「黑盒子」——拉下来就能跑,但里面到底装了啥、怎么造出来的,完全没概念。后来踩了不少坑才明白,搞懂镜像管理,才算真正入了容器化的门。
2.1 镜像的获取与查看
获取镜像,最直接的方式就是从镜像仓库拉取。Docker Hub 是默认的公共仓库,里面啥都有——从官方的 Nginx、Redis,到各种社区维护的奇奇怪怪的镜像。
拉取镜像的命令很简单:
docker pull nginx:latest
docker pull redis:7.0-alpine
我个人习惯,拉镜像时一定会指定版本标签。为什么?latest 标签是个坑。我在项目中遇到过,某天 CI 流水线突然挂了,查了半天才发现是 latest 镜像被更新了,底层依赖变了。从那以后,我团队里所有 Dockerfile 和部署脚本,必须写死版本号。
查看本地已有的镜像:
docker images
# 或者
docker image ls
输出大概长这样:
| REPOSITORY | TAG | IMAGE ID | CREATED | SIZE |
|---|---|---|---|---|
| nginx | 1.25 | abc123def456 | 2 weeks ago | 187MB |
| redis | 7.0-alpine | def789ghi012 | 1 month ago | 32.1MB |
想看得更细?用 docker inspect 可以扒出镜像的「底裤」——包括层信息、环境变量、入口命令等等。我曾经排查一个镜像启动失败的问题,就是靠 inspect 发现镜像里默认的 CMD 参数跟我们预期的不一样。
docker history 镜像名:标签 可以查看镜像的构建历史。每一层做了什么操作,一目了然。这对排查镜像「为什么这么大」特别有用。
2.2 镜像的构建(Dockerfile)
Dockerfile 是构建镜像的「配方」。说白了,就是告诉 Docker 一步步怎么把应用打包成镜像。
一个典型的 Dockerfile 长这样:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
这里我用了多阶段构建。为什么?你想想看,第一个阶段里装了一堆构建工具,最终产物可能就几百 KB。如果不用多阶段构建,那些构建工具全塞进最终镜像里,体积直接奔着 1GB 去了。
写 Dockerfile 有几个关键点:
- FROM:基础镜像的选择。我建议尽量用 Alpine 版本,体积小、安全漏洞少。
- WORKDIR:设置工作目录。别偷懒不写,否则所有路径都得写绝对路径,容易出错。
- COPY vs ADD:能用 COPY 就别用 ADD。ADD 会自动解压 tar 包,有时候会带来意想不到的行为。
- RUN:每个 RUN 指令都会产生一个新层。层数太多,镜像就大。我习惯把相关的命令用
&&串起来。
--build-arg)或者 Docker Secrets 来处理敏感信息。
构建镜像的命令:
docker build -t myapp:v1.0 .
-t 指定镜像名和标签,最后的 . 是构建上下文路径。嗯,这里要注意:构建上下文里不要放无关文件。我习惯在项目根目录放一个 .dockerignore 文件,把 node_modules、.git、*.log 之类的都排除掉,不然构建速度会慢得让你怀疑人生。
2.3 镜像的存储与分发
镜像构建好了,得找个地方存起来。私有仓库是企业的标配。为什么?安全、可控、速度快。
搭建一个简单的私有仓库:
docker run -d -p 5000:5000 --name registry registry:2
然后就可以推送镜像了:
docker tag myapp:v1.0 localhost:5000/myapp:v1.0
docker push localhost:5000/myapp:v1.0
生产环境里,我建议用 Harbor 或者 Nexus。它们提供了 Web UI、权限管理、漏洞扫描等功能。我在上一家公司就是用的 Harbor,配合 LDAP 做用户认证,开发、测试、生产各一个项目空间,权限分得清清楚楚。
分发镜像时,网络是个大问题。尤其是跨国拉镜像,慢得让人抓狂。解决方案?
- 配置镜像加速器:国内用阿里云、腾讯云的加速器,效果立竿见影。
- 搭建镜像代理缓存:在本地或内网部署一个
registry mirror,团队里所有人都走这个缓存,拉过的镜像就不需要再去外网拉了。 - 预拉取:在 Kubernetes 集群节点上提前把常用镜像拉好,避免 Pod 调度时等待镜像下载。
2.4 镜像的优化技巧
镜像优化,说白了就是「减肥」。一个几百 MB 的镜像和几十 MB 的镜像,部署速度、启动速度、磁盘占用,差距是巨大的。
我总结了几条实战经验:
- 选对基础镜像:Alpine 是首选。比如 Python 镜像,
python:3.11-slim比python:3.11小了将近 1GB。 - 减少层数:每个 RUN、COPY、ADD 都会产生一层。把多个命令合并到一个 RUN 里。但别走极端,为了合并而牺牲可读性。
- 清理临时文件:安装完依赖后,记得清理缓存。比如 apt 的
/var/lib/apt/lists/*,npm 的~/.npm。 - 多阶段构建:前面已经演示过了。这是最有效的瘦身手段之一。
- 利用 .dockerignore:别把本地开发环境里的垃圾文件带进镜像。
举个例子,一个优化前后的对比:
| 优化项 | 优化前 | 优化后 |
|---|---|---|
| 基础镜像 | node:18 (900MB+) | node:18-alpine (120MB) |
| 构建方式 | 单阶段 | 多阶段 |
| 最终大小 | 1.2GB | 158MB |
我曾经接手过一个项目,镜像体积 2.3GB,部署一次要等 5 分钟。优化后降到 180MB,部署时间缩短到 30 秒。团队里的人都惊呆了。
docker dive 这个工具,可以交互式地查看镜像每一层的内容,帮你找到「吃空间」的元凶。我每次优化镜像,都会先用 dive 扫一遍。
还有一个容易被忽略的点——镜像的安全扫描。镜像小了,攻击面也小了。用 trivy 或 clair 定期扫描镜像仓库,发现高危漏洞及时修复。我在生产环境里遇到过因为基础镜像里的 OpenSSL 漏洞被安全团队通报的情况,从那以后,扫描就成了 CI 流程里的必过环节。
嗯,镜像管理这块内容不少,但核心就这几件事:拉得到、造得出、存得住、传得快、体积小。把这几点吃透了,日常工作中大部分容器化问题都能搞定。