
前几天在公司部署服务的时候,发现一个nodejs应用的镜像居然有2.5G!我当时就懵了,这不科学啊,一个简单的web应用怎么可能这么大。同事开玩笑说是不是把整个node_modules都打包进去了...结果还真被他说中了一部分。
这种情况在实际工作中真的太常见了,很多时候我们构建的镜像莫名其妙就变得很大,但是又不知道问题出在哪里。直到我发现了dive这个工具,才算是找到了分析镜像的利器。
dive是一个用Go语言开发的Docker镜像分析工具,它可以让你深入了解镜像的每一层结构,看到每一层添加了什么文件,删除了什么内容,甚至可以计算出镜像的效率指标。
你可能会问,docker history命令不也能看镜像的层信息吗?确实可以,但是dive的优势在于:
说白了,docker history只能告诉你"发生了什么",而dive能告诉你"具体发生了什么,影响有多大"。
最简单的方式就是从GitHub releases页面下载对应系统的二进制文件:
# Linux
wget https://github.com/wagoodman/dive/releases/download/v0.12.0/dive_0.12.0_linux_amd64.tar.gz
tar -xzf dive_0.12.0_linux_amd64.tar.gz
sudo mv dive /usr/local/bin/
# macOS
brew install dive这种方式比较适合不想在本地安装的情况:
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest <镜像名>我个人更喜欢直接安装,因为用起来更方便一些。
如果你想体验最新的功能,可以从源码编译:
go install github.com/wagoodman/dive@latest不过说实话,直接用release版本就足够了,我用了这么久也没遇到什么bug。
安装完成后,使用dive分析镜像非常简单:
dive <镜像名>比如分析一个nginx镜像:
dive nginx:latest启动后你会看到一个分屏的界面,左边显示镜像的各个层,右边显示当前选中层的文件系统内容。
界面操作说明:
让我用一个真实的例子来演示。我之前构建了一个Python应用的镜像,Dockerfile是这样的:
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]看起来很正常对吧?但是构建出来的镜像有1.2G。用dive分析一下:
dive myapp:latest通过dive分析,我发现了几个问题:
python:3.9这个镜像本身就很大,包含了很多我不需要的系统工具和库。dive显示这个基础层就有900多MB。
在dive界面中,可以看到第一层(基础镜像层)的大小占了整个镜像的大部分。通过Tab键切换到右侧窗格,可以浏览基础镜像包含的文件,发现里面有gcc、各种开发工具等等。
RUN pip install这一层显示增加了300多MB,但实际的Python包应该没这么大。进入这一层的文件系统,发现/root/.cache/pip目录占用了很大空间。
COPY . . 这一层虽然只有几MB,但是包含了.git目录、pycache、测试文件等不需要的内容。
基于dive的分析结果,我重新编写了Dockerfile:
FROM python:3.9-slim
WORKDIR /app
# 只复制requirements文件,利用Docker缓存
COPY requirements.txt .
# 安装依赖并清理缓存
RUN pip install --no-cache-dir -r requirements.txt \
&& rm -rf /root/.cache/pip
# 只复制需要的应用文件
COPY src/ ./src/
COPY app.py .
CMD ["python", "app.py"]还添加了一个.dockerignore文件:
.git
.gitignore
README.md
Dockerfile
.dockerignore
__pycache__
*.pyc
*.pyo
*.pyd
.Python
.pytest_cache
.coverage
test/
docs/重新构建后用dive分析,镜像大小降到了180MB!效果非常明显。
dive会给你的镜像打一个效率分数。在界面底部可以看到类似这样的信息:
Image efficiency score: 95%
Potential wasted space: 12MB这个评分主要基于:
dive支持在CI/CD流水线中使用,可以设置镜像大小和效率的阈值:
# 设置镜像最大500MB,效率最低80%
dive myapp:latest --ci \
--lowestEfficiency=80 \
--highestWastedBytes=500MB如果镜像不符合要求,dive会返回非0退出码,可以让构建失败。
我在公司的Jenkins流水线中就加了这个检查,避免有人提交过大的镜像。
dive支持多种输出格式,方便集成到其他工具中:
# JSON格式输出
dive myapp:latest --json output.json
# 只显示汇总信息
dive myapp:latest --ci --quiet-ci经常需要对比优化前后的镜像差异,可以分别分析两个镜像:
dive myapp:v1.0
dive myapp:v2.0通过对比两个版本的层结构和大小变化,可以快速定位问题。
对于多阶段构建的Dockerfile,dive特别有用。可以清楚地看到哪些文件被复制到了最终镜像,哪些留在了构建阶段。
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html用dive分析这种镜像时,可以看到builder阶段的文件没有进入最终镜像,这样确保了镜像的精简。
在分析镜像时,注意查看是否包含了敏感文件,比如:
dive可以帮你浏览整个文件系统,确保不会意外泄露敏感信息。
通过长期使用dive分析各种镜像,我总结了一些常见的优化策略:
比如:
每个RUN指令都会创建一个新层,合并相关的RUN指令可以减少层数:
# 不好的写法
RUN apt-get update
RUN apt-get install -y git
RUN apt-get install -y curl
RUN apt-get clean
# 好的写法
RUN apt-get update && \
apt-get install -y git curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*这个真的很重要,很多人容易忽略。就像.gitignore一样,.dockerignore可以避免把不需要的文件复制到镜像中。
在同一个RUN指令中安装软件并清理:
RUN apt-get update && \
apt-get install -y python3-pip && \
pip3 install -r requirements.txt && \
apt-get remove -y python3-pip && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*这是很多新手会遇到的问题。如果在一个RUN指令中创建了文件,然后在另一个RUN指令中删除,文件还是会占用空间。
# 错误的做法
RUN wget https://example.com/largefile.tar.gz
RUN tar -xzf largefile.tar.gz && rm largefile.tar.gz
# 正确的做法
RUN wget https://example.com/largefile.tar.gz && \
tar -xzf largefile.tar.gz && \
rm largefile.tar.gzdive可以清楚地显示这种情况,你会看到删除操作创建了一个新层,但总大小没有减少。
Docker的缓存机制是基于层的,如果把经常变化的文件放在前面,会导致后续的层都需要重新构建。
# 不好的顺序
COPY . .
RUN pip install -r requirements.txt
# 好的顺序
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .使用多阶段构建时,注意不同阶段之间不会共享层,每个FROM都会开始新的层计算。
用了这么久dive,也发现了一些局限性:
不过这些问题相对于它带来的价值来说都是小问题。
除了dive,还有一些其他的镜像分析工具:
这个工具不仅能分析镜像,还能自动优化:
docker-slim build myapp:latest但是自动优化有时候会过于激进,可能会删掉必要的文件。
Google开源的工具,主要用于对比两个镜像的差异:
container-diff diff daemon://image1:tag daemon://image2:tag功能比较专一,适合做镜像对比分析。
主要用于镜像的复制和检查,也有一些分析功能:
skopeo inspect docker://nginx:latest相比之下,dive在交互性和详细程度方面还是有明显优势的。
镜像优化这个事情说起来简单,做起来却需要很多细节的把控。dive这个工具真的帮了我很大忙,让镜像分析变得直观和高效。
记得第一次用dive的时候,那种"终于知道问题出在哪里"的感觉真的很爽。就像医生给病人做CT扫描一样,能清楚地看到镜像的"内脏结构"。
当然,工具只是手段,关键还是要养成好的构建习惯。合理选择基础镜像、善用.dockerignore、及时清理临时文件...这些看似简单的操作,日积月累下来效果是很明显的。
如果你也经常被镜像大小困扰,强烈建议试试dive。相信我,用过之后你就回不去了。
最后,如果这篇文章对你有帮助的话,别忘了点个赞和转发。有任何问题也欢迎留言交流,我看到会及时回复的。
关注@运维躬行录,一起在运维的路上精进技艺,让每一次部署都更加优雅!