拥抱下一代容器化工具:Podman、Buildah 和 Skopeo(1)


俗话说,“工欲善其事,必先利其器。”在我们开始拥抱下一代的容器化工具之前,不妨先来做做准备工作。首先,我们将对这些工具进行概览,了解它们出现的背景以及每一种工具的使用场景。接着,我们介绍如何把它们安装到我们现有的系统中。最后,我们检查这些工具的主要配置项目,并说明大家需要注意的一些地方。一旦完成准备工作,我们就可以起步开跑了。

工具链概览

说到容器化工具,大家应该对 Docker 不会感到陌生吧。那么,为什么不用 Docker,而是另起炉灶去搞别的新工具呢?要回答这个问题,让我们从开放容器倡议谈起。

开放容器倡议

所谓“天下大事,分久必合”,对于容器化市场而言,当然不只 Docker 独一家。比如,CoreOS 这家公司[1]很早便投身于容器化技术,除了 CoreOS 操作系统,他们也推出了可以用来替代 Docker 的容器运行时 rkt。与此同时,容器化行业的其它玩家也在寻求除 Docker 之外的执行容器解决方案。在这样的背景下,整个容器化行业对于想要一个开放而又标准的容器化技术的呼声日渐增高。

在 Linux 基金会的主持下,Docker、CoreOS 及容器化行业的其他重要玩家于 2015 年 6 月宣布成立 Open Containers Initiative。Open Containers Initiative 即开放容器倡议,简称 OCI。OCI 的使命是制定容器化技术的标准,并推动容器化技术朝着更加开放的方向发展。经过 OCI 的牵头和组织,容器运行时规范容器镜像格式规范相继发布。

  • 容器运行时规范针对容器如何运行制定了标准。具体来说,容器在磁盘上看起来像什么,容器中要运行什么应用程序,以及如何执行容器等等,这些都是通过该规范定义的。在 OCI 成立之际,Docker 把从源代码中剥离出来的 libcontainer 库捐给了 OCI。基于这个库开发而成的 runc 于是自然成为了该规范的默认实现。目前,runc 是很多容器管理程序的低级容器执行工具。

  • 容器镜像格式规范定义了容器镜像文件的格式标准。这个规范以 Docker 镜像格式作为基础,并通过定义如何打包容器镜像,以便将其存储在 Registry 镜像仓库中。Docker、rkt 等容器运行时皆宣称支持该规范。

OCI 的出现,促进了容器化技术的创新,涌现出了一批遵循 OCI 容器运行时规范和容器镜像格式规范的新工具。这些工具包括 CRI-OPodmanBuildahSkopeo 等等。

Docker 所面临的问题

容器化社区的有识之士对 Docker 的诟病由来已久。这主要体现在以下几个方面:

  • 具有 daemon(守护进程):从架构上来说,Docker 采用的是 Client/Server(客户端/服务器)模型。一方面,在 Docker 中,不管我们执行什么操作,运行容器也好,构建容器镜像也罢,都需要 Docker CLI(命令行)客户端直接与 Docker 守护进程通信。但有时候,或许我们想在 CI/CD(持续集成/持续交付)中自动化构建容器镜像。在这种情况下,我们的容器镜像构建环境并不想要一个多余的 Docker 守护进程,此时它显然是个累赘。另一方面,即使我们不执行任何操作,Docker 守护进程依然会在后台运行。这样,Docker 守护进程就无谓的增加了服务器资源的额外消耗。因为 Docker 守护进程担负的职责太多,故而使其本身也变得臃肿不堪。于是有人对 Docker 守护进程进行调侃,称其为“胖 daemon”。

  • 需要 root(超级用户)权限:因为 Docker 守护进程绑定到的是 Unix Socket(套接字),而 Unix Socket 由 root 用户所有,所以 Docker 守护进程总是需要在 root 权限下运行。虽然我们可以通过加入 docker 用户组的方式来让普通用户也能够管理容器,但是,由于 docker 用户组所获得的权限跟 root 用户一样,故而仍然有潜在的安全风险。另外,对于完成构建容器镜像这类任务,使用者也许并不想要具有特权的 root 用户,而是具有一般权限的普通用户。为此,Google 还开发了一个名为 Kaniko 的工具。

  • 安全上的担忧:Linux 内核有一个称为 Audit(审计)的安全特性,系统管理员可以通过它来监视发生的安全事件。这些安全事件同时被记录到 audit.log 日志文件,以便查询和跟踪。假设我们想要跟踪 /etc/shadow 文件的安全事件,我们可以执行下列命令:

      root@codeland:~# systemctl start auditd  # 启动服务
      root@codeland:~# auditctl -w /etc/shadow # 加入监视
      root@codeland:~# cat /etc/shadow         # 执行操作
      root@codeland:~# ausearch -f /etc/shadow -i -ts recent # 查询
      type=PROCTITLE msg=audit(03/23/2019 09:59:34.123:1668) :
          proctitle=cat /etc/shadow
      type=PATH msg=audit(03/23/2019 09:59:34.123:1668) :
          item=0 name=/etc/shadow inode=2408902 dev=103:02
          mode=file,600 ouid=root ogid=root rdev=00:00
          nametype=NORMAL cap_fp=none cap_fi=none
          cap_fe=0 cap_fver=0
      type=CWD msg=audit(03/23/2019 09:59:34.123:1668) :
          cwd=/root
      type=SYSCALL msg=audit(03/23/2019 09:59:34.123:1668) :
          arch=x86_64 syscall=openat success=yes exit=3
          a0=0xffffff9c a1=0x7ffcccdf6bad a2=O_RDONLY a3=0x0
          items=1 ppid=10145 pid=13406 auid=xiaodong uid=root
          gid=root euid=root suid=root fsuid=root egid=root
          sgid=root fsgid=root tty=pts9 ses=1 comm=cat
          exe=/usr/bin/cat key=(null)
    

    在输出结果中,我们发现 auid(即 Audit UID)这个重要字段表明执行操作的用户为 xiaodong。这样,我们就把系统中发生的安全事件跟相关用户关联了起来。

      root@codeland:~# docker run --privileged -v /:/host alpine cat /host/etc/shadow
      root@codeland:~# ausearch -f /etc/shadow -i -ts recent
      type=PROCTITLE msg=audit(03/23/2019 10:21:29.711:1676) :
          proctitle=cat /host/etc/shadow
      type=PATH msg=audit(03/23/2019 10:21:29.711:1676) :
          item=0 name=/host/etc/shadow inode=2408902
          dev=103:02 mode=file,600 ouid=root ogid=root
          rdev=00:00 nametype=NORMAL cap_fp=none cap_fi=none
          cap_fe=0 cap_fver=0
      type=CWD msg=audit(03/23/2019 10:21:29.711:1676) :
          cwd=/
      type=SYSCALL msg=audit(03/23/2019 10:21:29.711:1676) :
          arch=x86_64 syscall=open success=yes exit=3
          a0=0x7ffc013eaf7b a1=O_RDONLY a2=0x0 a3=0x0
          items=1 ppid=26576 pid=26592 auid=unset uid=root
          gid=root euid=root suid=root fsuid=root egid=root
          sgid=root fsgid=root tty=(none) ses=unset comm=cat
          exe=/bin/busybox key=(null)
    

    而在 Docker 容器中执行后,同样的 auid 字段却变成了 unset。不幸的是,关联用户的记录丢失了。我们再也无从知晓是谁一手导致的这个安全事件。这个例子说明,对安全审计来说,Docker 显然还不够友好。[2]人们对 Docker 产生安全上的担忧实为正常表现。

或许你会说,既然 Docker 有上述不足,那么我们完全可以改进它嘛,毕竟它是开源软件呀。但事情往往并没有那么简单。举个例子,docker inspect 只能检查本地端 Docker 对象的相关信息。RedHat 的工程师为 docker inspect 开发了一个 --remote 接口。通过这个接口我们可以检查远程端的 Docker 对象信息。当 RedHat 的工程师向 Docker 上游发送 Pull 请求时,却被意外的拒绝了。他们给出的理由是不想让 Docker 命令行客户端变得更复杂。[3]于是,协同改进 Docker 的路也被堵死了。

下一代容器化工具

根据我们使用 Linux 的多年经验和对于容器化技术现状的考察,我们最终从容器化新型工具中选择了 3 款,它们分别是 Podman、Buildah 和 Skopeo。这些工具避免了 Docker 现有的短板,基于“每个程序只干一件事,并将其干好”的 Unix/Linux 哲学而构建,完全遵循 OCI 容器运行时规范和容器镜像格式规范,被容器化技术社区称为下一代容器化工具。

  • Podman:即 Pod Manager 的简称,Pod 这个概念来源于 Kubernetes,意指一组共享存储和网络的容器,并且包含如何运行容器的规格说明。在 Kubernetes 中,Pod 是最小的调度单元。从名称上来看,Podman 好像只是用来管理 Pod 的工具。其实不然,除了能够管理 Pod 之外,Podman 同样能够管理容器、容器镜像和容器卷。Podman 在设计时就考虑到了与 Docker 命令行客户端的兼容性,它有跟 docker 命令大部分相同的子命令和命令选项。

  • Buildah:专门用来构建 OCI 容器镜像的工具,我们可以使用它替代 docker build 所具有的功能。在支持从 Dockerfile 构建容器镜像的同时,Buildah 还能 mount(挂载)容器的根文件系统以便进行修改。此外,Buildah 也能用于自动化脚本中,是与 CI/CD 系统完美集成的理想工具。

  • Skopeo:在 Registry 镜像仓库之间迁移容器镜像和检查容器镜像的工具。另外,我们也可以使用 Skopeo 删除仓库中的容器镜像。不管是公开的 Registry 镜像仓库,还是私有的 Registry 镜像仓库,Skopeo 都能提供友好的支持。

Podman、Buildah 和 Skopeo 都没有后台 Daemon(守护进程),即便是无特权的普通用户也能运行。通过它们,我们可以构建一个与 Docker 完全兼容,然而却更加轻量、灵活和安全的容器化环境。从此,我们能够摆脱对 Docker 的依赖,循着一个更加开放和标准的方向前进。

上述工具的架构如下图所示。

从上到下依次为:

  • 第一层是本书将要详细讨论的高级工具:Podman、Buildah 和 Skopeo。

  • 第二层为库,包括 libpod、image(镜像)、storage(存储)、networking(网络)等,分别提供容器管理、镜像管理、容器及镜像存储、网络支持等功能。

  • 第三层是 Conmon,用来监视 OCI 运行时的工具。

  • 最后一层为 Runc,处于最低级的 OCI 运行时。


  1. CoreOS 后来被 RedHat 收购,再后来 RedHat 又被 IBM 收购。 ↩︎

  2. https://opensource.com/article/18/10/podman-more-secure-way-run-containers ↩︎

  3. https://opensource.com/article/17/7/how-linux-containers-evolved ↩︎