如果你是一名从事容器开发的工程师,那么Docker很可能是你首选的工具。但你是否知道,实际上存在着各种各样的容器运行时环境?有些运行时更为轻量级,有些则更加安全,而还有一些则是专门为Kubernetes设计的。

了解不同的容器运行时环境能让你有更多的选择。你可以根据自己的具体需求来挑选合适的工具——无论是为了获得更高的安全性、更低的资源消耗,还是为了更方便地与Kubernetes集成。

在本教程中,你将学习三种主要的容器运行时环境以及如何在你的系统中使用它们。我们会通过一些实际示例,并提供完整的代码供你立即尝试运行。学完之后,你就能明白在什么情况下应该使用哪种运行时环境,以及如何在不同运行时环境之间迁移容器。

目录

  1. 什么是容器运行时环境?

  2. 如何理解高级与低级容器运行时环境

  3. 如何将Docker作为基础运行时环境使用

  4. 如何使用Podman——这个无需守护进程的替代方案

  5. 如何使用Containerd

  6. 如何在不同的容器运行时环境之间迁移容器

  7. 实际应用案例

  8. 快速参考指南

  9. 总结

什么是容器运行时环境?

容器运行时环境其实就是用来运行容器的软件。例如,当你输入docker run nginx时,背后其实会发生一系列操作:Docker命令行界面会与Docker守护进程进行通信,而后者则会使用特定的容器运行时环境(通常是Containerd)来创建并运行容器。

可以这样理解:如果把容器比作手机上的应用程序,那么容器运行时环境就相当于让这些应用程序能够正常运行的操作系统。就像你可以在不同的手机上安装同一个应用程序一样,你也可以在不同的容器运行时环境中运行同一个容器。

为什么这很重要?

你可能会想知道,为什么需要关心究竟是什么在运行着你的容器呢?毕竟Docker本身运行得很好啊,对吧?其实有以下几个原因:

  1. 安全性:像Podman这样的运行时环境可以在不需要root权限的情况下运行容器。这意味着,即使有人突破了容器的防护机制,他们也无法获得对整个系统的完全控制权。

  2. 资源消耗:不同的容器运行时环境会消耗不同数量的内存和CPU资源。对于那些资源有限的服务器或边缘设备来说,这一点非常重要。

  3. 集成性:

    如果你要将容器部署到Kubernetes环境中,了解Containerd或CRI-O的相关知识有助于你解决生产环境中的各种问题。

  4. 许可政策:

    Docker Desktop对于大型企业来说有特定的许可要求,而像Podman这样的替代方案则完全免费。

以下是一张总结这些关键点的图表:

容器运行时对比图

如何理解高级运行时与低级运行时

容器运行时被分为两大类,了解这一区别有助于你理解各种组件是如何协同工作的。

低级运行时

runccrun这样的低级运行时负责实际创建容器的任务。它们直接与Linux内核交互,利用命名空间和cgroups等功能来构建隔离环境。

命名空间用于限制进程的可见范围。例如,进程命名空间的存在意味着容器无法看到系统中运行的其他进程;网络命名空间则使容器拥有独立的网络栈。

cgroups用于限制进程对资源的使用。你可以将容器的RAM使用量限制在512MB以内,或者将其使用的CPU核心数限制为50%。这样就能防止某个容器占用所有系统资源。

这些低级运行时遵循OCI(开放容器倡议)运行时规范。这一标准明确规定了如何运行容器,因此即使更换不同的运行时环境,容器依然能够正常工作。

高级运行时

Docker、Podman和containerd等高级运行时负责管理镜像、网络配置以及卷文件,并提供用户友好的界面。它们会从注册库中下载镜像,设置容器之间的网络连接,并管理容器的生命周期。

这些高级运行时在内部实际上还是使用了低级运行时。当你执行docker run命令时,Docker最终会调用runc来创建容器。这种分层设计使得用户既能享受到便捷的界面,又能受益于经过验证的低级运行时技术。

为什么这种分层结构很重要:

这种职责分离的设计非常高效。高级运行时可以专注于提升用户体验和添加新功能,而低级运行时则负责确保容器的稳定创建。你可以随意更换低级运行时环境,而不会影响整体工作流程。有些人会使用crun代替runc》,因为后者是用C语言编写的,启动速度更快。

如何将Docker作为基准进行比较

由于你可能已经熟悉Docker,我们就从它开始吧。使用Docker可以作为其他运行时环境的基准,用于进行对比测试。我们将构建一个简单的Web应用程序,然后在不同运行时环境中运行这个应用程序,从而观察它们之间的差异。

如何安装Docker

你可以找到适用于自己操作系统的安装指南:

如何运行测试容器

让我们通过运行一个简单的容器来验证Docker是否能够正常工作:

docker run hello-world

你应该会看到这样一条消息:

来自Docker的问候!
这条消息说明你的安装环境运行正常。

刚刚发生了什么?

当你执行那条命令时,Docker会检查本地是否存在hello-world这个镜像。由于没有找到,它就会从Docker Hub(一个公共镜像仓库)下载该镜像。然后,它会根据这个镜像创建一个容器,启动这个容器,容器会输出这条消息后退出。

所有这些操作都在几秒钟内完成。现在,让我们尝试构建一些更有用的东西吧。

如何创建Web服务器

为你的项目创建一个新的目录:

mkdir ~/container-demo
cd ~/container-demo

~符号表示你的主目录。在macOS上,这个目录是/Users/yourname;在Linux上,则是/home/yourname

创建一个简单的HTML文件:

cat > index.html 'EOF'


容器演示

  

来自Docker的问候!

这个应用程序是在一个容器中运行的。

EOF

这样就可以创建一个基本的HTML文件了。cat >命令用于将内容写入文件,而'EOF'表示“读取直到遇到文件结束标记”。这是一种通过命令行创建文件的便捷方法。

如何创建Dockerfile

你可以按照以下步骤来创建一个Dockerfile:

cat > Dockerfile 'EOF'
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EOF

了解Dockerfile

这个Dockerfile包含两条指令:

  1. FROM nginx:alpine:这一行指定了要使用的官方Nginx镜像。:alpine标签表示我们使用的是Alpine Linux版本,它的体积要小得多(大约20MB,而不是130MB)。由于体积小巧,Alpine Linux在容器环境中非常受欢迎。

  2. COPY index.html /usr/share/nginx/html/:这一行将你的HTML文件复制到Nginx用于提供文件服务的目录中。在容器内部,Nginx会被配置为从/usr/share/nginx/html/路径提供文件服务。

如何构建Docker镜像

docker build -t my-web-app .

-t选项表示“为镜像指定标签”——我们将这个镜像命名为my-web-app。末尾的.表示“使用当前目录作为构建环境”。Docker会在当前目录中查找Dockerfile,并将其中的所有文件发送给Docker守护进程进行构建。

构建过程中会显示如下输出:

[+] 正在构建……2.3秒后完成
=> [内部操作] 从Dockerfile中加载构建配置
=> =>> 正在传输Dockerfile:98B
=> [内部操作] 正在读取.dockerignore文件
...
=> =>> 镜像已被命名为docker.io/library/my-web-app

这表明Docker正在逐层构建你的镜像。Dockerfile中的每条指令都会生成一层新的镜像层。这些镜像层会被缓存起来,因此如果你没有对代码进行任何修改就重新构建镜像,整个过程会非常迅速。

如何运行Docker容器

docker run -d -p 8080:80 my-web-app

了解这些选项的含义:

  • -d表示“分离模式”——容器会在后台运行。如果不使用这个选项,容器就会在前台运行,此时你会看到Nginx的日志输出。使用-d选项后,容器会立即在后台启动。

  • -p 8080:80表示将主机机器上的8080端口映射到容器内的80端口。Nginx会在容器内监听80端口,因此如果你想通过浏览器访问容器内的服务,就需要进行这样的端口映射。我们选择了8080端口,但你也可以使用任何其他可用的端口。

打开你的浏览器,访问http://localhost:8080,你应该能看到你的HTML页面了!

本地主机上运行的Docker容器

如何查看正在运行的容器

docker ps

这个命令会显示所有正在运行的容器。输出结果可能类似于以下内容:

"/docker-entrypoint.…"   0.0.0.0:8080->80/tcp   peaceful_curie

Docker会自动为容器生成一个随机名称(在这个例子中是peaceful_curie)。如果你愿意,也可以使用--name选项指定容器的名称。

如何查看容器日志

docker logs 

替换为通过docker ps命令获得的容器ID(只需输入前几位字符即可)。这个命令会显示容器内部正在发生什么。对于运行Nginx的容器来说,你将会看到记录客户端请求情况的日志信息。

如何停止容器:

docker stop 

这种方式可以优雅地停止容器。Nginx会收到信号,从而顺利关闭。

现在你们已经了解了如何使用Docker,接下来让我们看看Podman是如何工作的。

如何使用Podman——这个无需守护进程的替代方案

现在我们来尝试一下Podman。它被设计为可以直接替代Docker,但它在某些方面存在差异,这些差异使得它在特定的使用场景中更具优势。

为什么会有Podman

Docker是以守护进程的形式运行的(即作为后台服务),并且需要root权限。这个守护进程会一直运行,等待用户的命令。这种架构也存在一些缺点:

  1. 安全性: Docker守护进程以root权限运行。如果有人攻破了这个守护进程,他们就能获得对整个系统的root访问权。

  2. 资源消耗:

    即使没有运行容器,Docker守护进程也会持续消耗系统资源。

  3. 单点故障:

    如果守护进程崩溃,所有运行的容器都会随之停止。

Podman通过不使用守护进程来解决这些问题。每个podman命令都是独立运行的。这种架构被称为“无需守护进程”的架构。

Podman的主要特点

总结来说,以下是Podman的一些主要优点,这些优点可能使得它非常适合用于你们的项目:

  1. 无需守护进程:

    每个命令都是独立运行的,不需要任何后台服务。

  2. 默认情况下无需root权限:

    容器是以普通用户的身份运行的,而不是以root权限运行。这大大提高了安全性。

  3. 可以替代Docker:

    大多数Docker命令在Podman中也能正常使用。你甚至可以将docker别名为podman,很多应用程序根本不会察觉到这种变化。

  4. 支持Pod概念:

    Podman具有与Kubernetes类似的“Pod”概念。这在容器工具中是非常独特的。

现在你们已经了解了Podman的优势,接下来我们就来看看如何使用它吧。

如何安装Podman

Podman的安装方法因操作系统而异。以下是官方提供的安装指南:

对于macOS用户而言(本教程中我们将使用macOS用户环境),您可以通过Homebrew来安装Podman:

brew install podman

如何初始化并启动Podman机器

在macOS上,Podman需要一个Linux虚拟机来运行容器(因为容器需要使用Linux内核的功能)。Podman Machine会帮您完成这一操作:

podman machine init

这样就会创建一个小的Linux虚拟机。您只需要执行这个命令一次即可。该虚拟机的大小约为1GB,在运行时消耗的资源也非常少。

初始化Podman机器

启动这个虚拟机:

podman machine start

验证它是否已经正常运行:

podman --version

您应该会看到类似以下的输出:

podman version 4.5.0

如何使用Podman运行容器

现在开始有趣的部分了。您可以使用与Docker几乎相同的命令来操作Podman。让我们构建并运行之前创建过的那个Web服务器吧:

# 构建镜像(使用与Docker相同的命令)
podman build -t my-web-app .

# 运行容器
podman run -d -p 8081:80 my-web-app

如果在使用`podman build`命令时遇到问题,您可以使用`docker image rm my-web-app:latest`来删除相应的Docker镜像。

底层实现上有什么不同?

尽管这些命令看起来相同,但实际上它们的工作原理是不同的:首先,Podman并不会启动任何后台守护进程。`podman`命令会直接创建并运行容器,而且容器是以当前用户的身份运行的,而不是以root用户身份。

您可以通过查看进程所属的用户来验证这一点:

podman top  user

您会看到自己的用户名,而不会是`root`。

Podman的Pod功能——一个独特的特性

Podman拥有Docker所没有的一个独特功能,那就是Pod。Pod是一组共享网络和存储资源的容器。Kubernetes也正是使用了类似的机制,因此Podman非常适合用于本地进行Kubernetes开发。

为什么Pod如此重要:

在实际应用中,通常会遇到需要协同工作的多个容器。例如,一个Web应用程序通常需要一个数据库来存储数据,还需要一个缓存层来临时存储那些被频繁访问的数据,同时还需要一个日志记录容器来记录请求信息、响应结果以及一些非敏感的关键应用元数据。

这四个容器(Web服务器、数据库、缓存服务器和日志记录容器)之间需要相互通信。在Docker中,你需要创建一个自定义网络并将这些容器连接到该网络上;而在Podman中,你可以直接创建一个Pod,让它自动处理这些网络连接问题。

如何创建Podman Pod

podman pod create --name my-app-pod -p 8082:80

这条命令会创建一个名为my-app-pod的Pod,并将主机上的8082端口映射到该Pod内部的80端口上。需要注意的是,你并不是在单独的容器上配置端口映射,而是在Pod层面上进行这种操作。

接下来向这个Pod中添加一个Web服务器:

podman run -d --pod my-app-pod --name web nginx:alpine

--pod这个参数告诉Podman将这个容器运行在Pod内部。由于Pod会自动处理端口映射工作,因此这个容器不需要自己配置端口映射。

再向这个Pod中添加一个Redis数据库:

podman run -d --pod my-app-pod --name cache redis:alpine

现在,有两个容器在同一Pod中运行。而它们的强大之处在于:它们共享同一个网络命名空间。

要查看你的Pod,请使用以下命令:

# 列出所有Pod
podman pod ps -a

# 查看某个Pod的详细信息
podman pod inspect 

# 查看在Pod中运行的进程
podman top pod 

# 查看该Pod中容器的日志记录
podman logs 

Podman Pod检查界面,显示容器运行状态

理解共享网络机制:

这两个容器都可以使用localhost来相互访问。Web服务器可以通过localhost:6379(Redis的默认端口)与Redis建立连接,就如同它们运行在同一台机器上一样。

Kubernetes的Pod也正是这样工作的。因此,如果你学习了Podman的相关知识,其实也就相当于了解了Kubernetes的网络机制。

如何从Pod生成Kubernetes YAML文件

这就是Podman真正发挥作用的地方——你可以从自己的Pod中生成符合Kubernetes规范的YAML文件:

podman generate kube my-app-pod > my-app-pod.yaml

打开 my-app-pod.yaml,你就会看到正确的 Kubernetes 配置文件:
# 将此文件的配置内容保存下来,然后使用 kubectl create -f 命令将其导入到 Kubernetes 集群中。
#
apiVersion: v1
kind: Pod
metadata:
annotations:
io.kubernetes.cri-o.SandboxID/cache: 5e56bd9eab1a02a88654e3614312302d0f3f8d3652480498e6d1eef7d4824019
io.kubernetes.cri-o.SandboxID/web: 5e56bd9eab1a02a88654e3614312302d0f3f8d3652480498e6d1eef7d4824019
creationTimestamp: "2026-02-12T13:44:55Z"
labels:
app: my-app-pod
name: my-app-pod
spec:
containers:
- args:
- nginx
- -g
- daemon off;
image: docker.io/library/nginx:alpine
name: web
ports:
- containerPort: 80
hostPort: 8082
- args:
- redis-server
image: docker.io/library/redis:alpine
name: cache

这个配置文件可以直接部署到任何 Kubernetes 集群中:
# 可以使用 minikube 集群来部署
kubectl apply -f my-app-pod.yaml

这对于本地开发来说非常有用。你可以使用 Podman 创建应用程序的原型,生成相应的 YAML 配置文件,然后直接将其部署到 Kubernetes 集群中,而无需重新编写任何代码。

如何管理 Podman 机器

在 macOS 或 Windows 上使用 Podman 时,实际上是在使用一个 Linux 虚拟机。以下是管理这些虚拟机的方法。

列出所有Podman机器:

podman machine list

podman machine list

这会显示你所有的Podman虚拟机、它们的状态(运行中还是已停止)以及名称。默认的机器通常被称为podman-machine-default

检查机器的状态和信息:

podman machine info

这将显示有关当前机器的详细信息,包括CPU、内存和磁盘使用情况。

停止Podman机器:

podman machine stop

如果你有多台机器,需要指定具体的名称:

podman machine stop podman-machine-default

这样就可以停止相应的虚拟机,但所有的镜像和容器都会保持原样。当机器被停止时,其中正在运行的所有容器也会随之停止。

启动已停止的机器:

podman machine start

或者使用具体的名称来启动:

podman machine start podman-machine-default

这样就可以重新启动虚拟机。你的镜像仍然存在,但容器会保持停止状态,除非你设置了不同的重启策略。

删除Podman机器:

podman machine rm podman-machine-default

这样就会彻底删除该虚拟机及其所有内容(镜像、容器、卷等)。当你想要重新开始使用或释放磁盘空间时,可以使用这个命令。

了解了Podman的基本工作原理之后,我们可以继续学习如何使用Containerd了。

如何使用Containerd

Containerd其实是Docker内部使用的运行时环境,也是大多数Kubernetes安装系统的默认运行时。当你运行Docker时,其实就是在使用Containerd,只不过你可能没有意识到这一点。

为什么直接使用Containerd?

你可能会疑惑,既然Docker已经在使用Containerd,为什么还要直接使用它呢?以下是一些原因:

  1. Kubernetes:大多数Kubernetes集群都使用Containerd作为容器运行时环境。了解Containerd有助于你解决生产环境中出现的问题。

  2. 占用资源少:Containerd没有用户界面,功能也非常简洁,因此它占用的内存比Docker Desktop要少得多(大约50MB对比2GB)。

  3. 适用于构建工具:如果你正在开发容器编排工具,直接使用Containerd可以让你获得更精细的控制能力。

了解容器d的架构

容器d的架构如下所示:

你的命令 → nerdctl → 容器d → runc → 容器

在这个流程中,nerdctl提供了类似Docker的命令行界面,容器d负责管理镜像及容器的生命周期,而runc则利用内核功能来创建容器。

如何使用nerdctl安装容器d

容器d主要是为一些系统(如Kubernetes)设计的,并非专为开发人员直接使用而设计。其安装方法会因操作系统不同而有所差异:

对于macOS用户(本教程也将使用这一系统),我们可以选择Lima,因为它会预先安装容器d和nerdctl。

brew install lima

Lima内置了nerdctl,因此无需另行安装。

对于Linux用户,可以直接通过包管理器来安装容器d,而nerdctl则可以从GitHub的下载页面获取。容器d在Linux系统中可以原生运行,无需使用虚拟机。

如何启动Lima实例

limactl start

这样就会创建一个预配置好的Linux虚拟机,其中已经安装了容器d和nerdctl。该虚拟机的默认配置为2GB内存和100GB硬盘空间,你也可以根据需要调整这些设置。

Lima会将你的主目录挂载到虚拟机中,因此你可以直接访问自己的文件。这种设计使得使用Lima非常方便——无需将文件复制到虚拟机中。

验证其是否正常工作:

lima nerdctl run hello-world

容器d与Hello-world容器的运行效果

如何使用nerdctl运行你的应用程序

这些命令与Docker中的命令几乎完全相同,这是有意为之——nerdctl的目标就是实现与Docker的兼容性。由于我们是在Lima环境中进行操作,因此会在命令前加上lima前缀。

进入你的项目目录:

cd ~/container-demo

构建镜像:

lima nerdctl build -t my-web-app .

运行容器:

lima nerdctl run -d -p 8083:80 my-web-app

访问 http://localhost:8083,即可查看运行在 Containerd 上的应用程序!

在本地主机上运行的 Containerd 容器

与 Docker 有什么不同?

从底层来看,两者之间存在很多差异。Containerd 负责管理你的镜像和容器。它并没有传统意义上的守护进程(Containerd 的运行方式与 dockerd 不同)。镜像的存储方式也有所不同(不过由于它们遵循 OCI 标准,因此仍然可以互相兼容)。

但从开发者的角度来看,使用的命令是相同的。这就是 OCI 等标准所具备的力量。

如何检查正在运行的容器:

lima nerdctl ps

这个命令会显示所有正在运行的容器。

正在运行的容器

如何管理 Lima 虚拟机

通过 Lima 使用 Containerd 时,实际上是在使用一台 Linux 虚拟机。以下是管理它的方法。

列出所有的 Lima 虚拟机:

limactl list

这个命令会显示所有的 Lima 虚拟机,包括它们的状态(是正在运行还是已停止)以及名称。默认的虚拟机通常被称为 default

检查虚拟机的状态及相关信息:

limactl info default

这个命令会显示指定虚拟机的详细信息,包括其配置和资源使用情况。

停止 Lima 虚拟机:

limactl stop default

这个命令会停止虚拟机,但不会删除其中的数据。所有的镜像和容器都会保持原样。当虚拟机被停止后,里面的所有正在运行的容器也会随之停止;下次重新启动虚拟机时,镜像仍然存在,但容器会处于停止状态。

重启已停止的虚拟机:

limactl start default

这个命令会重新启动虚拟机。由于镜像会在重启后仍然保留,因此无需重新创建它们。

删除 Lima 虚拟机:

limactl delete default

Containerd 虚拟机的列表及删除操作

这个命令会彻底删除虚拟机及其所有内容(包括镜像、容器和卷)。当你想要重新开始使用或释放磁盘空间时,可以使用这个命令。不过在删除后,你需要再次运行 limactl start 来创建新的虚拟机。

使用自定义设置创建新的虚拟机:

limactl start --name my-custom-vm --cpus 4 --memory 8

这种方式会创建一台拥有4颗CPU和8GB内存的新虚拟机。您可以为不同的项目创建多台Lima虚拟机。

如何在不同的运行时环境中迁移容器

得益于OCI(开放容器倡议)标准,您可以在不同的运行时环境之间迁移容器镜像。这一功能非常强大——您可以使用一种工具进行构建,然后使用另一种工具进行部署。

为什么标准如此重要

在OCI出现之前,每种容器运行时环境都使用自己独特的镜像格式,因此在不同运行时环境之间迁移镜像是非常困难甚至不可能的。

OCI制定了关于运行时规范、镜像规范以及镜像传输规范的统一标准,这些标准使得容器镜像能够在不同的环境中顺利迁移。

如今,所有主要的容器运行时环境都遵循这些标准,因此容器镜像已经具备了跨平台移植的能力。

方法1——使用容器注册服务

分享容器镜像最简单的方法就是通过Docker Hub、GitHub Container Registry或您自己建立的私有注册服务。任何容器运行时环境都可以从这些注册服务中下载或上传镜像。

首先,使用Docker进行构建:

docker build -t my-username/my-app:v1 .

镜像名称由三部分组成:my-username(您的注册服务用户名)、my-app(应用程序名称)以及v1(版本标签)。

然后将构建好的镜像上传到Docker Hub:

docker login
docker push my-username/my-app:v1

如果您还没有Docker Hub账户,需要先注册一个。执行docker login命令后,系统会要求您输入登录凭据。

接下来,使用Podman下载该镜像:

podman pull my-username/my-app:v1

Podman会从Docker Hub下载该镜像。尽管这个镜像是用Docker构建的,但由于两者都遵循OCI标准,因此Podman完全能够使用它。

或者也可以使用nerdctl来下载:

lima nerdctl pull my-username/my-app:v1

同样的镜像,却可以在三种不同的运行时环境中使用。这就是标准带来的便利之处。

方法2——导出与导入

如果您不想使用公共注册服务(比如您的镜像中包含专有代码),也可以将镜像导出为tar文件。这种方式非常适合在隔离环境中使用,或者简单地在不同机器之间传输镜像。

首先从Docker中导出镜像:

docker save my-web-app -o my-web-app.tar

这样就会生成一个名为my-web-app.tar的文件,其中包含了该镜像及其所有层结构。根据镜像的大小,这个文件的体积可能会达到几十甚至几百兆字节。

接下来,将导出的镜像导入到Podman中:

podman load -i my-web-app.tar

导入到 Nerdctl 中:

lima nerdctl load -i my-web-app.tar

现在,这三个运行时环境中都拥有了相同的镜像!你可以进行验证:

docker images
podman images  
lima nerdctl images

这三条命令的执行结果都会在对应的镜像列表中显示 my-web-app

了解镜像层结构:

当你导出一个镜像时,实际上就是将它的所有层都一起导出了。Dockerfile 中的每一行代码都会创建一层镜像层。这些镜像层可以在不同的镜像之间共享,从而节省磁盘空间。

例如,如果你有 10 个基于 nginx:alpine 创建的镜像,那么它们都会共享那些与 nginx 相关的镜像层。只有那些每个镜像独有的层才会占用额外的存储空间。

实际应用案例

让我们来看一些在哪些情况下选择合适的运行时环境会非常重要。这些例子能够说明技术决策所带来的实际影响。

使用案例 1 – 以安全为首要考虑的开发模式

如果你正在开发那些对安全性要求极高的应用程序(比如金融服务、医疗行业或政府相关应用),那么 Podman 提供的无根容器功能就会带来巨大的优势。

安全问题:

传统的 Docker 运行环境需要 root 权限。如果有人利用了容器中的漏洞并逃到了主机系统上,他们就会获得 root 权限。这种漏洞被称为“容器逃逸”漏洞。

而 Podman 的无根运行模式解决了这个问题:

# 默认情况下,所有 Podman 命令都是以你的用户身份运行的
podman run --rm -it alpine whoami

执行这条命令后,输出的结果会是你的用户名,而不是 root。其中,--rm 选项用于在命令执行完毕后删除容器,-it 选项使容器具有终端交互功能,alpine 是一个轻量级的 Linux 发行版,而 whoami 则是用来输出用户名的命令。

即使有人成功突破了容器的限制,他们也只能拥有你的用户权限。他们无法安装系统范围内的恶意软件、访问其他用户的数据、修改系统配置,也无法安装内核模块。

这样一来,容器逃逸带来的危害就被大大降低了。

安全场景示例:

假设你正在运行一个用于处理用户上传文件的网络应用程序。如果存在某个漏洞,攻击者就可以在你的容器中执行恶意代码。如果使用传统的 Docker 运行环境且以 root 权限运行,攻击者甚至可以逃出容器、安装rootkit工具、窃取服务器上的所有数据,并且在你修复了该漏洞之后依然能够继续发动攻击。

但如果使用 Podman 的无根模式,攻击者虽然可能仍然能够逃出容器,但他们只能访问你的用户有权访问的文件。他们无法在容器之外继续执行恶意操作,也无法影响其他用户或系统文件。

这种差异是显而易见的。

使用案例 2 – 在本地测试 Kubernetes

Podman能够在运行容器时生成Kubernetes YAML配置文件。在最终确定Kubernetes配置方案之前,使用它进行原型开发是非常理想的。

开发工作流程:

  1. 使用Podman在本地运行你的应用程序

  2. 快速进行测试与迭代

  3. 当程序运行正常后,生成Kubernetes YAML配置文件

  4. 将其部署到真实的Kubernetes集群中

下面举个实际例子。假设你正在开发一个带有数据库功能的Web应用程序:

# 创建一个Pod(类似于Kubernetes中的Pod)
podman pod create --name myapp -p 8080:80

# 添加Web服务器
podman run -d --pod myapp --name web nginx:alpine

# 添加PostgreSQL数据库
podman run -d --pod myapp --name db \
  -e POSTGRES_PASSWORD=secret \
  postgres:alpine

http://localhost:8080地址上测试你的应用程序。当确认其能够正常运行后,生成Kubernetes YAML配置文件:

podman generate kube myapp > myapp.yaml

现在你可以将myapp.yaml部署到任何Kubernetes集群中:

kubectl apply -f myapp.yaml

这种方式比手动编写Kubernetes YAML配置文件并在集群中进行调试要快得多。你可以在本地进行迭代开发,等到准备就绪后再进行部署。

为什么这很重要:

Kubernetes的学习曲线较为陡峭,其YAML配置文件格式繁琐且容易出错。通过先使用简单的Podman命令来生成配置文件,你可以先专注于开发应用程序本身,逐步学习Kubernetes的相关知识,及时发现配置错误,并且无需承担云服务的费用即可快速进行迭代开发。

应用场景3——资源受限的环境

containerd在占用系统资源方面表现最为优秀。如果你需要在边缘设备、Raspberry Pi或资源有限的服务器上运行容器,这一点就显得尤为重要了。

内存使用情况对比:

以下是各种运行时环境典型的内存占用情况:

  • Docker Desktop大约需要2GB的RAM(包括虚拟机、守护进程、用户界面以及Kubernetes组件)。

  • Podman大约需要500MB的RAM(在macOS系统中,这部分内存主要用于运行虚拟机)。

  • Containerd仅需要约50MB的RAM,完全专注于容器本身的运行,不会额外占用系统资源。

对于配备16GB RAM的开发笔记本电脑来说,这些差异其实并不明显。但考虑到以下场景,这种区别就显得非常重要了:

1. 边缘计算:

如果你在总内存仅为1GB的边缘设备上运行容器,Docker Desktop是无法正常运行的,而containerd则能够满足这些设备的资源需求。

2>物联网设备:

一块配备2GB RAM的Raspberry Pi如果运行Docker Desktop,留给应用程序的实际可用内存会非常少;而containerd则能更高效地利用资源。

3>高密度服务器:每台服务器上运行100个容器,因此每一MB的存储空间都十分重要。使用containerd而不是完整的Docker,每台服务器可以节省2GB的存储空间;由于共有100台服务器,因此总共可以节省200GB的存储空间。

边缘设备的示例配置:

# 在树莓派或类似设备上
sudo apt-get install containerd
sudo apt-get install nerdctl

# 现在你可以以最小的开销运行容器了
nerdctl run -d my-lightweight-app

这样,你的应用程序就可以使用大部分可用的内存,而不会与那些占用大量资源的运行时环境产生竞争。

快速参考指南

以下是各种运行时环境下常用命令的对比:

任务 Docker Podman nerdctl (通过Lima)
构建镜像 docker build -t app . podman build -t app . lima nerdctl build -t app .
运行容器 docker run -d app podman run -d app lima nerdctl run -d app
列出容器 docker ps podman ps lima nerdctl ps
查看日志 docker logs podman logs lima nerdctl logs
停止容器 docker stop podman stop lima nerdctl stop
删除容器 docker rm podman rm lima nerdctl rm
列出镜像 docker images podman images lima nerdctl images
拉取镜像 docker pull nginx podman pull nginx lima nerdctl pull nginx
推送到注册中心 docker push app podman push app lima nerdctl push app
在容器中执行命令 docker exec -it sh podman exec -it sh lima nerdctl exec -it sh

结论

在这份指南中,我们了解了三种主要的容器运行时环境,并学习了如何使用Docker、Podman和containerd。实际上,容器生态系统远不止这些工具,了解其他替代方案能让你在安全性、性能或特定应用场景方面有更多选择。

如果你正在学习相关知识,或者需要最完善的文档支持,那就使用Docker;如果你需要无root权限的安全环境,或者在构建持续集成/持续部署流程,那么Podman是更好的选择;而当你需要最小化资源消耗,或将容器部署到Kubernetes集群中时,containerd则是最佳选项。

得益于OCI标准,你的容器具有良好的跨平台兼容性。可以用Docker进行构建,用Podman进行测试,再用containerd进行部署——这些工具完全可以协同工作!你并不需要被束缚在某个特定的供应商或工具上。

像往常一样,我希望你们能够喜欢这份指南,并从中学到一些有用的知识。如果你们希望继续保持联系,或想了解更多关于DevOps的实际操作内容,可以关注我在LinkedIn上的账号,以及DevOps Cloud Projects项目。

祝大家在容器化技术的应用过程中一切顺利!

Comments are closed.