一、前言
最近在进行OS国产化交流中,了解到部分业务迁移到BClinux 8.2或Anolis 8.2时,原有docker业务需要迁移到新的容器平台:Podman,来完成容器的新的管理。Podman(全称 Pod Manager)是一款用于在 Linux® 系统上开发、管理和运行容器的开源工具。Podman 最初由红帽® 工程师联合开源社区一同开发,它可利用 lipod 库来管理整个容器生态系统。官方声称:Podman - The next generation of Linux container tools,它不仅可以管理 OCI 容器,还可以管理 pod 。
Podman 是一个 RedHat 公司发布的开源容器管理工具,初衷就是 Docker 的替代品,在使用上与 Docker 的相似,但又有着很大的不同。它与 Docker 的最大区别是架构,Docker 是以 C/S 架构运行的,我们平时使用的 docker 命令只是一个命令行前端,它需要调用 dockerd 来完成实际的操作,而 dockerd 默认是一个有 root 权限的守护进程。而Podman 不需要守护进程,直接通过 fork/exec 的形式启动容器,不需要 root 权限。这也是Podman其中一个较大优势,它采用了无守护进程的包容性架构,可以更安全、更简单地对容器进行管理,再加上 Buildah 和 Skopeo 等与之配套的工具和功能,开发人员能够按照自身需求来量身定制容器环境。 它实际与Docker扮演相同的角色,并且在很大程度上与 Docker 兼容,提供几乎相同的命令。总之,Podman是一个基于libpod库开发的容器运行时,用于在Linux操作系统上管理和运行容器。与传统的Docker容器运行时不同,Podman无需依赖Docker守护进程,它可以在不同的Linux发行版中独立运行。它支持常见的容器管理功能,如启动、停止、重启和删除容器,以及构建、推送和拉取容器镜像等。Podman还支持容器的网络和存储管理,可以使用CNI插件创建和管理容器的网络,支持使用多种存储驱动程序,如overlayfs、btrfs和zfs等。
Podman的一个显著特点是它使用的是rootless模式,这意味着它可以在普通用户权限下运行,而不需要root权限。这有助于提高容器运行的安全性和可移植性。同时,Podman支持通过Pods来管理一组相关的容器,这样可以方便地管理复杂的应用程序。
另外,Podman还支持使用systemd来管理容器,这使得它可以更好地集成到Linux系统中,与其他系统服务一起运行和管理。但无论怎样,它作为运行时,Podman的底层主要也是基于Linux的Cgroup和Namespace等技术。
Podman 可以帮助我能实现如下:
管理容器镜像和完整的容器生命周期,包括运行、联网、检查和下线。
为无根容器和容器集运行和隔离资源。
支持 OCI 和 Docker 镜像,以及与 Docker 兼容的 CLI。
打造一个无守护进程的环境,提高安全性并减少闲置资源消耗。
部署 REST API 来支持 Podman 的高级功能。
借助 checkpoint/restore in userspace(CRIU),实施面向 Linux 容器的检查点/恢复功能。CRIU 可以冻结正在运行的容器,并将其内存内容和状态保存到磁盘,以便容器化工作负载能够更快地重新启动。
自动更新容器。Podman 会检测更新的容器是否无法启动,并自动回滚到上一个工作版本。这可以将应用的可靠性提升到新的水平。
相关资料:containerd、Podman、Podman介绍、官网、Podman项目
二、架构和功能
2.1、运行时runtime
"运行时"就是程序运行的时候,也就是指令加载到内存并由CPU执行的时候,实际它就是一套比较底层的纯C语言API,是1个C语言库,包含了很多底层的C语言API,从而更好与Linux内核通信。与之相对应的是“编译时”,其指代码编译的时候,也就是C代码编译成可执行文件的时候,此时指令没有被CPU执行。对应的运行时库就是程序运行的时候所需要依赖的库。
每个环境都有自己的运行时,这里我们主要回顾容器的运行时(runtime),就是运行和管理容器进程、镜像的工具,可以把进程运行在容器中,并可通过SDK或命令来调用Linux的内核功能,从而创建出容器或其他关联容器底层的东西。runtime是在一个OCI(Open Container Initiative)组织中被明确定义的。OCI 发布了两个规范:runtime spec 和 image format spec;这样不同组织和厂商依据该规范开发的容器能够在不同的 runtime 上运行,保证了容器的可移植性和互操作性。 这个规范规定了容器之中应用被放到什么样的环境下、如何运行,比如说容器的根文件系统上哪个可执行文件会被执行,是用什么用户执行,需要什么样的 CPU,有什么样的内存资源、外置存储,还有什么样的共享需求等等。正如前文所述,runtime 就是容器(可理解就是一个独立的进程)真正运行的地方。整个过程中 需要跟操作系统 kernel 紧密协作,为容器提供运行环境。比如:那常见的Java环境举例,Java 程序就可看做是个容器,JVM 则好比是 runtime。JVM 为 Java 程序提供运行环境。同样的道理,容器只有在 runtime 中才能运行。
OCI规范概览:
镜像规范(常被称为“OCI 1.0 images")定义了容器镜像的内容
运行时规范( 常被称为“CRI(Container Runntime Interface) 1.0”或者容器运行时接口)表述了容器的”配置,运行环境和生命周期“。容器运行时接口(Container Runtime Interface, CRI),是指上层应用无需编译就可以支持多种容器运行时的插件接口,通过插件的切换方式应用不同封装的容器。
容器网络接口(CNI)描述了如何在容器内部配置网络接口,不过它是在CNCF下标准化的, 而不是OCI。
目前主流的有三种容器 runtime:
LXC 是 Linux 上老牌的容器 runtime。Docker 最初也是用 lxc 作为 runtime。lxc 是 lxd 的管理工具。
runc 是 Docker 自己开发的容器 runtime,符合 oci 规范,也是现在 Docker 的默认 runtime。Docker公司把其容器核心技术libcontainer捐给OCI(Open Container Intiative)后,更名为runC。Runc 的管理工具是 docker engine,docker engine 包含后台 deamon 和 cli 两部分,大家经常提到的 Docker 就是指的 docker engine。
rkt 是 CoreOS 开发的容器 runtime,符合 oci 规范,因而能够运行 Docker 的容器。Rkt 的管理工具是 rkt cli。
当前后来随着发展,也出现了其他runtime,比如Kata Container、CRI-O等;最先,Docker使用的是LXC但是层次隔离不太完整,所以后来Docker开发了libcontainer,最后演变为了runC。
Runtime 主 要 定 义 了 以 下 规 范 , 并 以 json 格 式 保 存 在/run/docker/runtime-runc/moby/容器 ID/state.json 文件,此文件会根据容器的状态实时更新内容:
版本信息:存放 OCI 标准的具体版本号。
容器 ID:通常是一个哈希值,可以在所有 state.json 文件中提取出容器 ID 对容器进行
批量操作(关闭、删除等),此文件在容器关闭后会被删除,容器启动后会自动生成。
PID:在容器中运行的首个进程在宿主机上的进程号,即将宿主机的那个进程设置为容
器的守护进程。
容器文件目录:存放容器 rootfs 及相应配置的目录,外部程序只需读取 state.json 就
可以定位到宿主机上的容器文件目录。
容器创建:创建包括文件系统、namespaces、cgroups、用户权限在内的各项内容。
容器进程的启动:运行容器启动进程,该文件在
/run/containerd/io.containerd.runtime.v1.linux/moby/容器 ID/config.json。
容器生命周期:容器进程可以被外部程序关停,runtime 规范定义了对容器操作信号的
捕获,并做相应资源回收的处理,避免僵尸进程的出现。
OCI 容器镜像主要包含以下内容:
文件系统:定义以 layer 保存的文件系统,在镜像里面是 layer.tar,每个 layer 保存了
和上层之间变化的部分,image format spec 定义了 layer 应该保存哪些文件,怎么表
示增加、修改和删除的文件等操作。
manifest 文件:描述有哪些 layer,tag 标签及 config 文件名称。
config 文件:是一个以 hash 命名的 json 文件,保存了镜像平台,容器运行时容
器运行时需要的一些信息,比如环境变量、工作目录、命令参数等。
index 文件:可选的文件,指向不同平台的 manifest 文件,这个文件能保证一个镜
像可以跨平台使用,每个平台拥有不同的 manifest 文件使用 index 作为索引。
父镜像:大多数层的元信息结构都包含一个 parent 字段,指向该镜像的父镜像。
参数:
ID:镜像 ID,每一层都有 ID
tag 标签:标签用于将用户指定的、具有描述性的名称对应到镜像 ID
仓库:Repository 镜像仓库
os:定义类型
architecture :定义 CPU 架构
author:作者信息
create:镜像创建日期
容器运行时具有掌控容器运行的整个生命周期,包括镜像的构建和管理、容器的运行和管理等功能。容器运行时接口(Container Runtime Interface, CRI),是指上层应用无需编译就可以支持多种容器运行时的插件接口,通过插件的切换方式应用不同封装的容器。容器运行时向上提供容器调用接口,包括容器生成与销毁的全生命周期管理的功能,向下提供调用接口,负责具体的容器操作事项,例如一个典型的Kubernetes集群中,Kubernetes对具体的容器类型不感知,而通过Kubernetes节点代理kubelet实现了 CRI客户端 API,可以使用任何实现 CRI 服务器 API的容器运行时来管理其节点上的容器和 Pod。
根据容器运行时提供功能,可以将容器运行时分为低层运行时和高层运行时。
1)低层运行时主要负责与宿主机操作系统打交道,根据指定的容器镜像在宿主机上运行容器的进程,并对容器的整个生命周期进行管理。而这个低层运行时,正是负责执行我们前面讲解过的设置容器 Namespace、Cgroups等基础操作的组件。常见的低层运行时种类有:
RunC:传统的运行时,基于Linux Namespace和Cgroups技术实现,代表实现Docker
RunV:基于虚拟机管理程序的运行时,通过虚拟化 guest kernel,将容器和主机隔离开来,使得其边界更加清晰,代表实现是Kata Container和Firecracker
Runsc:runc + safety ,通过拦截应用程序的所有系统调用,提供安全隔离的轻量级容器运行时沙箱,代表实现是谷歌的gVisor
2)高层运行时主要负责镜像的管理、转化等工作,为容器的运行做前提准备。主流的高层运行时主要containerd和CRI-O。
高层运行时与低层运行时各司其职,容器运行时一般先由高层运行时将容器镜像下载下来,并解压转换为容器运行需要的操作系统文件,再由低层运行时启动和管理容器。
另外这里还要注意的一个容器引擎containerd,2020年CNCF基金会宣布Kubernetes 1.20版本将不再仅支持Docker容器管理工具;早前Docker 1.11的Docker Engine里就包含了containerd,而现在则是把containerd从Docker Engine里彻底剥离出来,作为一个独立的开源项目独立发展,目标是提供一个更加开放、稳定的容器运行基础设施。在Docker Engine里containerd相比,独立的containerd将具有更多的功能,可以涵盖整个容器运行时管理的所有需求。另外独立之后containerd的特性演进可以和Docker Engine分开,专注容器运行时管理,可以更稳定。
Containerd是一个工业标准的容器运行时,重点是它简洁,健壮,便携,在Linux和window上可以作为一个守护进程运行,它可以管理主机系统上容器的完整的生命周期:镜像传输和存储,容器的执行和监控,低级别的存储和网络。每个containerd只负责一台机器,Pull镜像,对容器的操作(启动、停止等),网络,存储都是由containerd完成。具体运行容器由runC负责,实际上只要是符合OCI规范的容器都可以支持。
Containerd和docker不同,containerd重点是集成在大规模的系统中,例如kubernetes、Swarm、Mesos等【对于容器编排服务来说,运行时只需要使用containerd+runC,更加轻量,容易管理。】。Containerd 被设计成嵌入到一个更大的系统中,而不是直接由开发人员或终端用户使用。
2.2、Podman 是如何管理容器的?
用户可以通过命令行调用 Podman,从存储库拉取容器并运行它们。Podman 调用配置好的容器运行时来创建运行的容器。由于没有专门的守护进程,Podman 使用 systemd(一种用于 Linux 操作系统的系统和服务管理器)来进行更新并让容器在后台保持运行。通过将 systemd 和 Podman 集成,我们可以为容器生成控制单元,并通过自动启用 systemd 的前提下运行它们。
用户可以在系统上管理自有的存储库,也可通过 systemd 单元来控制自有容器的自动启动和管理。允许用户管理自己的资源并使容器以无根方式运行,可以阻止诸如使 /var/lib/containers 目录可被写入等不良做法,或防范可能会导致应用暴露于额外安全问题的其他系统管理做法。这样也可确保每一用户具有单独的容器和镜像集合,并可在同一主机上同步使用 Podman,而不会彼此干扰。用户完成自己的工作时,可将变更推送到共有的镜像仓库,将他们的镜像共享给其他人。
Podman 也可部署 RESTful API(REST API)来管理容器。REST 是表述性状态传递的英文缩写。REST API 是遵循 REST 架构规范的应用编程接口,支持与 RESTful Web 服务进行交互。借助 REST API,您可以从 cURL、Postman 和 Google 的 Advanced REST 客户端等许多平台调用 Podman。
三、部署配置
四、工具应用
4.1
4.2、Podman 桌面管理工具
Podman Desktop 使你能够轻松地使用本地环境中的容器,Podman Desktop 无守护进程,它利用 Podman Engine 提供轻量级且无守护程序的容器工具。该工具允许浏览、管理容器的生命周期、检查容器、来自不同容器引擎的镜像等。另,它专注于将 Podman 作为默认打包的容器引擎的同时,还兼容了其他容器引擎。Podman Desktop 具有以下一些特性:
- 管理容器:列出、搜索、检查、连接、运行和停止容器。
- 构建、拉取和推送镜像:从该工具构建镜像,通过管理仓库来拉取和推送镜像,从这些镜像运行容器。
- 管理 Podman 资源:查看分配的内存、CPU 和存储;如果需要,创建新机器
- 兼容Docker desktop扩展:指定 Docker desktop扩展的 OCI 镜像以将其导入。
- Podman Desktop 能够通过添加包装器来拦截 API 调用来使用 Docker Desktop UI 插件。通过添加 Docker Desktop 扩展,你可以扩展 Podman Desktop 的功能。还可以在后台使用插件来管理不同的容器引擎。通过添加新插件。
资源获取:二进制包
五、附录:
5.1、Containerd回顾
如上图所示,docker为我们提供了抽象的容器调用接口,而其内部功能的具体实现却是调用的contanierd;2016年,docker 把负责容器生命周期的模块 containerd拆分出来,并将其捐赠给了社区。
Containerd的特点:
简洁的基于 gRPC 的 API 和 client library。
完整的 OCI 支持(runtime 和 image spec)。
同时具备稳定性和高性能的定义良好的容器核心功能。
一个解耦的系统(让 image、filesystem、runtime 解耦合),实现插件式的扩展和重用。
Containerd的作用:
管理容器的生命周期(从创建容器到销毁容器)。
拉取/推送容器镜像。
存储管理(管理镜像及容器数据的存储)。
调用 runC 运行容器(与 runC 等容器运行时交互)。
管理容器网络接口及网络。
使用 bucketbench 对 Docker、crio 和 Containerd 的性能测试结果,包括启动、停止和删除容器,以比较它们所耗的时间,可以发现Containerd 在各个方面都表现良好,总体性能优于 Docker 和 crio。
Containerd架构:Containerd 采用标准的 C/S 架构:服务端通过 GRPC 协议提供稳定的 API;客户端通过调用服务端的 API 进行高级的操作。为了实现解耦,Containerd 将不同的职责划分给不同的组件,每个组件就相当于一个子系统(subsystem)。连接不同子系统的组件被称为模块。containerd 需要调用 runC,所以在安装 containerd 之前请先安装 runC。
containerd 的技术架构:
Containerd 被分为三个大块: Storage 、 Metadata 和 Runtime。
Containerd 两大子系统为:
Bundle : 在 Containerd 中,Bundle 包含了配置、元数据和根文件系统数据,你可以理解为 容器的文件系统。而 Bundle 子系统允许用户从镜像中提取和打包 Bundles。
Runtime : Runtime 子系统用来执行 Bundles,比如创建容器。其中,每一个子系统的行为都由一个或多个模块协作完成。
【几个概念】:
1)containerd 是一个高级容器运行时,又名容器管理器。简单来说,它是一个守护进程,在单个主机上管理完整的容器生命周期:创建、启动、停止容器、拉取和存储镜像、配置挂载、网络等。
2)ctr 是作为 containerd 项目的一部分提供的命令行客户端。该ctr界面 [显然] 与 Docker CLI不兼容,乍一看,可能看起来不太用户友好。因为它的主要受众是测试守护进程的容器开发人员。ctr + containerd比docker + dockerd更接近实际的容器。
3)nerdctl 是一个相对较新的containerd命令行客户端。与ctr不同,nerdctl的目标是用户友好和docker兼容。在某种程度上,nerdctl + containerd可以无缝地替代docker + dockerd。
4)crictl 是一个命令行客户端,用于 [kubernetes] CRI兼容的容器运行时。引入 Kubernetes 容器运行时接口 (CRI)以使 Kubernetes 容器运行时不可知。Kubernetes节点代理kubelet实现了 CRI客户端 API,可以使用任何实现 CRI 服务器 API的容器运行时来管理其节点上的容器和 Pod。
相关资源:containerd官网、containerd官方安装步骤
5.2、Docker、containerd的关系
1)就docker本身而言,包括docker client和dockerd,是一个客户端工具,用来把用户的请求发送给docker daemon(dockerd)。dockerd:dockerd是对容器相关操作的最上层封装,直接面向操作用户。Docker daemon,一般也会被称为docker engine。dockerd启动时会启动containerd 子进程。
containerd-shim是一个真实运行容器的载体,为了能够支持多种OCI Runtime,containerd内部使用containerd-shim,每启动一个容器都会起一个新的containerd-shim的进程。它通常指定三个因素:容器ID、bundle目录(对应某个容器生成的目录,一般位于:/var/run/docker/containerd/containerID)
2)containerd囊括了单机运行一个容器时所需要的一切;containerd是一个工业级别标准的容器运行时,它强调简单性、健壮性和可移植性,几乎囊括了单机运行一个容器运行时所需要的一切:执行、分发、监控、网络、构建、日志等。主要作用是:
1)、管理容器的生命周期(从创建容器到销毁容器)
2)、拉取/推送容器镜像
3)、存储管理(管理镜像及容器数据的存储)
4)、调用runC运行容器(与runC等容器运行时交互)
5)、管理容器网络接口及网络
dockerd实际真实调用的还是containerd的api接口,containerd是dockerd和runC之间的一个中间交流组件。runC是一个轻量级的工具,用来运行容器的,我们可以不用通过docker引擎,直接运行容器。