原文:https://learn.openshift.com/subsystems/container-internals-lab-2-0-part-2
一、镜像层和仓库:检查基础镜像、层和历史
本节的目的是了解基础镜像和多层镜像(存储库)之间的区别。此外,请尝试理解镜像层和存储库之间的区别。
可以使用podman history命令来查看存储仓库中的所有层。但是ubi7是基础镜像,它没有父镜像,设计的目的就是构建其他镜像:
podman history registry.access.redhat.com/ubi7/ubi:latest
现在,让我们来看看最小基本镜像,它是红帽通用基本镜像(Universal Base Image UBI)集合的一部分。请注意,它相当小:
podman history registry.access.redhat.com/ubi7/ubi-minimal:latest
现在用下面的Dockerfile来构建一个多层镜像:
podman build -t ubi7-change -f ~/labs/lab2-step1/Dockerfile
使用 podman images 命令来查看镜像:
然后可以继续使用 podman history 命令来查看这个新生成的镜像的所有层:
注意:第一个镜像ID(底部)是和 registry.access.redhat.com/ubi7/ubi 镜像相匹配的,从可信的来源(也就是拥有出处或维护托管链)构建可信的基础镜像非常重要。容器存储库是由层组成的,但是我们经常将它们简单地称为“容器镜像”或容器。在架构系统时,我们必须使用精确的语言,否则最终会给用户带来混乱。
二、镜像URL:将业务需求映射到URL、名称空间、存储库和标签
现在我们来检查URL的不同部分,最常见的就是只指定仓库名字:
podman inspect ubi7/ubi
但是,到底发生了什么?与DNS类似,podman命令行正在注册服务器上解析存储库的完整URL和标签。下面的命令会给你完全相同的结果:
podman inspect registry.access.redhat.com/ubi7/ubi:latest podman inspect registry.access.redhat.com/ubi7/ubi podman inspect ubi7/ubi:latest podman inspect ubi7/ubi
下面继续使用相同的Dockerfile创建一个新的镜像,注意添加了一个 test 标签:
podman build -t ubi7:test -f ~/labs/lab2-step1/Dockerfile
现在继续使用 podman inspect ubi7 命令,发现解析失败了。尝试更加完整的URL,加上test标签:podman inspect ubi7:test ,这样就可以解析成功了。
请注意,podman解析容器镜像的方式类似于DNS解析。每个容器引擎都是不同的,Docker实际上会解决一些podman没有解决的问题,因为没有关于如何解析镜像uri的标准。如果您测试的时间足够长,您会发现对名称空间、存储库和标签解析的许多其他警告。通常,最好总是使用完整的URI,指定服务器、名称空间、存储库和标签。在构建脚本时请记住这一点。容器看起来很简单,但是你需要注意细节。
三、镜像内部:检查容器镜像中的库、解释器和操作系统组件
在本练习中,我们将了解容器镜像中的内容。 Java特别有趣,因为它使用glibc,即使大多数人没有意识到。 为了证明这一点,我们将使用ldd命令,该命令向您显示链接二进制文件的所有库。 动态链接二进制文件时(二进制文件启动时会加载库),这些库必须安装在系统上,否则二进制文件将无法运行。 特别是在此示例中,您可以看到使JVM以完全相同的行为运行要求以相同的方式进行编译和链接。 换句话说,并不是所有的Java镜像都相同:
podman run -it registry.access.redhat.com/jboss-eap-7/eap70-openshift ldd -v -r /usr/lib/jvm/java-1.8.0-openjdk/jre/bin/java
linux-vdso.so.1 => (0x00007fff2b2b6000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fd5dac67000) libz.so.1 => /lib64/libz.so.1 (0x00007fd5daa50000) libjli.so => /usr/lib/jvm/java-1.8.0-openjdk/jre/bin/../lib/amd64/jli/libjli.so (0x00007fd5da841000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fd5da63d000) libc.so.6 => /lib64/libc.so.6 (0x00007fd5da279000) /lib64/ld-linux-x86-64.so.2 (0x000056134440b000) Version information: /usr/lib/jvm/java-1.8.0-openjdk/jre/bin/java: libjli.so (SUNWprivate_1.1) => /usr/lib/jvm/java-1.8.0-openjdk/jre/bin/../lib/amd64/jli/libjli.so libc.so.6 (GLIBC_2.2.5) => /lib64/libc.so.6 /lib64/libpthread.so.0: ld-linux-x86-64.so.2 (GLIBC_2.2.5) => /lib64/ld-linux-x86-64.so.2 ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2 ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2 libc.so.6 (GLIBC_2.14) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.3.2) => /lib64/libc.so.6 libc.so.6 (GLIBC_PRIVATE) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.2.5) => /lib64/libc.so.6 /lib64/libz.so.1: libc.so.6 (GLIBC_2.3.4) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.14) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.4) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.2.5) => /lib64/libc.so.6 /usr/lib/jvm/java-1.8.0-openjdk/jre/bin/../lib/amd64/jli/libjli.so: libdl.so.2 (GLIBC_2.2.5) => /lib64/libdl.so.2 libpthread.so.0 (GLIBC_2.2.5) => /lib64/libpthread.so.0 libc.so.6 (GLIBC_2.14) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.3) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.4) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.2.5) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.3.4) => /lib64/libc.so.6 /lib64/libdl.so.2: ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2 libc.so.6 (GLIBC_PRIVATE) => /lib64/libc.so.6 libc.so.6 (GLIBC_2.2.5) => /lib64/libc.so.6 /lib64/libc.so.6: ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2 ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2 $
动态脚本语言同样会根据系统库进行编译和链接:
podman run -it registry.access.redhat.com/ubi7/ubi ldd /usr/bin/python
$ podman run -it registry.access.redhat.com/ubi7/ubi ldd /usr/bin/python linux-vdso.so.1 => (0x00007ffec5ac1000) libpython2.7.so.1.0 => /lib64/libpython2.7.so.1.0 (0x00007fc3c9009000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc3c8ded000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fc3c8be8000) libutil.so.1 => /lib64/libutil.so.1 (0x00007fc3c89e5000) libm.so.6 => /lib64/libm.so.6 (0x00007fc3c86e3000) libc.so.6 => /lib64/libc.so.6 (0x00007fc3c8314000) /lib64/ld-linux-x86-64.so.2 (0x000055bc9df8c000) $
检查像“curl”这样的公共工具,可以演示从操作系统中使用了多少库。首先,启动RHEL Tools container.。这是Red Hat发布的一个特殊映像,包含了在容器环境中进行故障排除所需的所有工具。它很大,但是很方便:
$ podman run -it registry.access.redhat.com/rhel7/rhel-tools bash [root@8f062c6f8209 /]# ldd /usr/bin/curl linux-vdso.so.1 => (0x00007ffd7efa2000) libcurl.so.4 => /lib64/libcurl.so.4 (0x00007fbcb8a56000) libssl3.so => /lib64/libssl3.so (0x00007fbcb87fd000) libsmime3.so => /lib64/libsmime3.so (0x00007fbcb85d4000) libnss3.so => /lib64/libnss3.so (0x00007fbcb82a5000) libnssutil3.so => /lib64/libnssutil3.so (0x00007fbcb8075000) libplds4.so => /lib64/libplds4.so (0x00007fbcb7e70000) libplc4.so => /lib64/libplc4.so (0x00007fbcb7c6b000) libnspr4.so => /lib64/libnspr4.so (0x00007fbcb7a2d000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fbcb7810000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fbcb760c000) libz.so.1 => /lib64/libz.so.1 (0x00007fbcb73f6000) libc.so.6 => /lib64/libc.so.6 (0x00007fbcb7027000) libidn.so.11 => /lib64/libidn.so.11 (0x00007fbcb6df4000) libssh2.so.1 => /lib64/libssh2.so.1 (0x00007fbcb6bc7000) libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007fbcb6979000) libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007fbcb6690000) libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007fbcb645d000) libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007fbcb6258000) liblber-2.4.so.2 => /lib64/liblber-2.4.so.2 (0x00007fbcb6049000) libldap-2.4.so.2 => /lib64/libldap-2.4.so.2 (0x00007fbcb5df4000) librt.so.1 => /lib64/librt.so.1 (0x00007fbcb5beb000) /lib64/ld-linux-x86-64.so.2 (0x000056310eea8000) libssl.so.10 => /lib64/libssl.so.10 (0x00007fbcb5979000) libcrypto.so.10 => /lib64/libcrypto.so.10 (0x00007fbcb5515000) libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007fbcb5305000) libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007fbcb5101000) libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fbcb4ee7000) libsasl2.so.3 => /lib64/libsasl2.so.3 (0x00007fbcb4cca000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fbcb4aa2000) libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fbcb486b000) libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fbcb4609000) libfreebl3.so => /lib64/libfreebl3.so (0x00007fbcb4405000) [root@8f062c6f8209 /]#
让我们看看是什么包交付这些库?OpenSSL和网络安全服务(Network Security Services)库。当在OpenSSL或NSS中发现一个新的CVE(Common Vulnerabilities & Exposures”公共漏洞和暴露)时,需要构建一个新的容器映像来修补它:
[root@8f062c6f8209 /]# rpm -qf /lib64/libssl.so.10 openssl-libs-1.0.2k-19.el7.x86_64 [root@8f062c6f8209 /]# rpm -qf /lib64/libssl3.so nss-3.44.0-7.el7_7.x86_64
对于Apache和大多数其他依赖库实现安全性或深度硬件集成的守护进程和实用程序来说,情况也是类似的:
$ podman run -it registry.access.redhat.com/rhscl/httpd-24-rhel7 bash bash-4.2$ ldd /opt/rh/httpd24/root/usr/lib64/httpd/modules/mod_ssl.so linux-vdso.so.1 => (0x00007fffd959b000) libssl.so.10 => /lib64/libssl.so.10 (0x00007f2cbbcdd000) libcrypto.so.10 => /lib64/libcrypto.so.10 (0x00007f2cbb879000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f2cbb65d000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f2cbb459000) libc.so.6 => /lib64/libc.so.6 (0x00007f2cbb08a000) libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007f2cbae3d000) libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007f2cbab54000) libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007f2cba94f000) libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007f2cba71c000) libz.so.1 => /lib64/libz.so.1 (0x00007f2cba506000) /lib64/ld-linux-x86-64.so.2 (0x00005577bc14d000) libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007f2cba2f5000) libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007f2cba0f1000) libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f2cb9ed8000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f2cb9cb0000) libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f2cb9a4e000) bash-4.2$ rpm -qf /lib64/libcrypto.so.10 openssl-libs-1.0.2k-19.el7.x86_64 bash-4.2$ exit exit $
这意味着只要其中一个库中存在安全漏洞,就需要准备好重建所有容器映像。