本章内容:
■ Podman是什么
■ 与Docker相比,Podman有什么优势
■ Podman的使用示例
写作这本书是不容易的,因为很多人带着不同的期望和经验来阅读它。你可能已经拥有一些关于容器、Docker或Kubernetes的经验,或者至少对学习更多关于Podman的知识感兴趣,因为你已经听说过它。如果你之前使用或者了解过Docker,会发现在大多数情况下Podman和Docker的工作方式是一样的,但是Podman解决了一些Docker一直存在的问题。其中最重要的两点是,Podman提供了增强的安全性和以非root权限执行命令的能力。这意味着你可以在没有root访问权限(或特权)的情况下使用Podman来管理容器。Podman的设计使其在默认情况下比Docker具有更好的安全性。
Podman和Docker都是开源的(也是免费的)。除此之外,Podman通过命令行界面(CLI)执行的命令与Docker也非常相似。本书将向你讲述如何在单节点上使用Podman作为本地容器引擎来启动容器,你可以在本地启动容器,也可以通过远程的REST API来启动容器。同时,本书也将向你展示如何使用Podman与Buildah、Skopeo这样的开源工具来发现、运行和构建容器。
在正式学习之前,我认为首先定义本书中要用到的术语很重要。在容器领域,像容器编排器、容器引擎和容器运行时这样的术语经常被交替使用,这通常会导致概念混淆。以下是本书对这些术语的总结。
■ 容器编排器: 指将容器分配到多个不同的机器或者节点上的软件项目或产品。编排器通过与容器引擎通信来运行容器。当前主要的容器编排器是Kubernetes,最初它被设计用来与Docker守护进程交互,但是由于Kubernetes主要使用CRI-O或containerd作为其容器引擎,因此Docker正在被逐步淘汰。CRI-O和containerd是专为Kubernetes编排和运行容器而构建的(CRI-O将在附录A中介绍)。此外,还有Docker Swarm和Apache MESOS等其他容器编排器。
■ 容器引擎: 最初被用来实现将容器化应用程序配置在单个本地节点上运行。普通用户、管理员和开发人员都可以直接启动容器引擎。容器引擎可以作为systemd的一个启动项被启动,也可以通过Kubernetes等容器编排器启动。上文提到CRI-O和containerd这两种容器引擎被Kubernetes用来管理本地容器,实际上这两种容器引擎并不打算让普通用户直接使用。Docker和Podman则是被普通用户用来在单台机器上开发、管理和运行容器化应用程序的两种主要的容器引擎。Podman很少会被用在Kubernetes中启动容器,因此,本书中也不会过多介绍Kubernetes相关的知识。Buildah是另外一种容器引擎,仅用于构建容器镜像。
■ OCI (开放容器计划)容器运行时: 它负责完成Linux内核参数的配置,并最终启动容器化应用程序。最常用的两种容器运行时是runc和crun。Kata和gVisor是另外两种容器运行时。附录B将对各种OCI容器运行时的差异进行说明。
图1-1展示了开源容器项目从属的类别。
图1-1 按编排器、引擎和运行时对不同容器开源项目进行分类
Podman是pod manager的缩写。pod是Kubernetes项目中普遍存在的一个概念。一个pod可以有一个或多个容器,这些容器共享相同的命名空间和控制组(cgroups,用于资源约束)。第4章会更深入地介绍pod。Podman可以用来运行单个容器或者pod。Podman的Logo是一组海豹,如图1-2所示。该图案源自爱尔兰人概念里的美人鱼形象。一组海豹就形成了逻辑概念上的pod。
图1-2 Podman的Logo
Podman项目将Podman描述为“用于在Linux系统上开发、管理和运行OCI容器的无守护进程的容器引擎。容器既可以以特权模式运行,也可以以非特权模式运行”。Podman通常用简单的一行“alias docker=podman”来描述,因为Podman可以使用与Docker相同的命令行来完成Docker所能做的几乎所有事情。但正如你在本书中了解的那样,Podman可以做的还远不止这些。理解Docker对于理解Podman并不是必须的,但会很有帮助。
提示 开放容器计划(Open Container Initiative,OCI)是一个标准化组织,其主要目标是围绕容器格式和运行时建立开放的行业标准。可以通过访问https://opencontainers.org了解更多信息。
Podman的上游项目位于github.com上的Containers项目中,如图1-3所示。在该网页上还包括其他容器库和容器管理工具,如Buildah和Skopeo(有关这些工具的说明,参见附录A)。
如1.2.1节中所描述的那样,Podman支持新的OCI格式的镜像,同时也支持旧的Docker(V2和V1)格式的镜像。Podman可以运行来自诸如docker.io和quay.io等容器镜像注册服务器中的任何镜像,还支持数百个其他容器镜像注册服务器。Podman将这些镜像拉到Linux主机上,然后会以与Docker和Kubernetes类似的方式启动这些镜像。同Docker一样,Podman支持所有类型的OCI运行时,包括runc、crun、Kata和gVisor(见附录B)。
本书旨在帮助Linux管理员了解将Podman用作主要容器引擎的好处。你可以通过本书了解如何在尽可能确保系统配置安全的同时,允许普通用户使用容器。Podman最主要的一个使用场景是在诸如边缘设备这样的单节点环境上运行容器化应用程序。Podman和systemd一起使用可以实现在无须人工干预的情况下管理节点上应用程序的整个生命周期。Podman的目标是可以在Linux上自然地运行容器,从而充分利用Linux平台的所有功能。
提示 可以将Podman安装在Linux的多种发行版以及macOS和Windows平台上。如果你想进一步了解如何在你的平台上获取Podman,请参阅附录C。
图1-3 Containers是Podman和其他相关容器工具的开发者站点
应用程序开发者也是本书的目标读者。对开发者来说,Podman是一种以安全方式实现容器化应用程序的很好的工具。Podman允许开发者在所有Linux发行版中创建Linux容器。此外,Podman也可以在macOS和Windows平台上使用。在这些平台上,它可以通过网络与运行在虚拟机或者Linux机器上的Podman服务进行通信。本书将向你介绍如何使用容器、构建容器镜像,然后将容器化应用程序转换成在边缘设备上运行的单节点服务或者基于Kubernetes的微服务。
Podman和其他一些容器工具都是开源项目,这些项目的贡献者来自全世界不同的公司、大学和组织。当然,这些开源项目也欢迎新的贡献者来优化和改进。要了解有关如何加入这个项目的信息,可以参阅附录D。本章首先简要概述一下容器,然后介绍使Podman成为容器领域非常好的工具的一些核心功能。
容器是在Linux系统上运行的一组进程,它们相互隔离。容器确保一个进程组不会干扰系统上的其他进程。恶意进程不能支配系统资源,否则会阻止其他进程任务的顺利执行。我们也要防止恶意容器攻击其他容器、窃取数据或造成拒绝服务攻击。容器的最终目标是允许应用程序安装它们自己版本的共享库,而不会与需要不同版本的相同库的应用程序发生冲突。相反,它们允许应用程序运行在虚拟化环境中,从而使得容器化环境中的应用程序看上去拥有了整个系统。
容器通过以下方式进行隔离。
cgroups在手册页(https://man7.org/linux/man-pages/man7/cgroups.7.html)上被定义为控制组(Control Groups),通常被称为cgroups,是Linux内核的一项功能,它允许将进程组织成分层的组,并对这些分层的组实现多种类型的资源限制和监控。
cgroups控制的资源实例如下。
■ 一组进程可以使用的内存量。
■ 进程可使用的CPU核数。
■ 进程可使用的网络资源数量。
cgroups的基本思想是防止一组进程独占某些系统资源,使得其他进程无法使用。
内核中的很多安全工具可以实现容器间的相互隔离。安全限制的目标是限制特权升级,并防止一组流氓进程对系统实施破坏性行为,比如:
■ 通过放弃一些Linux能力来限制root用户的权限。
■ 通过SELinux控制对文件系统的访问。
■ 只读访问内核文件系统。
■ 通过seccomp限制内核中可用的系统调用。
■ 通过用户命名空间将主机上的一组UID映射到另一组UID,从而允许访问有限的特权环境。
表1-1提供了进一步的信息和链接,可以通过这些链接获取有关这些安全功能的更多细节。
表1-1 高级Linux安全功能
Linux内核使用命名空间来创建一个虚拟化环境。在这个虚拟化环境中,一组进程看到的是一组资源,另一组进程则看到的是另一组不同的资源。这些虚拟化环境使进程无法看到系统的其他部分,感觉像一个虚拟机(VM),且没有额外的系统开销。下面给出命名空间的若干示例。
■ 网络命名空间:限制对主机网络的访问,但是提供了对虚拟网络设备的访问。
■ 挂载命名空间:限制只能看到容器文件系统,而不能看到所有的文件系统。
■ PID命名空间:限制容器进程只能看到容器内的进程,而看不到系统上的其他进程。
这些容器技术在Linux内核中已经存在很多年了。用于隔离进程的安全工具始于20世纪70年代的UNIX,而SELinux在2001年就已经存在。命名空间和控制组功能分别在2004年和2006年被引入。
提示 Windows容器镜像也是存在的,但是本书主要关注的是基于Linux的容器。即使在Windows上运行Podman,实际上使用的还是Linux容器。在macOS上使用Podman的方法可以参阅附录E。在Windows上使用Podman的方法可以参阅附录F。
直到Docker项目引入了容器镜像和容器镜像注册服务器等概念,容器技术才真正开始被广泛使用。可以这么说,它们创造了一种新的软件交付方式。
传统的在Linux系统上安装多个软件应用程序可能会导致依赖管理问题。在容器出现之前,可以使用像RPM和Debian Packages这样的软件包管理器来打包软件。这些软件包安装在主机上,并且共享包括共享库在内的所有内容。当开发者团队测试他们的代码时,在主机上运行时可能一切都正常。而当质量工程师团队在不同的机器上用不同的软件包测试软件时,就可能遭遇失败。两个团队需要一起努力才能达成期望的效果。软件最终交付给客户,这些客户可能有许多不同的配置或安装了不同版本的软件依赖,而这些问题也会导致应用程序不可用。
容器镜像通过将所有软件捆绑到一个单元中的方式,解决了依赖管理问题。你可以将所有软件库、可执行文件和配置文件一起交付。软件通过容器技术与主机隔离。通常,应用程序需要跟主机交互的唯一部分是主机内核。
开发人员、质量工程师和客户运行完全相同的容器化环境和应用程序,有助于保证运行环境的一致性,并限制了可能由错误配置引起的bug数量。
人们经常将容器与虚拟机进行比较,因为两者都具有在单个节点运行多个被隔离的应用程序的特性。当使用虚拟机时,你需要管理整个虚拟机操作系统和被隔离的应用程序。你需要管理不同内核、init 系统、日志系统、安全更新、备份等的完整生命周期。系统不仅需要处理应用程序的开销,还需要处理整个操作系统的运行开销。在容器世界里,你运行的只有容器化应用程序,不需要进行操作系统管理,也没有其他开销。图1-4展示了3个应用程序在3个不同的虚拟机中运行。
图1-4 在3个虚拟机中运行3个应用程序的物理机
使用虚拟机时,你最终需要管理四个操作系统。而使用容器时,这3个应用程序仅在其所需的用户空间下运行,你只需要管理一个操作系统,如图1-5所示。
图1-5 以容器化方式运行3个应用程序的物理机
将应用程序打包在容器镜像中允许在同一个物理机安装多个有冲突要求的应用程序。例如,一个应用程序可能需要的C库版本与另一个应用程序所需的C库版本不同,使得这些应用程序不能同时安装。图 1-6 展示了在不使用容器的情况下运行在操作系统上的传统应用程序。
容器可以在其容器镜像中包含正确的C库,每个镜像都可能包含特定于容器应用程序的不同版本的库。你可以运行来自完全不同版本的应用程序。
如图1-7所示,容器让运行相同应用程序的多个实例变得非常简单。容器镜像鼓励将单个服务或者应用程序打包到单个容器中。通过容器,你可以很方便地通过网络建立多个应用程序之间的连接。
你可以构建3个不同的容器镜像,然后通过将它们连接在一起来构建微服务,而不是设计具有Web前端、负载均衡器和数据库的单体应用程序。微服务使得你和其他用户可以尝试运行多个数据库和Web前端,然后将它们编排在一起。容器化的微服务使软件的共享和复用成为可能。
图1-6 运行在同一个服务器上的传统的LAMP技术栈
(Linux + Apache + MariaDB + PHP/Perl应用程序)
图1-7 LAMP技术栈被打包成单独的微服务容器。由于容器通过网络进行通信,
因此它们可以轻松地移动到其他虚拟机中,使复用变得更加容易
一个容器镜像包含3个组件。
■ 一个包含运行应用程序所需的所有软件的目录树。
■ 一个描述rootfs内容的JSON文件。
■ 一个被称为manifest列表的JSON文件,用于将多个镜像连接在一起以支持不同的硬件架构。
目录树被称为rootfs(根文件系统),该组件的布局类似Linux系统的根目录(/)。
第一个JSON文件中定义了在rootfs中运行的可执行文件、工作目录、要使用的环境变量、可执行文件的维护者以及其他有助于识别镜像内容的标签。你可以通过使用podman inspect命令的方式来查看这个JSON文件。
$ podman inspect docker:/ /registry.access.redhat.com/ubi8
{
…
"created": "2022-01-27T16:00:30.397689Z", ◁--- 镜像创建的日期
"architecture": "amd64", ◁--- 镜像的架构
"os": "linux", ◁--- 镜像的操作系统
"config": {
"Env": [ ◁--- 镜像开发人员想要在容器中设置的环境变量
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"container=oci"
],
"Cmd": [ ◁--- 在容器启动时要执行的默认命令
"/bin/bash"
],
"Labels": { ◁--- 用于帮助描述镜像内容的标签。这些字段可以是任意形式的,不影响镜像的运行方式,但可用于搜索和描述镜像
"architecture": "x86_64",
"build-date": "2022-01-27T15:59:52.415605",
…
}
第二个JSON文件即manifest列表,允许arm64机器上的用户拉取与amd64机器上名称相同的镜像。Podman会根据机器的默认硬件架构使用此manifest列表来拉取对应镜像。Skopeo是一个工具,它使用与Podman相同的底层库,可在github.com/containers/skopeo(参见附录A)上查看其源代码。Skopeo可以提供更低级别的信息输出,以检查容器镜像的结构。在以下示例中,使用skopeo命令和--raw选项来检查registry.access.redhat.com/ubi8镜像的manifest规范。
$ skopeo inspect --raw docker:/ /registry.access.redhat.com/ubi8
{
"manifests": [
{
"digest": "sha256:cbc1e8cea
8c78cfa1490c4f01b2be59d43ddbb
ad6987d938def1960f64bcd02c", ◁--- 当架构和操作系统匹配时,精确拉取的镜像摘要
"mediaType": "application/vnd.docker.distribution.manifest.v2+json", ◁--- mediaType描述了镜像的类型,如OCI、Docker等
"platform": {
"architecture": "amd64", ◁--- 镜像的架构摘要:amd64
"os": "linux" ◁--- 镜像的操作系统摘要:Linux
},
"size": 737
},
{
"digest": ◁--- 这个段落指向一个不同架构(arm64)的镜像
"sha256:f52d79a9d0a3c23e6ac4c3c8f2ed8d6337ea47f4e2dfd46201756160ca193308",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm64",
"os": "linux"
},
"size": 737
},
…
}
镜像使用Linux tar工具将rootfs和JSON文件打包在一起。然后,这些镜像被存储在称为容器镜像注册服务器(例如docker.io、quay.io和Artifactory)的Web服务器上。Podman等容器引擎可以将这些镜像拷贝到一个物理机上并将它们解压到文件系统中。然后,容器引擎会将镜像的JSON文件、引擎内置的默认值和用户的输入进行合并来创建一个新的容器OCI运行时规范的JSON文件。这个JSON文件是用来描述如何运行该容器化应用程序的。
在最后一步,容器引擎启动一个称为容器运行时(例如runc、crun、Kata或gVisor)的轻量程序。容器运行时会读取容器的JSON文件、内核cgroups、安全约束和命名空间,并最终启动容器的主进程。
OCI标准委员会定义了存储和容器镜像的标准格式,以及容器引擎的标准。它创建了OCI镜像格式,该格式标准化了容器镜像和镜像的JSON文件格式。它还创建了OCI运行时规范,该规范标准化了由OCI运行时使用的容器的JSON文件。OCI标准允许其他容器引擎(例如Podman )通过遵循这些标准,与存储在容器镜像注册服务器中的所有镜像一起工作,并以与包括Docker在内的所有其他容器引擎完全相同的方式运行它们(请参阅图1-7)。
我经常被问到这样一个问题:“既然已经有了Docker,为什么还需要使用Podman呢?”其中一个原因是开源软件的用户可以自由选择。就如操作系统一样,操作系统通常支持各种编辑器、Shell、文件系统和Web浏览器。我相信Podman的设计从根本上是优于Docker的,而且它还提供了多种可提高容器安全性和可用性的功能。
Podman的优势之一在于它是在Docker存在很久之后才推出的。Podman的开发者从完全不同的视角研究了改进Docker的设计方案。因为Docker是开源的,因此Podman使用了Docker的一些代码,同时也符合如开放容器计划(OCI)这样的新标准。Podman和开源社区合作,专注于开发新的功能。
在本节的其余部分,我将介绍改进之处。表1-2描述和比较了Podman和Docker中的一些可用功能。
表1-2 Podman和Docker的功能比较
Podman最重要的功能可能是它有能力在非特权(rootless)模式下运行。很多情况下,你不想给用户提供完全的root访问权限,但是用户和开发者仍需要运行容器和构建容器镜像的权限。需要root权限会导致很多对安全敏感的公司无法广泛使用Docker。然而,除了标准登录账户,在Linux中Podman不需要额外的安全功能就可以运行容器。
你可以将Docker客户端以普通用户运行,方法是将用户添加到Docker用户组(/etc/group),但我认为授予此访问权限是在Linux机器上可以做的最危险的事情之一。访问docker.sock允许你通过运行以下命令获得主机的完全root访问权限。在这个命令中,你会将整个主机操作系统根目录挂载到容器的/host目录上。--privileged标志会关闭所有容器安全性。然后你使用chroot命令将当前进程的主目录更改为/host。这意味着在执行chroot命令后,你将进入操作系统的根目录“/”,并具有完全的root特权。
$ docker run -ti --name hacker --privileged -v /:/host ubi8 chroot /host
#
此时,你已经拥有机器的完整root特权,可以“为所欲为”。当你入侵机器后,你可以简单地执行docker rm命令来删除容器和你的所有操作记录。
$ docker rm hacker
当Docker配置为使用默认文件记录日志时,所有启动容器的记录都会被删除。我认为这比在没有root权限的情况下设置sudo更糟糕。因为至少在使用sudo的情况下,你有机会在你的日志文件中看到sudo的执行。
而使用Podman时,运行在系统上的进程始终由用户拥有,并且没有比普通用户更高的能力。即使你跳出容器,进程仍然以你的UID运行,并且系统上的所有操作都记录在审计日志中。Podman的用户不能简单地删除容器并覆盖他们的痕迹。要获取更多信息,请参见第6章。
提示 Docker现在具有类似Podman的非特权运行能力,但几乎没有人以这种方式运行它。只为了启动一个容器而在你的主目录中启动多个服务,这种方式并没有流行起来。
Docker是作为REST API服务器构建的。可以说Docker基本上是一个包含多个守护进程的客户端-服务器架构。当用户执行Docker客户端时,会借助一个命令行工具连接到Docker守护进程。然后,Docker守护进程会将镜像拉取到它的存储中,接着连接到containerd守护进程,最后containerd守护进程执行用来创建容器的OCI运行时。Docker守护进程是一个通信平台,负责容器中初始进程(PID1)的stdin、stdout和stderr的读写通信。该守护进程将所有输出信息都发送回Docker客户端。用户会认为容器的进程就像当前会话的子进程一样,但其实在背后存在大量的通信过程。图1-8展示了Docker客户端-服务器架构。
图1-8 Docker的客户端-服务器架构。容器是containerd的直接产物,而不是Docker客户端的。
系统内核感知不到客户端程序和容器之间的关系
总之,Docker客户端与Docker守护进程通信,后者与containerd守护进程通信,containerd守护进程最终启动像runc这样的OCI运行时来真正启动容器的PID1。以这种方式运行容器会使得过程比较复杂。多年来,守护进程中的任何一个故障都会使得所有容器宕机,而且往往难以诊断发生了什么。而Podman的核心工程团队都有着深受UNIX系统哲学影响的操作系统背景。
UNIX和C是按照fork/exec计算模型设计的。基本上,当你运行一个新程序时,像Bash shell这样的父程序会fork(派生)一个新进程,然后将新程序作为旧程序的子程序执行。Podman工程团队认为,通过构建一个工具从容器镜像注册服务器中拉取容器镜像、配置容器存储,然后启动OCI运行时作为容器引擎子进程来启动容器,可以使容器更加简单易用。
在UNIX操作系统中,进程可以通过文件系统和进程间通信(IPC)机制共享内容。操作系统的这些特性使得多个容器引擎可以共享存储,而无须运行守护进程来控制访问和共享内容。除了使用操作系统的文件系统所提供的锁机制,容器引擎之间不需要进行通信。后续章节将探讨这种机制的优缺点。图1-9显示了Podman的架构和通信流程。
图1-9 Podman的fork/exec架构。用户启动Podman来执行OCI运行时,
接着OCI运行时启动容器。容器是Podman的直接产物
从根本上来说,Podman是不同于Docker的,因为它是无守护进程的。Podman可以像Docker一样运行所有容器镜像,并使用相同的容器运行时来启动容器。然而,Podman无须多个持续以root权限运行的守护进程就可以实现这些功能。
假设你有一个要在系统启动时运行的Web服务。该Web服务被打包在容器内,因此你需要一个容器引擎。如果使用Docker,你需要将其安装在你的机器上并运行,并且与之相关的每个守护进程都须保持运行状态且接受服务连接。然后,启动Docker客户端以启动Web服务。现在你的容器化应用程序以及所有Docker守护进程都在运行。使用Podman,则仅须使用Podman命令来启动容器,Podman本身并不存在守护进程。Podman创建的容器将持续运行,而且不会产生运行多个守护进程的额外开销。在物联网设备和边缘服务器等低端机器上,更少的开销往往更受欢迎。
Docker的一个非常棒的特性是它具有简单易用的命令行界面。当然,还有其他容器命令行,如RKT、lxc和lxcd,也有自己独立的命令行界面。Podman团队早就意识到,独特的命令行界面并不会有助于Podman获得更多的市场份额。Docker是主导工具,几乎每个想要尝试容器的人都使用Docker命令行界面。此外,如果你在线搜索有关如何使用容器的信息,你几乎肯定会得到Docker命令行的使用示例。所以从一开始,我们就认为Podman必须与Docker命令行相匹配。我们很快就开发了一个命令:alias Docker = Podman。
使用此命令,你可以继续输入Docker命令,但Podman会负责运行你的容器。假如Podman命令行与Docker不同,用户会很容易将其视为Podman的一个bug,并且往往要求Podman做出修改以适应用户的使用习惯。当然,也有一些命令是Podman不支持的,如Docker Swarm。但在大多数情况下,Podman是可以完全替换Docker CLI的。
很多发行版提供了一个名为podman-docker的软件包,该软件包将别名从docker更改为podman并提供手册页的链接。这个别名的修改意味着,当你输入docker ps命令的时候,实际执行的是podman ps命令。如果你运行man docker ps,则会显示podman ps的手册页。图1-10是来自一位Podman用户的推特消息,他将docker命令的别名设置为podman,之后他惊讶地发现他已经使用Podman两个月了,而他一直认为自己使用的是Docker。
早在2018年,Alan Moran就曾发推文说:“我完全忘记了大约两个月前我设置了‘alias docker="podman"’,这真是一场梦。”Joe Thomson回复说:“那是什么提醒了你?”Alan Moran回答:“docker help。”然后Podman help就出现了。
图1-10 关于“alias docker = podman”的推文
Podman可以作为一个由套接字激活的REST API服务来运行,因此允许远程客户端管理和启动Podman容器。Podman支持Docker API以及涉及Podman高级功能的Podman API。通过使用Docker API,Podman支持docker-compose和其他使用docker-py Python绑定的工具。这意味着,即使你的基础设施是使用Docker套接字来启动容器的,你也可以简单地将Docker替换为Podman服务,并继续使用你现有的脚本和工具。第9章会介绍Podman服务。
基于Podman REST API,macOS、Windows和Linux系统上的远程Podman客户端可以与运行在Linux机器上的Podman容器进行交互。附录E和F将介绍如何在macOS和Windows上使用Podman。
systemd是操作系统上最基础的init系统。Linux系统上的init进程是内核启动时启动的第一个进程。因此,init系统是操作系统上所有进程的“祖先”,可以对所有进程进行监控。Podman想把容器的运行和init系统完全结合起来。用户希望在系统启动时使用systemd启动和停止容器。容器需要支持以下操作。
■ 支持容器内的systemd。
■ 支持套接字激活。
■ 支持通过systemd通知机制来告诉systemd容器化应用程序已完全激活。
■ 允许systemd完全管理容器化应用程序的cgroups和生命周期。
基本上,容器是作为systemd单元文件中的服务来运行的。许多开发人员希望在容器内运行systemd,以便在容器内运行多个系统定义的服务。
然而,Docker社区不同意这一点,并拒绝了所有试图将systemd集成到Docker中的“pull request”。他们认为Docker应该管理容器的生命周期,同时也不想接纳那些想要在容器内运行systemd的用户的想法。
Docker社区认为Docker守护进程应该作为进程的控制器,由Docker守护进程管理容器的生命周期,并在启动时启动和停止容器,而不是systemd。然而,问题在于systemd拥有比Docker更多的功能,包括启动顺序、套接字激活、服务就绪通知等。图 1-11 是第一届 DockerCon 会议上Docker员工的实际工牌,反映了他们对systemd的不认同。
图1-11 DockerCon会议上的
Docker员工工牌
开发人员在设计Podman时就希望确保它与systemd完全集成。当你在容器内运行systemd时,Podman以systemd期望的方式设置容器,并允许它以有限权限作为容器的PID1进程运行。通过systemd单元文件,Podman允许你在容器内运行服务,就像服务在操作系统或者虚拟机中运行一样。Podman支持套接字激活、服务通知和很多其他的systemd单元文件功能。Podman使得生成符合最佳实践的systemd单元文件很简单,以便在systemd服务中运行容器。要获取更多信息,请参阅第7章中关于systemd集成的内容。
Containers项目(https://github.com/containers)是Podman、容器库和其他容器管理工具的所在之处,它希望拥抱操作系统的所有功能并将其完全集成。第 7 章将介绍Podman与systemd的集成。
Podman的优势之一在其名称中就可以看出来。前文就曾提到,Podman实际上是pod manager的缩写。正如Kubernetes官方文档所说,“pod(像海豹群一样,因此有了Podman的Logo,或者说是豌豆荚)是一组(一个或多个)容器;这些容器共享存储、网络资源及有关如何运行这些容器的规范声明”。Podman既可以像Docker一样一次创建一个容器,也可以在一个pod中同时管理多个容器。容器的设计目标之一是将服务拆分到多个独立的容器中,即微服务。然后你可以将这些容器关联起来构建一个更大的服务。pod允许你像管理单个实体一样,将多个服务组合在一起形成一个更大的服务。Podman的目标之一是允许创建并使用pod做一些功能验证。图1-12展示了运行在同一个系统上的两个pod,每个pod包含3个容器。
图1-12 运行在一个主机上的两个pod。每个pod运行两个不同的应用容器和一个infra容器
Podman的podman generate kube命令允许你基于运行的容器和pod生成Kubernetes YAML文件,可以在第7章中看到更多与此相关的介绍。
类似地,Podman的podman play kube命令允许用户执行Kubernetes YAML文件并在你的主机上生成pod和容器。我建议在单个主机上使用Podman运行pod和容器,如果是多台机器则建议使用Kubernetes将用户的pod和容器调度到多台机器上运行并通过用户的基础设施管理。其他项目如kind(https://kind.sigs.k8s.io/docs/user/rootless),正在尝试在Kubernetes的引导下使用Podman来运行pod。
Podman等容器引擎支持使用短名称(如ubi8)来拉取镜像,而不需要指定这些镜像所在的注册服务器(registry.access.redhat.com)。完整的镜像名称包含用来拉取镜像的容器镜像注册服务器的名称:registry.access.redhat.com/library/ubi8:latest。表1-3中展示了镜像名称的组成部分。
表1-3 短名称与容器镜像名称映射表
在使用短名称时,Docker被硬编码为总是从https://docker.io拉取镜像。假如你希望从其他容器镜像注册服务器拉取镜像,则必须指定完整的镜像名称。在下面的示例中,我尝试拉取ubi8/httpd-24镜像,但是失败了,原因是容器镜像在docker.io上并不存在。实际上,该镜像位于registry.access.redhat.com上。
# docker pull ubi8/httpd-24
Using default tag: latest
Error response from daemon: pull access denied for ubi8/httpd-24,
repository does not exist or may require 'docker login': denied: requested
access to the resource is denied
所以,如果我想使用ubi8/httpd-24,我必须输入包括容器镜像注册服务器在内的完整名称。
# docker pull registry.access.redhat.com/ubi8/httpd-24
Docker引擎将docker.io作为首选容器镜像注册服务器,优先级高于其他容器镜像注册服务器。然而Podman在设计之初就允许你指定多个注册服务器,就像你可以使用不同的包管理工具(如dnf、yum和apt)来安装软件包一样。你甚至可以从注册服务器列表中将docker.io选项删除。现在如果你尝试使用Podman拉取ubi8/httpd-24,Podman将会为你提供一个注册服务器列表。
$ podman pull ubi8/httpd-24
? Please select an image:
registry.fedoraproject.org/ubi8/httpd-24:latest
▸ registry.access.redhat.com/ubi8/httpd-24:latest
docker.io/ubi8/httpd-24:latest
quay.io/ubi8/httpd-24:latest
当你做出选择后,Podman会记录短名称别名,不再提示和使用之前选择好的容器镜像注册服务器。Podman还支持很多其他功能,如注册服务器黑名单、只拉取已签名镜像、设置镜像的mirror和指定硬编码的短名称,这样使得特定的短名称直接映射到全名称(请查阅第5章)。
Podman支持多种不同的容器镜像源和目标,这些源和目标被称为传输方式(见表1-4)。Podman可以从容器镜像注册服务器和本地容器存储中拉取镜像,还支持以OCI、OCI TAR、传统的Docker TAR、目录格式存储的镜像,以及直接从Docker守护进程中获取的镜像。Podman命令可以轻松地运行每种格式的镜像。
表1-4 Podman支持的传输方式
容器引擎往往有很多内置常量,比如它们运行时使用的命名空间、SELinux是否启用以及容器运行时运行哪些功能。如果使用Docker,这些常量的值大部分都是硬编码的,并且默认不能被修改。对比来看,Podman则具有完全可定制的配置。
Podman有内置的默认值,但是定义了三处存储其配置文件的位置。
■ /usr/share/containers/containers.conf:每个发行版可以定义其想要使用的配置。
■ /etc/containers/containers.conf:可以在这里设置系统级别的参数覆盖。
■ $HOME/.config/containers/containers.conf:只能在非特权模式下指定。
通过这些配置文件,你可以配置自己想要的Podman默认运行方式。你也可以在默认配置下以更高的安全性来运行Podman。
Podman与用户命名空间完全集成。非特权模式依赖用户命名空间,用户命名空间允许将多个UID分配给一个用户。用户命名空间使得在同一个系统上的不同用户之间保持隔离,因此,你可以让多个非特权用户运行具有多个UID的容器,所有用户之间相互隔离。
用户命名空间可以将容器相互隔离。Podman可轻松启动多个具有唯一用户命名空间的容器,然后内核会基于UID分离,将进程与主机用户隔离并使进程彼此之间也隔离开来。
与此不同,Docker仅支持在单独的用户命名空间运行容器,这意味着所有容器都运行在同一个用户命名空间。一个容器里的root用户与另一个容器里的root用户相同。Docker不支持在不同的用户命名空间运行每个容器,这意味着容器从用户命名空间的角度来看会相互攻击。所以即使Docker支持了这种模式,也几乎没有人使用Docker在独立的用户命名空间运行容器。
与Docker一样,Podman不是容器编排器。Podman是一个以非特权或者特权模式在单个主机上运行容器工作负载的工具。如果你想在多台机器上编排运行容器,则需要更高级别的工具。
我认为现在最好的容器编排工具是Kubernetes。Docker也有一个名为Swarm的编排器,曾经有过一些人气,但现在似乎失宠了。由于Podman团队认为Kubernetes是在多台机器上运行和管理容器的最佳方式,所以Podman并不支持Docker的Swarm功能。目前Podman已被用在不同的编排器中并用于网格/HPC计算,开源开发人员甚至将其添加到Kubernetes前端界面。
■ 容器技术已经存在多年,但容器镜像和容器镜像注册服务器的引入为开发人员提供了一种更好的软件交付形式。
■ Podman是一款优秀的容器引擎,适用于几乎所有的单节点容器项目。它对于开发、构建和运行容器化应用程序非常有用。
■ 与Docker一样,Podman易于使用,且具有与Docker完全相同的命令行界面。
■ Podman支持REST API,它允许远程工具和语言(包括docker-compose)与Podman容器一起工作。
■ 与Docker不同,Podman拥有多项显著的功能,包括支持用户命名空间、多种传输方式、可定制镜像注册服务器、与systemd的高度集成、fork/exec模型以及开箱即用的非特权模式等。
■ Podman是一种更安全的容器运行方式。