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 指令都会产生一个新层。层数太多,镜像就大。我习惯把相关的命令用 && 串起来。
注意: 不要把敏感信息写进 Dockerfile!比如数据库密码、API Key。我曾经见过有人直接把生产环境的密钥硬编码在 Dockerfile 里,然后推到了公共仓库……后果可想而知。用构建参数(--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 的镜像,部署速度、启动速度、磁盘占用,差距是巨大的。

我总结了几条实战经验:

  1. 选对基础镜像:Alpine 是首选。比如 Python 镜像,python:3.11-slimpython:3.11 小了将近 1GB。
  2. 减少层数:每个 RUN、COPY、ADD 都会产生一层。把多个命令合并到一个 RUN 里。但别走极端,为了合并而牺牲可读性。
  3. 清理临时文件:安装完依赖后,记得清理缓存。比如 apt 的 /var/lib/apt/lists/*,npm 的 ~/.npm
  4. 多阶段构建:前面已经演示过了。这是最有效的瘦身手段之一。
  5. 利用 .dockerignore:别把本地开发环境里的垃圾文件带进镜像。

举个例子,一个优化前后的对比:

优化项 优化前 优化后
基础镜像 node:18 (900MB+) node:18-alpine (120MB)
构建方式 单阶段 多阶段
最终大小 1.2GB 158MB

我曾经接手过一个项目,镜像体积 2.3GB,部署一次要等 5 分钟。优化后降到 180MB,部署时间缩短到 30 秒。团队里的人都惊呆了。

检查工具:docker dive 这个工具,可以交互式地查看镜像每一层的内容,帮你找到「吃空间」的元凶。我每次优化镜像,都会先用 dive 扫一遍。

还有一个容易被忽略的点——镜像的安全扫描。镜像小了,攻击面也小了。用 trivyclair 定期扫描镜像仓库,发现高危漏洞及时修复。我在生产环境里遇到过因为基础镜像里的 OpenSSL 漏洞被安全团队通报的情况,从那以后,扫描就成了 CI 流程里的必过环节。

嗯,镜像管理这块内容不少,但核心就这几件事:拉得到、造得出、存得住、传得快、体积小。把这几点吃透了,日常工作中大部分容器化问题都能搞定。