当前位置: 首页 > news >正文

如何缩小物联网设备的 Docker 镜像

大家好!我是大聪明-PLUS

物联网 (IoT) 设备通常缺乏资源来拉取和使用重量级的 Docker 镜像。本文将展示如何 使用工具  在 不重新编译容器化应用程序的情况下,将 Docker 镜像大小减少36-91%。我们还将介绍如何为使用 Rust、Go 和 C/C++ 编写的原生应用程序创建极简镜像。patchelfstrace

❯ 为什么要缩小 Docker 镜像?

Docker 镜像的大小及其包含的层数决定了设备下载和解压镜像所需的内存和磁盘空间。像 Raspberry Pi Zero 这样的设备缺乏解压 Home Assistant 镜像所需的资源。然而,Raspberry Pi Zero 拥有足够的资源来运行该程序。在这种情况下,减小镜像大小可以提高 Docker 的性能。此外,通过仅包含应用程序实际使用的文件,可以减少潜在的攻击面。这种方法不仅适用于物联网设备,也适用于服务器。

减小您自己开发的容器化应用程序的镜像大小很容易。只需编译一个静态二进制文件,并在最终镜像中仅包含该文件即可。但是,即使在使用第三方应用程序时,也有几种方法可以避免重新编译。其中,重要的是减小容器化脚本镜像的大小。

 

如果所讨论的应用程序被编译成ELF二进制文件 (C、C++、Fortran、Rust、Go 等文件通常如此),那么您可以使用该工具patchelf 找到应用程序中使用的所有库并将它们复制到完成的图像中。

缩写“ELF”代表“可执行和可链接文件格式”。除其他元数据外,此格式还指定了程序的解释器路径(例如, /lib64/ld-linux-x86-64.so.2 在 x86_64 平台上)和 rpath runtime search path (例如  ,  )。/lib64

使用程序解释器,我们将 ELF 文件本身及其所有依赖项(库)动态加载到内存中,然后执行它。在 Linux 中,这可以手动完成: /lib64/ld-linux-x86-64.so.2 /bin/sh 或者简单地 /bin/sh

程序的解释器使用该路径rpath来查找其所有依赖项。在大多数 Linux 发行版中(据我所知,只有 Guix 和 Nix 是例外),此路径为空,解释器会在硬编码路径(例如 /lib64)中搜索依赖项。

我们将使用该工具 patchelf 来更改解释器和 rpath,并用它readelf 来检查 ELF 文件。我们还会发现该工具非常有用ldd——它可以显示解释器及其所有依赖项。

如输出所示,Debian 中的 rpath 为空,并且/bin/sh 仅依赖于 libc。在 Guix 中执行相同命令的输出会有很大不同。这只是一个例子;我们不会详细解释 Guix 为什么使用非空rpath


$ readelf --headers /bin/sh | grep -A2 INTERPINTERP         0x0000000000000318 0x0000000000400318 0x00000000004003180x0000000000000050 0x0000000000000050  R      0x1[Requesting program interpreter: /gnu/store/gsjczqir1wbz8p770zndrpw4rnppmxi3-glibc-2.35/lib/ld-linux-x86-64.so.2]
$ readelf --dynamic /bin/sh | grep RUNPATH0x000000000000001d (RUNPATH)            Library runpath: [/gnu/store/lxfc2a05ysi7vlaq0m3w5wsfsy0drdlw-readline-8.1.2/lib:/gnu/store/bcc053jvsbspdjr17gnnd9dg85b3a0gy-ncurses-6.2.20210619/lib:/gnu/store/gsjczqir1wbz8p770zndrpw4rnppmxi3-glibc-2.35/lib:/gnu/store/930nwsiysdvy2x5zv1sf6v7ym75z8ayk-gcc-11.3.0-lib/lib:/gnu/store/930nwsiysdvy2x5zv1sf6v7ym75z8ayk-gcc-11.3.0-lib/lib/gcc/x86_64-unknown-linux-gnu/11.3.0/../../..]
$ ldd /bin/shlinux-vdso.so.1 (0x00007ffe777f6000)libreadline.so.8 => /gnu/store/lxfc2a05ysi7vlaq0m3w5wsfsy0drdlw-readline-8.1.2/lib/libreadline.so.8 (0x00007efca9070000)libhistory.so.8 => /gnu/store/lxfc2a05ysi7vlaq0m3w5wsfsy0drdlw-readline-8.1.2/lib/libhistory.so.8 (0x00007efca9063000)libncursesw.so.6 => /gnu/store/bcc053jvsbspdjr17gnnd9dg85b3a0gy-ncurses-6.2.20210619/lib/libncursesw.so.6 (0x00007efca8ff1000)libgcc_s.so.1 => /gnu/store/930nwsiysdvy2x5zv1sf6v7ym75z8ayk-gcc-11.3.0-lib/lib/libgcc_s.so.1 (0x00007efca8fd7000)libc.so.6 => /gnu/store/gsjczqir1wbz8p770zndrpw4rnppmxi3-glibc-2.35/lib/libc.so.6 (0x00007efca8dd9000)/gnu/store/gsjczqir1wbz8p770zndrpw4rnppmxi3-glibc-2.35/lib/ld-linux-x86-64.so.2 (0x00007efca90c9000)

❯ 例如:Stubby

让我们使用 Stubby patchelf 来缩小 Docker 镜像  ——一个支持 DNS-over-TLS 的名称解析工具。我们将使用 Debian 镜像作为基础,但这个过程非常典型,并不特定于这个 Linux 发行版或这个应用程序。

首先,我们将编写一个 Dockerfile,它首先从 Debian 存储库安装 Stubby 和所有必需的软件包,然后在第二步中仅将必要的文件复制到从头开始创建的最终映像中。

# Dockerfile
FROM debian:latest AS builder# install stubby and patchelf
RUN apt-get update && apt-get install -y stubby ca-certificates patchelf# copy and run patchelf script
COPY patchelf.sh /tmp/patchelf.sh
RUN /tmp/patchelf.sh# create the final image from scratch (i.e. without the base image)
FROM scratch# copy only the /out directory that contains the files that are actually used by stubby
COPY --from=builder /out /EXPOSE 53/udp
EXPOSE 53/tcpCMD ["/bin/stubby"]

接下来,我们编写一个脚本 patchelf来指定需要复制的文件。该文件将复制所有依赖项、程序解释器、二进制文件本身、配置文件,以及 OpenSSL 库配置文件,以及受信任的 SSL 证书列表。

#!/bin/sh
set -ex
mkdir -p /out/lib /out/bin /out/etc /out/var/cache/stubby /out/var/run /out/usr/lib
# copy the libraries that stubby uses
ldd /usr/bin/stubby |sed -rne 's/.*=> (.*) \(.*\)$/\1/p' |while read -r path; docp "$path" /out/libdone
# copy the interpreter
cp /lib64/ld-linux-x86-64.so.2 /out/lib
# copy stubby and its configuration file
cp /usr/bin/stubby /out/bin/stubby
# make stubby listen on all addresses to access it from outside the container
sed -i 's/127\.0\.0\.1/0.0.0.0/g' /etc/stubby/stubby.yml
cp -r /etc/stubby /out/etc/stubby
# copy openssl library configuration and certificates
cp -r /etc/ssl /out/etc/ssl
cp -r /usr/lib/ssl /out/usr/lib/ssl
find /out/etc/ssl/certs -not -type d -not -name ca-certificates.crt -delete
rm -rf /out/usr/lib/ssl/misc
# patch stubby binary to use the copied interpreter and libraries
patchelf --set-interpreter /lib/ld-linux-x86-64.so.2 --set-rpath /lib /out/bin/stubby
ldd /out/bin/stubby
find /out
# check that stubby works
chroot /out /bin/stubby -V

现在我们构建图像并确保它正常工作。

$ docker build --tag stubby:debian-patchelf .
$ docker inspect docker inspect -f "{{ .Size }}" stubby:debian-patchelf
13120030
$ docker run --init --rm --publish 53:53/udp stubby:debian-patchelf stubby -l
# in the other terminal window
$ dig @127.0.0.1 +short google.com
142.251.220.206

❯ 结果

我们使用命令将生成的镜像与其类似镜像进行比较docker inspect。替代镜像基于 Debian 和 Alpine,并且未使用脚本创建patchelf

图像

大小,MiB

评论

stubby:debian-patchelf

12.5

9% stubby:debian

stubby:debian

143.4

 

stubby:alpine-patchelf

9.0

64%的stubby:alpine

stubby:alpine

14.1

 

结果不言而喻。与 Debian 的 Stubby 镜像相比,我们的镜像体积缩小了91%  ,与 Alpine 的 Stubby 镜像相比,体积缩小了 36%。我们只需要包含 Stubby 实际使用的文件即可。效果令人印象深刻。

❯ 限制

Patchelf 完全自动化复制依赖项和程序解释器,但所有其他文件都必须手动复制。此外,如果你的程序无法编译为 ELF 二进制文件(例如,如果它是用 NodeJS 或 Python 编写的),那你就没那么幸运了。在这种情况下strace,。

❯ Strace

 

 

此工具拦截二进制文件执行的系统调用,并将其参数打印到屏幕上。Strace使用与调试 相同的内核 API,因此它会显著降低被跟踪程序的速度。幸运的是,我们只需要在 Docker 镜像构建过程中使用此工具。

❯示例:家庭助理

当我尝试使用官方 Docker 镜像时,我无法在我的 Raspberry Pi Zero 上安装这个程序。当我尝试拉取这个镜像时,它会并行下载多个层,然后由于磁盘空间不足而无法提取它们。我不得不临时插入一个 U 盘,将目录移动到其中/var/lib/docker,然后拉取镜像并将该目录返回到 Raspberry Pi——就是这样,镜像运行起来了。

现在让我们为 Home Assistant 创建一个新的 Docker 镜像——一个单层镜像。与原始镜像相比,它将占用最小的磁盘空间。

首先,让我们使用官方镜像创建一个 Dockerfile 并从那里开始构建。

# Dockerfile
FROM ghcr.io/home-assistant/home-assistant:stable AS builderRUN apk update && apk add straceCOPY strace.sh /tmp/strace.sh
RUN /tmp/strace.shFROM scratchCOPY --from=builder /out /# default Home Assistant port
EXPOSE 8123/tcp# default Home Assistant command
CMD ["/usr/local/bin/python3", "-m", "homeassistant", "--config", "/config"]

然后我们将编写一个脚本, strace该脚本将查找 Home Assistant 访问的所有文件并将它们复制到最终图像中。

#!/bin/sh
set -ex
mkdir -p /out/lib /out/usr/local/bin /out/usr/bin /out/usr/local/lib
# copy ffmpeg and its dependencies
ldd /usr/bin/ffmpeg |sed -rne 's/.*=> (.*) \(.*\)$/\1/p' |while read -r path; docp "$path" /out/libdone
cp /lib/ld-musl-x86_64.so.1 /out/lib
cp /usr/local/bin/python3 /out/usr/local/bin/python3
cp /usr/bin/ffmpeg /out/usr/bin/ffmpeg
# copy frontend files manually
mkdir -p /out/usr/local/lib/python3.11/site-packages
cp -r /usr/local/lib/python3.11/site-packages/hass_frontend /out/usr/local/lib/python3.11/site-packages/hass_frontend
# copy all the files that home assistant actually opens
strace -f -e open,stat,lstat timeout 30s python3 -m homeassistant --config /config 2>&1 |sed -rne 's/.*(open|stat)\(.*"([^"]+)".*/\2/p' |grep -vE '^/(dev|proc|sys|tmp)' |sort -u |while read -r path; doif ! test -e "$path"; thencontinuefiif test -d "$path"; then# create directoriesmkdir -p /out/"$path"else# copy filesmkdir -p /out/"$(dirname "$path")"cp -n "$path" /out/"$path" 2>/dev/null || truefidone
# recreate config directory
rm -rf /out/config
mkdir /out/config

现在您需要构建图像并确保它正常工作。

$ docker build --tag home-assistant:strace .
$ docker run --rm --publish=8123:8123/tcp home-assistant:strace \python3 -m homeassistant --config /config
# now open https://127.0.0.1:8123/ in the browser

❯ 结果

我们使用 docker inspect 命令将生成的图像的大小与原始图像的大小进行了比较。

图像

大小,MiB

尺寸, %

home-assistant:strace

590

31

ghcr.io/home-assistant/home-assistant:stable

1886

100

我们成功将镜像大小缩小了 69%。在这种情况下,最重要的是 Raspberry Pi Zero 可以加载新镜像并运行,而不会遇到磁盘空间限制。

❯ 限制

一个明显的限制 strace 是前端文件不会自动复制,因为只有在发出相应的 HTTP 请求时才会读取它们。虽然某些 HTTP 请求可以使用 [ ] 进行 curl,但通常需要所有前端文件。将它们全部复制到最终镜像中要简单得多。

❯ 您自己的图像

 

使用自己的 Docker 镜像比使用第三方镜像容易得多。您可以将程序编译为静态或动态链接的二进制文件,然后使用工具 patchelf 复制依赖项和解释器。本节将介绍如何为 Rust、Go 和 C/C++ 编译静态二进制文件。通常, 使用musl库 及其附带工具 来构建项目musl-gcc,但对于某些语言来说,此过程更简单。

❯ 静态 Rust 二进制文件

要在项目中使用该库musl,您需要安装基于它的工具链musl,然后为目标平台编译该项目。

$ rustup toolchain add stable --target x86_64-unknown-linux-musl 
# here we remove debugging information and optimize for size
$ env RUSTFLAGS='-Copt-level=z -Cstrip=symbols' \cargo build --release --target x86_64-unknown-linux-musl

现在,您构建一个仅包含最终二进制文件的 Docker 镜像。

FROM scratch
COPY target/x86_64-unknown-linux-musl/release/app /bin/app
CMD ["/bin/app"]

如您所见,生成的镜像仅包含一个二进制文件,没有任何依赖项。因此,在使用静态二进制文件时,Docker 仅充当一种便捷的分发机制。

❯ Go 静态二进制文件

Go 不使用库musl,但它有自己的静态实现libc。这使得编译静态二进制文件更加容易。

$ env CGO_ENABLED=0 go build -ldflags '-s -w' -o app ./cmd/app

现在我们以与 Rust 二进制文件相同的方式构建 Docker 镜像。

FROM scratch
COPY app /bin/app
CMD ["/bin/app"]

❯ 静态 C/C++ 二进制文件

在本例中,我们将尝试用 [ ] 替换 C/C++ 编译器 musl-gcc ,并使用链接标志 [ ] 在 GCC 中激活静态编译 -static。这还需要重新编译所有依赖项。正因如此,这种方法对于由于某种原因而倾向于动态链接的依赖项尤其成问题。例如,使用不支持动态链接的 GNU libc 功能时、动态加载其他库时,或者使用难以手动重新配置静态链接的复杂构建指令时。这就是为什么在处理 C/C++ 二进制文件时通常使用 [ ] 的原因 patchelf

以下清单显示了如何编译静态二进制文件 cmake

$ cat > CMakeLists.txt << 'EOF'
project (HelloWorld)
add_executable (app app.c)
EOF$ cat > app.c << 'EOF'
#include <stdio.h>
int main() {printf("Hello world\n");return 0;
}
EOF$ mkdir build-musl
$ cd build-musl
$ env CC=musl-gcc LDFLAGS='-static' cmake -DCMAKE_BUILD_TYPE=Release ..
$ make
[ 50%] Building C object CMakeFiles/app.dir/app.c.o
[100%] Linking C executable app
[100%] Built target app
$ ldd ./appnot a dynamic executable

❯ 结论

 

有很多方法可以减少 Docker 镜像的大小:

  • 仅使用包含所需的依赖项 patchelf

  • 仅使用包含必要的文件 strace

  • 将您自己的程序编译成包含所有依赖项的静态二进制文件。

平均而言,我们可以将镜像大小减少约 50%(至少根据我们的实验)。紧凑的 Docker 镜像方便在资源有限的设备(例如 Raspberry Pi Zero)上运行。然而,减少潜在的攻击面对平台的益处最大,尤其是在您的镜像不包含 wget、 curl或 Shell 解释器等工具的情况下。

 

http://www.dtcms.com/a/434471.html

相关文章:

  • 513.找树左下角的值(二叉树算法题)
  • LeetCode:84.完全平方数
  • 《API网关性能优化指南:从请求拥堵到毫秒级响应,并发下的架构重构实践》
  • 免费p2p网站建设企业管理系统开发平台
  • 报告派研读:2025年电力设备及新能源行业深度报告
  • 站长之家源码垂直电商平台有哪些?
  • K8s中的ETCD存储机制
  • 【精品资料鉴赏】397页WORD详解智慧城市顶层设计方案
  • 16种粮食谷物分类数据集5300张17类别
  • 2025基于springboot的网上蛋糕销售系统
  • SSE是什么?SSE解决什么问题?在什么场景使用SSE?
  • 算法偏见的解药:将敏捷“灵魂”注入AI伦理
  • 基于前端+Node.js 的 Markdown 笔记 PDF 导出系统完整实战
  • lesson71:Node.js与npm基础全攻略:2025年最新特性与实战指南
  • 购买域名后 可以做网站么灰色关键词排名优化
  • 专业做家具的网站小程序注册方法
  • OWASP ZAP 工具安全测试介绍
  • Git介绍 常用命令
  • 教育机构作图:含拼团 / 课程封面模板,适配小程序
  • linux内核时间定时器延时
  • 远程安装vps相关软件记录
  • 山东高端网站建设方案宁波网站建设那家好
  • x86_64 centos7.2 上用aarch64-linux-gnu-gcc4.8.5交叉编译qt5.11.3
  • GLib线程池全面解析:原理、应用与性能优化
  • 东莞网站设计网址电影网站建设方案ppt模板下载
  • 51单片机-驱动LCD1602液晶显示屏教程
  • 【C++哲学】面向对象的三大特性之 多态
  • Python - 100天从新手到大师:第二十六天Python操作Word和PowerPoint文件
  • 算法基础 典型题 前缀和
  • 广告网站制作多少钱wordpress修改密码后还是登陆不了