在我看来,最近4年来容器创新中最令人悲伤的事情,就是构建容器镜像构建机制缺乏创新。一个容器镜像是一个由镜像内容的压缩包和一些JSON文件组成的压缩包。容器的基础镜像是一个完成的根文件系统和一些描述用的JSON文件。然后用户可以在上面增加层,每个增加的层会形成一个压缩包和记录变化的JSON文件。这些层和基础镜像一起打包组合成了容器镜像。
基本上所有人都通过docker build和Dockerfile格式文件来构建镜像。上游docker在几年前就已经停止接受修改和增强Dockerfile格式和构建方式的pull request。在容器进化过程中,Dockerfile扮演了重要的角色。开发和运维可以简单直接的方式构建镜像。然而在我看来,Dockerfile只是一个简化的bash脚本,到目前为止还有好多问题没有解决。例如:
为了构建容器镜像,Dockerfile必须依赖Docker守护进程
目前为止还没有人创建出标准的工具,可以独立于Docker命令来创建OCI格式镜像。
诸如ansible-containers和OpenShift S2I(Source2Image)等工具,仍然在底层使用了Docker引擎。
Dockerfile中的每一行都会创建一个新的镜像,这能够提升开发阶段构建镜像的效率,因为工具可以知道Dockerfile中的每一行是否有修改,如果没有修改,之前构建的镜像可以被复用而无需重新构建。但是这会导致产生大量的层。
因为这个问题许多人都要求提供一个合并层的方式以限制层的数量。最终上游docker已经接受了部分建议满足了该需求。
为了能够从安全的站点拉取镜像内容,通常用户需要一些安全措施。例如用户需要有RHEL认证和订阅授权才能够在镜像中增加RHEL内容。
这些密钥最终会保存在镜像的层中,开发者需要在这些层中移除它们。
为了能够在构建镜像的时候能够挂载卷,我们在自己分发的atomic项目和docker包中增加了-v参数,但是上游docker没有接受这些补丁。
构建出来的组件最终被放置在容器镜像中。因此,虽然Dockerfile对于刚开始构建的开发者,可以更好的了解整个构建过程,但是这对于大规模企业环境来说不是一个高效的方式。另外,在使用自动化容器平台之后,用户不会关注构建OCI标准镜像的方式是否高效。
出发吧buildah在2017年DevConf.cz上,我让我们团队的Nalin Dahyabhai看看称之为containers-coreutils的构建工具,本质上这是一个使用了containers/storage库和containers/image库的一系列命令行工具,它们可以模仿Dockerfile的语法。Nalin将其命令为buildah,用来取笑我的波士顿口音。使用一些buildah原语,我们就可以构建一个容器镜像:
mnt=$(buildah mount $ctr):
挂载刚创建的容器镜像($ctr)。
返回挂载点路径。
现在可以使用这个挂载点写入内容。
dnf install httpd –installroot=$mnt:
我们可以使用主机系统上的命令将内容写入到容器中,这意味着我们可以把密钥放在主机上,而不用放入到容器中,另外构建工具也可以保存在主机上。
dnf等命令也不用事先安装到容器中,Python等依赖也是,除非应用程序依赖。
cp foobar $mnt/dir:
我们可以使用bash支持的任何命令来填充容器。
buildah commit $ctr:
我们可以在需要的时候创建层。层的创建由用户控制而不是工具。
buildah config --env container=oci --entrypoint /usr/bin/httpd $ctr:
在Dockerfile中支持的命令也可以指定。
buildah run $ctr dnf -y install httpd:
buildah同样支持run命令,它不依赖容器运行时守护进程,而是通过执行runc直接在锁定的��器中执行命令。
buildah build-using-dockerfile -f Dockerfile .:
buildah同样支持使用Dockerfile来构建镜像。
我们希望将类似ansible-containers和OpenShift S2I这样的工具改成buildah,而非依赖一个容器运行时守护进程。
构建容器镜像和运行容器采用相同的运行时环境,对于生产环境还会遇到一个大问题,既针对容器安全需要同时满足二者的权限需求。通常构建容器镜像的时候需要远多于运行容器所需要的权限。例如,默认情况下我们会允许mknod能力。mknod能力允许进程能够创建设备节点,但是在生产环境几乎没有应用程序需要这个能力。在生产环境中移除mknod能力能够让系统变得更加安全。
另一个例子是我们会默认给容器镜像以读写权限,因为安装进程需要将软件包安装到/usr目录中。但是在生产环境中,我个人建议应该将所有容器运行在只读模式。容器中的进程应该只能允许写入tmpfs或者挂载到容器内的卷上。通过将构建镜像和运行容器分离,我们可以修改这些默认设置,让运行环境更加安全。
Kubernetes的运行时抽象:CRI-O