在容器化领域,Podman作为 Docker 的无守护进程替代方案越来越受欢迎,尤其是对于优先考虑安全性和灵活性的开发人员而言。经过前面的学习,我们已经初步入门如何使用Podman命令行来管理容器。我们今天来尝试使用YAML脚本来管理多容器的单机编排,它允许用户使用熟悉的语法管理多容器应用程序而无需根守护进程。

2025.02 深圳·东莞·松山湖·三丫坡·华为员工培训中心·图书馆

Podman Compose

主要功能

Podman Compose 是一个命令行工具,使用podman-py编写的,功能类似于 Docker Compose。Podman-compose是通过使用RESTful API来启动容器。它允许您使用 YAML 配置文件定义、管理和运行多容器应用程序。与 Docker Compose 一样,Podman Compose 从文件中读取配置docker-compose.yml并将其转换为 Podman 命令。Podman 与 Docker 的不同之处在于,它默认以非 root 用户身份运行容器,从而提高了安全性和灵活性,尤其是在多用户环境中。Podman Compose扩展了此功能,使您能够在更安全的环境中编排容器服务。

使用Compose的3个核心步骤

  1. 定义应用环境,配置dockerfile文件以便可以在任何地方复制它
  2. 定义组成应用的服务,配置docker-compose.yml文件
  3. 启动并运行整个应用程序

Podman的主要优势

  1. 无根操作:无需root权限即可管理容器
  2. Docker Compose 兼容性:它支持大多数docker-compose.yml配置
  3. 安全性:不需要守护进程,因此与 Docker 相比,它不太容易受到攻击
  4. 可交换的后端:如果需要,Podman 可以与其他容器后端一起工作。

基本用法

安装Podman-compose

1
2
3
4
5
$ sudo dnf install -y podman-compose
$ podman-compose -v
podman-compose version 1.3.0
podman version 5.3.1
$ mkdir compose && cd compose && touch podman-compose.yml

创建podman-compose.yml

podman-compose.yml文件定义了应用程序所需的服务、网络和卷。这是一个包含两个服务的简单示例:一个Web服务和一个数据库服务。

1
2
3
4
5
6
7
8
9
10
11
12
version: '3'
services:
web:
image: httpd:latest
ports:
- "8080:80"
db:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD: P@ssw0rd
ports:
- "3306:3306"

运行容器

要启动podman-compose.yml文件中定义的容器,请使用以下命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 后台拉起服务
$ podman-compose up -d
8dd89b3c422552e30a39c425226d0ab986f91857b3a8fd32ab9605e72fd1e4af
746a5811deb206bd2cfd53a9e5f77d1d58f46eb31c6a72930601aa346877b18e
compose_web_1
fe8a4165055d5248dba7828e2951edd482d89cc90fbcac166c044f04d782ef0b
compose_db_1
# 校验
$ podman ps
CONTAINER ID IMAGE COMMAND PORTS NAMES
746a5811deb2 docker.io/library/wordpress:latest apache2-foregroun... 0.0.0.0:8080->80/tcp compose_web_1
fe8a4165055d docker.io/library/mysql:latest mysqld 0.0.0.0:3306->3306/tcp, 33060/tcp compose_db_1

此命令将启动webdb容器。然后我们可以校验一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 检验web服务
$ curl 127.0.0.1:8080
<html><body><h1>It works!</h1></body></html>
# 检验数据库服务
$ sudo dnf -y install mysql
$ mysql -uroot -p -h127.0.0.1
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 9.2.0 MySQL Community Server - GPL

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;

停止容器

要停止正在运行的容器,您可以使用:

1
2
3
4
5
6
7
8
# 这将停止并删除与配置相关的所有容器。
$ podman-compose down
compose_web_1
compose_db_1
compose_db_1
compose_web_1
8dd89b3c422552e30a39c425226d0ab986f91857b3a8fd32ab9605e72fd1e4af
compose_default

进阶用法

Podman Compose 可以处理更复杂的配置。以下是一些管理多容器应用程序的高级示例。

添加网络

您可以在文件中定义自定义网络docker-compose.yml。这允许容器在隔离网络中进行通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
version: '3'

services:
webApp:
container_name: webApp
image: httpd:latest
restart: always
networks:
- frontend
- backend
ports:
- "8080:80"

mySql:
container_name: mySql
image: mysql:latest
restart: always
networks:
- backend
environment:
MYSQL_ROOT_PASSWORD: P@ssw0rd

networks:
frontend:
driver: bridge
name: frontend
backend:
driver: bridge
internal: true
name: backend

在此示例中,Web服务与frontendbackend网络都进行通信,但mysql数据库连接到backend

使用持久卷

为了在容器重启时保持数据持久,您可以在docker-compose.yml文件中定义卷,这确保即使容器停止或删除,数据仍保持完整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
version: '3'

services:
webApp:
container_name: webApp
image: wordpress:latest
restart: always
networks:
- frontend
- backend
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: mySql
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: se(urepa55
volumes:
- $HOME/webApp/web_data:/var/www/html
depends_on:
- mySql

mySql:
container_name: mySql
image: mysql:latest
restart: always
networks:
- backend
environment:
MYSQL_ROOT_PASSWORD: P@ssword
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: se(urepa55
security_opt:
- no-new-privileges:true
volumes:
- $HOME/webApp/mysql_data:/var/lib/mysql

volumes:
wordpress_data:
driver: local
mysql_data:
driver: local

networks:
frontend:
driver: bridge
name: frontend
backend:
driver: bridge
internal: true
name: backend
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 执行podman-compose命令
[sujx@docker compose]$ podman-compose up -d
mySql
webApp
webApp
mySql
0d53d6ab2b5bf9ce8b0d62ee889dc857567d882effd060cc2e193d026f19803b
backend
frontend
f2477194e504c96fe34d0e79a1ce0313590c985063436d4ad7604dd678213d03
4f693cd301c3f34eb31739e7e6f555ae3395310c638fe9b8b378e10e5943c77c
mySql
4d5f8e9e9b25c7ca6cd1b1ab1909926f2a5db2d7f4af7ecf0ebc5248beff5948
webApp
# 测试数据库连接
[sujx@docker compose]$ podman exec -it mySql mysql -uwpuser -p
# 确认数据持久化
[sujx@docker compose]$ podman volume inspect wordpress_data

然后我们访问相关站点

无根模式

Podman 的主要优势之一是其无 root 操作,这增强了安全性。Podman Compose 继承了此功能,允许您以非 root 用户身份运行容器。

1
podman-compose --rootless up

此命令确保您的容器以无根模式运行,从而在多用户环境中提供更好的安全性和隔离性。

常见问题

尽管 Podman Compose 的设计非常方便用户使用,但您在设置和执行过程中仍可能会遇到一些问题。以下是一些常见问题及其解决方案。

1.不支持的命令

由于 Podman 不是 Docker,因此某些docker-compose.yml功能可能无法立即使用。请务必参考Podman 文档以确保兼容性。

2.网络连接问题

在某些情况下,由于网络配置原因,容器可能无法正确通信。请确保在配置文件中使用正确的网络。

3.卷安装错误

由于路径或权限不正确,可能会发生与卷安装相关的错误。请确保设置了正确的目录权限,尤其是在无根模式下。

4.Podman Compose 是 Docker Compose 的替代品吗?

是的,Podman Compose 的工作原理与 Docker Compose 类似,通常可以作为使用文件管理容器的替代品docker-compose.yml

5.如何确保我的 Podman 容器在无根模式下运行?

只需以普通用户身份安装 Podman Compose,然后无需 即可运行命令sudo。Podman 会自动检测无根环境。

6.我可以将 Docker Compose 与 Podman 一起使用吗?

虽然 Podman Compose 是首选工具,但您可以通过设置环境变量来重定向命令,将 Docker Compose 与 Podman 结合使用。不过,Podman Compose 专门针对 Podman 进行了优化,可提供更无缝的体验。

7.Podman Compose 是否支持 Docker Swarm?

Podman Compose 不支持开箱即用的 Docker Swarm 或 Kubernetes。对于超出简单容器管理的编排,请考虑将 Podman 与KubernetesOpenShift结合使用。

8.Podman Compose 比 Docker Compose 慢吗?

不是,Podman Compose 针对性能进行了优化,并且在某些情况下,由于其无守护进程架构,它可以比 Docker Compose 更快。

Kubernets YAML文件

将命令行选项转换为结构化语言,对于从单节点容器转移到运行规模化容器的开发人员来说是一个障碍。这涉及到如何指定卷、镜像、安全约束、网络端口等。Podman的产品经理Scott McCarty提出一个一个想法:“我真正想做的是帮助用户从Podman转移到使用Kubernets管理其容器。”这就促使开发人员创建了一个新的Podman命令: podman generate kube

用Podman生成K8S YAML文件

单个容器生成yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 创建一个httpd的容器,并将web服务端口映射到8081
[sujx@docker ~]$ podman run -d --name http -p 8081:80 httpd
b312c82803bc1ef5ba5496eef84aa5a0ffdce3fe620e8b5cf540714bf02b1baf
# 检查容器运行情况
[sujx@docker ~]$ curl 127.0.0.1:8081
<html><body><h1>It works!</h1></body></html>
# 将容器属性导出为kubernets YAML文件
[sujx@docker ~]$ podman generate kube http >> http.yaml
# 查看YAML文件
[sujx@docker ~]$ cat http.yaml
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-5.3.1
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2025-02-26T13:36:36Z"
labels:
app: http-pod
name: http-pod
spec:
containers:
- image: docker.io/library/httpd:latest
name: http
ports:
- containerPort: 80
hostPort: 8081

然后,我们就可以依据这个文件添加replicas这样的运行参数,从而加快Kubernets应用的部署

compose生成yaml

创建compose文件

将前文中所用到的podman-compose文件添加pod的标签 “io.podman.compose.pod: wordpress”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
version: '3'

services:
webApp:
container_name: webApp
image: wordpress:latest
restart: always
networks:
- frontend
- backend
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: mySql
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: se(urepa55
volumes:
- $HOME/webApp/web_data:/var/www/html
depends_on:
- mySql
labels:
io.podman.compose.pod: wordpress

mySql:
container_name: mySql
image: mysql:latest
restart: always
networks:
- backend
environment:
MYSQL_ROOT_PASSWORD: P@ssword
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: se(urepa55
volumes:
- $HOME/webApp/mysql_data:/var/lib/mysql
labels:
io.podman.compose.pod: wordpress

volumes:
wordpress_data:
driver: local
mysql_data:
driver: local

networks:
frontend:
driver: bridge
name: frontend
backend:
driver: bridge
internal: true
name: backend
运行pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 执行compose
[sujx@docker compose]$ podman-compose up -d
e16655a97b1f7be0b7c2cd47dfe8ac7b28cd4441ba2f07084ec21d8f10494dbc
dc8017d1e0fa6516aa8c793628169822ffbc10fbeae2a33da9314eaeff901ade
mySql
9a01c257347596ea98cbeec7cf8accf9b7b8ce965aedabe213859c5da8c11599
webApp
# 查看容器
[sujx@docker compose]$ docker ps
CONTAINER ID IMAGE COMMAND PORTS NAMES
b312c82803bc docker.io/library/httpd:latest httpd-foreground 0.0.0.0:8081->80/tcp http
dc8017d1e0fa docker.io/library/mysql:latest mysqld 3306/tcp, 33060/tcp mySql
# 查看pod
[sujx@docker compose]$ podman pod list
POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
e16655a97b1f pod_compose Running 14 seconds ago 2
# 导出yaml文件
[sujx@docker compose]$ podman generate kube pod_compose > wordpress.yaml
生成YAML文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-5.3.1

# NOTE: The namespace sharing for a pod has been modified by the user and is not the same as the
# default settings for kubernetes. This can lead to unexpected behavior when running the generated
# kube yaml in a kubernetes cluster.
---
apiVersion: v1
kind: Pod
metadata:
annotations:
io.kubernetes.cri-o.SandboxID/mySql: 9a134961881c0d6d3ae7309c6a8f99cd07f5eb2e46fe2174a073a1cf7b7446b3
io.kubernetes.cri-o.SandboxID/webApp: 9a134961881c0d6d3ae7309c6a8f99cd07f5eb2e46fe2174a073a1cf7b7446b3
creationTimestamp: "2025-02-26T13:31:10Z"
labels:
app: podcompose
name: podcompose
spec:
containers:
- args:
- mysqld
env:
- name: MYSQL_ROOT_PASSWORD
value: P@ssword
- name: MYSQL_USER
value: wordpress
- name: MYSQL_PASSWORD
value: se(urepa55
- name: MYSQL_DATABASE
value: wordpress
image: docker.io/library/mysql:latest
name: mySql
volumeMounts:
- mountPath: /var/lib/mysql
name: home-sujx-webApp-mysql_data-host-0
- args:
- apache2-foreground
env:
- name: WORDPRESS_DB_HOST
value: mySql
- name: WORDPRESS_DB_PASSWORD
value: se(urepa55
- name: WORDPRESS_DB_USER
value: wordpress
image: docker.io/library/wordpress:latest
name: webApp
ports:
- containerPort: 80
hostPort: 8080
volumeMounts:
- mountPath: /var/www/html
name: home-sujx-webApp-web_data-host-0
volumes:
- hostPath:
path: /home/sujx/webApp/mysql_data
type: Directory
name: home-sujx-webApp-mysql_data-host-0
- hostPath:
path: /home/sujx/webApp/web_data
type: Directory
name: home-sujx-webApp-web_data-host-0

使用边车容器生成

应用的部署
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 创建pod
$ podman pod create --name wordpress-pod -p 8080:80
0fe838363193acf301f7ccd0e64d7a7d3939a2d01a95133caa3667ba18da9f86

# 创建持久化卷
# 创建 WordPress 数据卷
$ podman volume create wordpress-data
# 创建 MySQL 数据卷
$ podman volume create mysql-data

# 创建 MySQL 容器
$ podman run -d \
--pod wordpress-pod \
--name mySql \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD="P@ssword" \
-e MYSQL_DATABASE=wordpress \
-e MYSQL_USER=wordpress \
-e MYSQL_PASSWORD="se(urepa55" \
docker.io/library/mysql:latest

# 创建 wordpress 容器
$ podman run -d \
--pod wordpress-pod \
--name wordpress \
-v wordpress-data:/var/www/html \
-e WORDPRESS_DB_HOST=mySql \
-e WORDPRESS_DB_USER=wordpress \
-e WORDPRESS_DB_PASSWORD="se(urepa55" \
-e WORDPRESS_DB_NAME=wordpress \
docker.io/library/wordpress:latest

# 查看pod状态
$ podman pod ps
POD ID NAME STATUS CREATED INFRA ID # OF CONTAINERS
0fe838363193 wordpress Running 30 minutes ago 40f050d3a766 3
# 查看容器状态
$ podman ps --pod
IMAGE PORTS PODNAME
localhost/podman-pause:5.3.1-1732147200 0.0.0.0:8080->80/tcp wordpress
docker.io/library/mysql:latest 0.0.0.0:8080->80/tcp, 3306/tcp, 33060/tcp mySql wwordpress
docker.io/library/wordpress:latest 0.0.0.0:8080->80/tcp wordpress wwordpress
# 导出YAML文件
$ podman generate kube wordpress-pod >> wordpress-pod.yaml
查看YAML文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-5.3.1
apiVersion: v1
kind: Pod
metadata:
annotations:
io.kubernetes.cri-o.SandboxID/mySql: 9b9005a230e398b1f82844fdef9072f1a27c2a7cd3a54089d33e540c1198e287
io.kubernetes.cri-o.SandboxID/wordpress: 9b9005a230e398b1f82844fdef9072f1a27c2a7cd3a54089d33e540c1198e287
creationTimestamp: "2025-02-26T14:42:19Z"
labels:
app: wordpress-pod
name: wordpress-pod
spec:
containers:
- args:
- mysqld
env:
- name: MYSQL_ROOT_PASSWORD
value: P@ssword
- name: MYSQL_PASSWORD
value: se(urepa55
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_USER
value: wordpress
image: docker.io/library/mysql:latest
name: mySql
ports:
- containerPort: 80
hostPort: 8080
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-data-pvc
- args:
- apache2-foreground
env:
- name: WORDPRESS_DB_NAME
value: wordpress
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_PASSWORD
value: se(urepa55
- name: WORDPRESS_DB_HOST
value: mySql
image: docker.io/library/wordpress:latest
name: wordpress
volumeMounts:
- mountPath: /var/www/html
name: wordpress-data-pvc
volumes:
- name: mysql-data-pvc
persistentVolumeClaim:
claimName: mysql-data
- name: wordpress-data-pvc
persistentVolumeClaim:
claimName: wordpress-data

Kubernets YAML部署到podman上

获取目标文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod1
name: pod1
spec:
containers:
- image: traefik/whoami
imagePullPolicy: IfNotPresent
name: pod1
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
执行目标文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 拉取镜像并创建pod
[sujx@docker podman]$ podman play kube pod1-build.yml
✔ docker.io/traefik/whoami:latest
Trying to pull docker.io/traefik/whoami:latest...
Getting image source signatures
Copying blob acd87f41b56b done |
Copying blob 1d1ddb624e47 done |
Copying blob 8f64377bf2e7 done |
Copying config 9943fa5dfa done |
Writing manifest to image destination
Pod:
0bbcb963f9fdb678ee1e27c2601e3dbefaeecca9e5bc66b1acf4bdf498054e20
Container:
121f65a16979e99a2f16a28325d6f29ffb27006f369654e0faea2fc7ac8fe1f6

# 校验pod
[sujx@docker podman]$ podman pod ps
POD ID NAME STATUS INFRA ID # OF CONTAINERS
0bbcb963f9fd pod1 Running 30a2774ac83e 2
# 检查容器
[sujx@docker podman]$ podman ps --pod
IMAGE PORTS NAMES POD ID PODNAME
localhost/podman-pause:5.3.1-1732147200 0bbcb963f9fd-infra 0bbcb963f9fd pod1
docker.io/traefik/whoami:latest 80/tcp pod1-pod1 0bbcb963f9fd pod1

# 关闭pod和容器
[sujx@docker podman]$ podman play kube pod1-build.yml --down
Pods stopped:
0bbcb963f9fdb678ee1e27c2601e3dbefaeecca9e5bc66b1acf4bdf498054e20
Pods removed:
0bbcb963f9fdb678ee1e27c2601e3dbefaeecca9e5bc66b1acf4bdf498054e20
Secrets removed:
Volumes removed: