原创 Dipanshu 2024-11-04 07:15 广东
从 1.2GB 到 8MB,部署时间缩短 85%,云成本降低 60%,实在太香了!
你是否一样厌倦了与臃肿的 Docker 镜像进行抗争?这些镜像是否会占用磁盘空间并影响部署速度?值得注意的是:顶级 DevOps 团队已经将镜像大小削减了 99%。在本指南中,我将揭开他们一直保密的技术。
臃肿的 Docker 镜像的隐性成本
在我们深入研究解决方案之前,让我们先讨论一下为什么这很重要。过大的 Docker 镜像不仅会带来麻烦,还会让你付出代价:
时间:构建和部署周期较慢
金钱:增加存储和带宽成本
性能:应用程序响应速度降低
从 1.2GB 到 8MB 的历程:案例研究
为了说明这些技术的威力,让我们看一个真实的例子。我们采用了一个基于 Python 的标准机器学习应用程序,初始 Docker 镜像大小为 1.2GB,并将其优化至仅 8MB,优化步骤如下:
多阶段构建
图层优化
最少的基础镜像(包括 Scratch)
高级技术,例如 distroless 镜像
安全最佳实践
起点:臃肿的镜像
这通常是你可能遇到的典型 Dockerfile
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]
该 Dockerfile 虽然有效,但以下原因会导致镜像尺寸较大:
使用完整的Python镜像
包括不必要的构建工具和依赖项
层缓存效率低下
可能包含不必要的文件
现在一起看看可以优化镜像的技术:
多阶段构建:游戏规则改变者
多阶段构建是一种强大的技术,可以显著减少最终 Docker 镜像的大小,它使我们能够将构建时依赖项与运行时依赖项分开。
使用最小的基础镜像
根据你的用例,将完整的 python 版本替换为 slim 或 alpine 版本
FROM python:3.9-slim AS builder
单阶段 Dockerfile
# an official Python runtime as a parent image
FROM python:3.9-slim
# Install necessary build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*# Set the working directory
WORKDIR /app# Copy the requirements file and install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt# Copy the rest of the application code
COPY . .# Compile the model (if necessary)
RUN python compile_model.py# Run the inference script
CMD ["python", "inference.py"]
构建此镜像大约需要 1.2GB,这么大的尺寸是由于这包含了所有构建工具和开发库。
多阶段 Dockerfile
第一阶段:构建阶段
我们将设置工作目录
安装必要的构建工具
复制并安装我们的 python 依赖项
复制应用程序代码
使用 PyInstaller 创建独立的可执行文件
# Stage 1: Build
FROM python:3.9-slim AS builder
# Install necessary build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*# Set the working directory
WORKDIR /app# Copy the requirements file and install dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt# Copy the application code
COPY . .# Compile the model (if necessary)
RUN python compile_model.py# Install PyInstaller
RUN pip install pyinstaller# Create a standalone executable
RUN pyinstaller --onefile inference.py
第二阶段:生产阶段
从scratch镜像开始 - 一个完全空的镜像
仅复制必要的文件Build stage
我们设置编译应用程序的入口点
# Stage 2: Production
FROM scratch
# Set the working directory
WORKDIR /app# Copy only the necessary files from the build stage
COPY --from=builder /app/dist/inference /app/inference
COPY --from=builder /app/model /app/model# Run the inference executable
ENTRYPOINT ["/app/inference"]
构建此多阶段 Dockerfile 的镜像大小约为85 MB — 减少了90%以上
层优化:每个字节都很重要
你在 Dockerfile 中写入的每条指令都会在 Docker 镜像中创建一个新层。例如 RUN、COPY 和 ADD 命令各自添加一个层,并且每个层都会添加镜像大小以及构建时间
最小化层:将多个运行命令组合成单个 RUN 指令,这种方法可以最大限度地减少冗余层并保持镜像更干净。例如:你应该将它们组合起来,而不是用于安装包和清理临时文件的单独 RUN 命令。
使用&&链接命令并在同一层进行清理。
RUN apt-get update && apt-get install -y python3-pip python3-dev && \
pip3 install numpy pandas && \
apt-get clean && rm -rf /var/lib/apt/lists/*
从头开始的最小基础镜像:少即是多
这是创建 Docker 镜像的最强大、但也是最具挑战性的方法。从头开始创建镜像意味着使用从头开始的基础镜像(https://hub.docker.com/_/scratch),无底层操作系统、没有依赖性、没有预先存在的数据或应用程序。把它想象成一个空的存储光盘,必须用数据填充它,因为里面什么也没有。
你放入其中的任何内容都会影响其大小,也意味着,如果你需要任何依赖项或支持应用程序或工具,你需要自己将它们安装在镜像上。
它主要在两种情况下有用:
创建自己的基础镜像时。当创建了自己的 Linux 发行版,不想把它放在另一个基础镜像之上,比如 ubuntu 之类的,就可以可以使用临时镜像,然后将你自己的 Linux 发行版放在上面。
拥有独立的可执行应用程序时。例如,对于基于 Python 的独立 ML/DL 应用程序(例如处理预测的模型服务器),可以使用 PyInstaller 等工具将代码编译为可执行文件。编译后,将可执行文件放入临时 Docker 镜像中。由于此镜像缺少操作系统或库,因此只需手动添加必要的依赖项(例如 TensorFlow、PyTorch、模型文件或配置文件)。这可以使镜像最小化并针对部署进行优化。
# syntax=docker/dockerfile:1
FROM scratch
ADD myapp /
CMD ["/myapp"]
先进技术
Distroless 镜像
想象一下你正在收拾行李去旅行,你有三个选择:
收拾好你的整个衣柜(Full Distribution Image)
不带任何东西,在目的地购买所有东西(Scratch Image)
仅打包你实际需要的内容(Distroless Image)
Google 的 distroless 镜像介于完整发行版和临时镜像之间,这是“恰到好处”的选项,它仅包含运行应用程序所需的内容。
Distroless 镜像特点如下:
比完整分布镜像小
更安全,因为不必要的组件更少
仍然包含 SSL 证书和时区数据等重要内容
FROM gcr.io/distroless/python3-debian10
COPY --from=builder /app/dist/main /app/main
COPY --from=builder /app/model /app/model
COPY --from=builder /app/config.yml /app/config.yml
ENTRYPOINT ["/app/main"]
使用 Docker 构建工具包
Docker BuildKit 为构建 Docker 镜像提供了改进的性能、安全性和更灵活的缓存失效。启用它
DOCKER_BUILDKIT=1 docker build -t myapp .
其他技术
消除不必要的文件:不要保留任何应用程序,镜像内的数据将直接增加镜像大小。相反,将容器连接到外部存储卷并将数据存储在那里,以便应用程序可以访问它,也可以不会使镜像膨胀。或者,你的应用程序还可以连接到外部数据存储(例如 MySQL 或 AWS S3)并从那里访问数据。
使用.dockerignore文件: .dockerignore类似于.gitignore。它允许从最终镜像中排除特定文件和目录,我们可以将 .dockerignore 文件添加到项目的根目录。例如,可以将大型数据文件、虚拟环境、日志、模型检查点和临时文件添加到. dockerignore 文件。
# Exclude large datasets
data/
# Exclude virtual environment
venv/# Exclude cache, logs, and temporary files
__pycache__/
*.log
*.tmp
*.pyc
*.pyo
*.pyd
.pytest_cache
.git
.gitignore
README.md# Exclude model training checkpoints and tensorboard logs
checkpoints/
runs/
利用镜像分析工具:诸如 Docker slim 之类的镜像压缩工具Dive也非常强大。它们将让你分析每个镜像层,包括层大小和内部文件,并可以帮助你找出自重在哪里以及你可以删除什么。
Unikernels:更小的镜像,包含你的应用程序和底层操作系统,旨在直接运行,但需要更深入的理解才能有效实现。(我个人还没有尝试过,但它们可以比典型的 Docker 镜像小 80%)
安全精简 Docker 镜像的基本安全实践
使用受信任的官方基础镜像,避免来自未知来源的未经验证的镜像
始终以非 root 用户身份运行容器
RUN adduser --disabled-password --gecos "" appuser
USER appuser
通过限制端口和 IP 地址来限制容器的网络暴露
docker run -p 127.0.0.1:8080:8080 myimage
定期扫描 Docker 镜像是否存在已知漏洞。(考虑使用Trivy等工具定期扫描镜像是否存在漏洞)
docker scan your-image:tag
避免将 API 密钥或密码等敏感信息直接硬编码到 Dockerfile 或环境变量中,而是使用 Docker 机密或由编排工具管理的环境变量等方法
为容器启用日志记录和监控以跟踪任何可疑活动
结论
实施这些技术后,我们取得了以下成果:
镜像大小:从 1.2GB 减少到 8MB(减少 99.33%)
部署时间:缩短 85%
云成本:降低 60%
请记住,关键是从最小的基础镜像开始,使用多阶段构建将构建环境与运行时环境分开,并不断优化层和依赖项。通过这些方法,可以显着减小 Docker 镜像的大小!
鼓励大家在自己的项目中尝试这些优化技术,从多阶段构建开始,然后逐步应用其他技术,看看可以将 Docker 镜像制作得有多小、有多高效。Happy optimizing :)
作者丨Dipanshu 编译丨Rio
*本文为dbaplus社群编译整理,如需转载请取得授权并标明出处!欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn