浏览 1.5k
本文是我们容器技术系列文章的一部分:
在本文中,我将会探讨有关容器镜像的内容,具体就是如何让容器镜像变得更小,以及这样做有何意义。同时,我将展示一些示例代码和命令,它们可构建一个极小的容器镜像(用于测试)。
镜像存在无需容器,但容器需要运行镜像才能存在。
镜像定义部署应用的方式——例如,要公开哪些端口、应用运行时启动(或入口点)等。
容器中运行单个应用时,无需共享库。毕竟,没有其他应用可以共享代码!
如果在容器中运行,则无需进行这一常见的分离工作。不分离共享库意味着,作为一名开发人员,您只需包含运行应用所需的组件,仅此而已。在容器环境中,这意味着没有共享库。
# podman images
REPOSITORY TAG IMAGE ID ...
docker.io/library/ubuntu latest 9873176a8ff5 ...
docker.io/library/fedora latest 055b2e5ebc94 ...
registry.fedoraproject.org/f33/fedora-toolbox latest af1f279fed20 ...
... CREATED SIZE
... 3 weeks ago 75.1 MB
... 7 weeks ago 184 MB
... 6 months ago 351 MB
减少镜像大小有两种常见的选择:Alpine Linux 和 Red Hat 通用基础镜像 (UBI)。
来自 Red Hat 的 UBI 采用的是另一种方法。UBI 是一组标准化容器镜像,包含一组供开发人员使用的运行时。
# podman images
REPOSITORY TAG IMAGE ID ...
registry.access.redhat.com/ubi8/ubi latest 8215cb84fa58 ...
registry.access.redhat.com/ubi8/ubi-minimal latest 3f32499d4f3a ...
docker.io/library/alpine latest d4ff818577bc ...
... CREATED SIZE
... 2 weeks ago 234 MB
... 2 weeks ago 105 MB
... 3 weeks ago 5.87 MB
# more pausle.c
#include
int main(void) {return pause(); }
一般情况下,我会运行此 gcc 命令来编译应用,并进行几个优化:
# gcc -Os -fdata-sections -ffunction-sections -fipa-pta -W1,--gc-sections -W1,-O1 -W1,--as-needed -W1,--strip-all pausle.c -o pausle-dynamic
结果是一个非常小的二进制文件 (仅有 15 KB):
# ls -lh pausle-dynamic
-rwxr-xr-x. 1 root root 15K Jul 22 22:00 pausle-dynamic
此应用采用动态链接。也就是说,它需要操作系统上的共享库才能运行。这一点可以通过运行 ldd 命令来检查。
# ldd pausle-dynamic
linux-vdso.so.1 (0x00007fffafbe3000)
libc.so.6 => /lib64/libc.so.6 (0x00007fb193983000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb193b5d000)
为了将其构建到容器中,需要使用包含共享库的基础镜像。我使用了 podman 和 Dockerfile 来进行构建,因为它们的使用非常广泛。
# more Dockerfile
FROM registry.access.redhat.com/ubi8/ubi-minimal
ADD pausle-dynamic /
CMD ["/pausle-dynamic"]
我使用了 RedHat UBI 最小镜像(步骤1),并添加了我自己预编译的应用,让容器在开始后运行它(步骤3)。
# podman build --tag=pausle-dynamic .
STEP 1: FROM registry.access.redhat.com/ubi8/ubi-minimal
STEP 2: ADD pausle-dynamic /
--> 344589591c7
STEP 3: CMD ["/pausle-dynamic"]
STEP 4: COMMIT pausle-dynamic
--> 1f72538cf84
1f72538cf84c10ae525e545fb5596840f09d277eccaffae46f6b6a3815339c8b
Podman 镜像命令显示新镜像的大小 (105 MB) 没有超过 ubi-minimal 基础镜像,因为我的应用仅增加了 adds 15 KB。
# podman images
REPOSITORY TAG IMAGE ID ...
localhost/pausle-dynamic latest 1f72538cf84c ...
registry.access.redhat.com/ubi8/ubi-minimal latest 3f32499d4f3a ...
docker.io/library/alpine latest d4ff818577bc ...
... CREATED SIZE
... About a minute ago 105 MB
... 4 weeks ago 105 MB
... 5 weeks ago 5.87 MB
运行此镜像时,可以看到它是可用的并且正在运行。
# podman run -d pausle-dynamic
5905d1ae4dc00b37f47ae4dlef4c7d99d8d5e1bd781da3b1decc436b10f5663b
# podman ps -a
CONTAINER ID IMAGE COMMAND ...
5905d1ae4dc0 localhost/pausle-dynamic:latest /pausle-dynamic ...
... CREATED STATUS
... 4 seconds ago Up 4 seconds ago
可以在新容器内执行 shell,查看我的二进制文件:
# podman exec -it 5905d1ae4dc0 /bin/bash
# ldd pausle-dynamic
linux-vdso.so.1 (0x00007ffd89762000)
libc.so.6 => /lib64/libc.so.6 (0x00007fa65a658000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa65aa1d000)
该应用与我编译并构建到容器中的应用相同。因为它是动态链接的,所以需要在操作系统中运行共享库。这意味着,我的应用需要包含这些库的基础镜像才能运行。这增加了容器的大小,所以它仍然不是我想要的最小值。
可以做两件事来使这个容器镜像小得多。可以静态链接我的代码,意味着我的应用将共享库“捆绑”到二进制文件中。
运行此命令来静态链接:
# gcc -Os -s static -ffunction-sections -fipa-pta -W1,--gc-sections pausle.c -o pausle-dynamic
# strip pausle-static
# ls-lh pausle-static
-rwxr-xr-x. 1 root root 697K Jul 22 22:31 pausle-static
ls 命令显示由此生成的应用二进制文件大小为 697 KB ,比动态链接的应用大很多,原因是库被捆绑到应用中。
现在,如果运行 ldd 命令来显示共享库,将会收到消息,提示可执行文件不是动态的。
# ldd pausle-static
not a dynamic executable
在 Dockerfile 中,使用特殊 no-op 关键字 scratch 来表明没有使用基础镜像。
# more Dockerfile
FROM scratch
ADD pausle-static /
CMD ["/pausle-static"]
现在,运行 podman build 命令,
# 按与之前相同的方式构建镜像:
STEP 1: FROM scratch
STEP 2: ADD pausle-static /
--> 7fb16e85314
STEP 3: CMD ["/pausle-static"]
STEP 4: COMMIT pausle-static
--> f7f7c833975
f7f7c83397545aef51e0ad665def03040d5a06adf50651133184864bd1adaed4
容器的构建方式与动态可执行文件完全相同,但生成的镜像比动态镜像小得多 – 只有 716 KB, 仅比静态编译的二进制文件本身 (697 KB) 大一点。
# podman images
REPOSITORY TAG IMAGE ID ...
localhost/pausle-static latest f7f7c8339754 ...
localhost/pausle-dynamic latest 1f72538cf84c ...
... CREATED SIZE
... 2 minutes ago 716 kB
... 20 minutes ago 105 MB
初始化容器并确认它正在运行:
# podman run -d pausle-static
1748d59be199a797aa01f569b10445787d3f40439fb0b404b22e4226f4f44e09
# podman ps -a
CONTAINER ID IMAGE COMMAND ...
1748d59be199 localhost/pausle-static:latest /pausle-static ...
... CREATED STATUS
... 5 seconds ago Up 6 seconds ago
如果尝试在容器内执行 shell 或运行 ls 命令,将会收到错误消息,提示容器中没有其他应用。这是因为容器不包含操作系统或基础镜像。
# podman exec -it 1748d59be199 /bin/bash
Error: executable file `/bin/bash` not found in $PATH: No such file or directory: OCI not found
# podman exec -it 1748d59be199 /bin/ls
Error: executable file `/bin/ls` not found in $PATH: No such file or directory: OCI not found
构建小型容器镜像在各种场景中都很有用,例如开发和测试。它可显著缩短新镜像的构建时间,包括将镜像推送到远程仓库所花费的时间。
如前所述,较小的容器镜像(尤其是在不使用基础镜像的情况下)也具有更小的攻击面和更少的依赖项,减少了镜像内无关库、依赖项和其他内容的占用空间。而且在大多数情况下,创建小镜像会带来一种整洁匀称的感觉,这让它成为一件非常酷的事情!
请前往NGINX开源社区:
按点赞数排序
按时间排序