It feels like forever since I wrote my Red Hat Enterprise Linux 8 Beta intro to Podman. In fact, it's been quite a while, and a lot has happened since then. For some time now, I've been planning on moving the Digital Ocean Droplet that hosts my sites from the CentOS 7 Docker platform to the CentOS 8 Podman platform. I would really love to do something more sophisticated, but to be honest, a single host running containers is all I need. So, I set out to move from the Docker Compose world to Podman.
docker-compose alternatives still emerging
I used Docker Compose for one simple reason: I can define my applications in a portable and easy-to-write YAML file, which gives me the ability to group each site with its dependencies. For instance, this site requires a web server running PHP and a database to store its data. Those are two containers, and managing them separately seems silly. Instead, I grouped them in a Docker Compose file. This solution allows me to work with the whole stack when I want to do things like stop services, or pull in updates for the container images I chose to use.
Pods
Well, moving to CentOS 8 meant replacing Docker with Podman. Podman does not have a counterpart to the docker-compose
command. Well, it does, sort of. There's a project in the works called podman-compose, which is supposed to do the same basic thing as docker-compose
. I wanted to find the "right" solution, though. Honestly, that was not an easy task. You'd think that a Google search for the "Podman counterpart to docker-compose
" would get an article about how Podman replaces that functionality with something else. I couldn't find anything, though. What I did find was a reference to pods in Podman. Pods are a way of grouping containers together inside their own namespace, network, and security context. You can even start and stop the whole pod at once. The only thing it doesn't get me is a clean YAML file to define my services.
Note: I found this great example from Red Hat on pods, and it even touches on networking: Podman: Managing pods and containers in a local container runtime.
Podman play
Podman does, however, let you import Kubernetes definitions using the podman play
command. Kubernetes definitions are YAML. It sounds like the solution for me. I spent some time trying to learn how to write these things and eventually came across the Kompose tool. Kompose converts docker-compose
files into Kubernetes definitions. I thought it was perfect. Except it wasn't. What I needed specifically was a pod definition, and that's not what Kompose gave me. I might have been able to make a pod definition out of what I had, but I had another hunch.
Podman generate
Podman lets you generate Kubernetes definitions from the existing runtime. For example, if you have a running container, you can use podman generate
to create a YAML file to define that container. You can also do that with a pod. So, I manually defined one of my WordPress sites in Podman.
Here are a few notes on that process.
Mapping ports
In the Docker world, ports are mapped to containers. That's true in Podman as well—except when you're running inside a pod. See, the pod is like a container of containers. Networking within the pod is more similar to networking within a host OS. Pods reach each other over the local host, and external networking reaches the pod, not the containers directly. When you run containers in a pod, you need to map ports on the pod like you would on the container in Docker or docker-compose
. I also found that, although one of the benefits to Podman is the ability to run as a standard user, I had to do all of this as root because of some security problems I ran into when I created the pods. The problems were mainly centered around SELinux. I will likely circle back and try to re-do all of this without superuser privileges.
So, let's create a pod:
[gangrif@batou-lan ~]$ sudo podman pod create --name my-pod -p 8080:80
850425b9c02dc438a04c278196ef645fc9b8a27070a80d2c1d53aca0f1730502
[gangrif@batou-lan ~]$ sudo podman pod ls
POD ID NAME STATUS CREATED # OF CONTAINERS INFRA ID
850425b9c02d my-pod Created 7 seconds ago 1 b525a0511d3e
This command creates a pod with port 8080 mapped to inside port 80. If you then spin up a container listening on port 80, you'd have connectivity.
Create a container in the pod
To create a container in the pod, use podman run
, but don't map a port. This makes more sense when you have more than one container to work with, so I'm going to create a database container and then a WordPress container.
[gangrif@batou-lan ~]$ sudo podman run \
-d --restart=always --pod=my-pod \
-e MYSQL_ROOT_PASSWORD="myrootpass" \
-e MYSQL_DATABASE="wp" \
-e MYSQL_USER="wordpress" \
-e MYSQL_PASSWORD="w0rdpr3ss" \
--name=wptest-db mariadb
Trying to pull registry.fedoraproject.org/mariadb...
manifest unknown: manifest unknown
Trying to pull registry.access.redhat.com/mariadb...
name unknown: Repo not found
Trying to pull registry.centos.org/mariadb...
manifest unknown: manifest unknown
Trying to pull docker.io/library/mariadb...
Getting image source signatures
Copying blob 42ed51adaf49 done
Copying blob 127c9761dcba done
Copying blob 7e2d48f22ade done
Copying blob a4a2a29f9ba4 done
Copying blob 4039240d2e0b done
Copying blob d13bf203e905 done
Copying blob 6518a50ecb7c done
Copying blob b5bc5a5c2503 done
Copying blob 67412c7f89bc done
Copying blob 58175d975ba9 done
Copying blob 0c6efbafd3cb done
Copying blob 1e18725209e8 done
Copying blob 05202eb0846d done
Copying config 22851c7fe9 done
Writing manifest to image destination
Storing signatures
fed9756de1017a0ab38ff4b687a854af5422ec2c9fd13e175a78283426ccfc04
[gangrif@batou-lan ~]$ sudo podman run \
-d --restart=always --pod=my-pod \
-e WORDPRESS_DB_NAME="wp" \
-e WORDPRESS_DB_USER="wordpress" \
-e WORDPRESS_DB_PASSWORD="w0rdpr3ss" \
-e WORDPRESS_DB_HOST="127.0.0.1" \
--name wptest-web wordpress
Trying to pull registry.fedoraproject.org/wordpress...
manifest unknown: manifest unknown
Trying to pull registry.access.redhat.com/wordpress...
name unknown: Repo not found
Trying to pull registry.centos.org/wordpress...
manifest unknown: manifest unknown
Trying to pull docker.io/library/wordpress...
Getting image source signatures
Copying blob f54006e0dc29 done
Copying blob e0d3d1244592 done
Copying blob eb2d00c10344 done
Copying blob 8559a31e96f4 done
Copying blob e0276193a084 done
Copying blob 3a60f364b0c5 done
Copying blob 8faf60068506 done
Copying blob c59965a5777f done
Copying blob 42c09ef39fe7 done
Copying blob db37570cfdf4 done
Copying blob 3e309988c00b done
Copying blob 2c289722ebb3 done
Copying blob e80a84c5a269 done
Copying blob 491a234e2c26 done
Copying blob b83f4c8507f7 done
Copying blob 944a23d0ea39 done
Copying blob a650de05eb1e done
Copying blob a7780c30584c done
Copying blob 267943a2fe25 done
Copying blob ed59c3cd6acc done
Copying config a5fef19f5a done
Writing manifest to image destination
Storing signatures
A49711540329f4307ff218c277bb5a2848b37bf6ded922dfd2a839c564ddbdf1
Notice that I pointed the wordpress_db_host
in the env:
to localhost. That's because the WordPress container is going to find the database container on the local host. Like magic. Our pod has three containers. Yes, I ran two, but the third is the container that does the pod magic.
[gangrif@batou-lan ~]$ sudo podman pod ls
POD ID NAME STATUS CREATED # OF CONTAINERS INFRA ID
850425b9c02d my-pod Running 12 minutes ago 3 b525a0511d3e
And, as expected, podman ps gives us two containers.
[gangrif@batou-lan ~]$ sudo podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a49711540329 docker.io/library/wordpress:latest apache2-foregroun... 2 minutes ago Up 2 minutes ago 0.0.0.0:8080->80/tcp wptest-web
fed9756de101 docker.io/library/mariadb:latest mysqld 5 minutes ago Up 5 minutes ago 0.0.0.0:8080->80/tcp wptest-db
In my browser, I can get to the WordPress setup page in my container via localhost:8080.
The initial WordPress setup page is displayed. Success.
We're done, right? Nope. Now we want to make a YAML file that defines all of this.
Generate the YAML for our pod
We output our pod as a YAML definition by using podman generate kube
command:
[gangrif@batou-lan ~]$ sudo podman generate kube my-pod >> my-pod.yaml
[gangrif@batou-lan ~]$ cat my-pod.yaml
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-1.9.3
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2020-07-01T20:17:42Z"
labels:
app: my-pod
name: my-pod
spec:
containers:
- command:
- apache2-foreground
env:
- name: PATH
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- name: TERM
value: xterm
- name: HOSTNAME
value: my-pod
- name: PHP_MD5
- name: PHP_VERSION
value: 7.4.7
- name: PHPIZE_DEPS
value: "autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake
\t\tpkg-config \t\tre2c"
- name: APACHE_CONFDIR
value: /etc/apache2
- name: PHP_ASC_URL
value: https://www.php.net/distributions/php-7.4.7.tar.xz.asc
- name: PHP_EXTRA_BUILD_DEPS
value: apache2-dev
- name: PHP_CFLAGS
value: -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
- name: WORDPRESS_VERSION
value: 5.4.2
- name: WORDPRESS_DB_NAME
value: wp
- name: WORDPRESS_DB_HOST
value: 127.0.0.1
- name: PHP_LDFLAGS
value: -Wl,-O1 -pie
- name: APACHE_ENVVARS
value: /etc/apache2/envvars
- name: WORDPRESS_DB_USER
value: wordpress
- name: PHP_CPPFLAGS
value: -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
- name: PHP_URL
value: https://www.php.net/distributions/php-7.4.7.tar.xz
- name: PHP_INI_DIR
value: /usr/local/etc/php
- name: WORDPRESS_DB_PASSWORD
value: w0rdpr3ss
- name: PHP_EXTRA_CONFIGURE_ARGS
value: --with-apxs2 --disable-cgi
- name: PHP_SHA256
value: 53558f8f24cd8ab6fa0ea252ca8198e2650160649681ce5230c1df1dc2b52faf
- name: WORDPRESS_SHA1
value: e5631f812232fbd45d3431783d3db2e0d5670d2d
- name: GPG_KEYS
value: 42670A7FE4D0441C8E4632349E4FDC074A4EF02D 5A52880781F755608BF815FC910DEB46F53EA312
- name: container
value: podman
image: docker.io/library/wordpress:latest
name: wptest-web
ports:
- containerPort: 80
hostPort: 8080
protocol: TCP
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities: {}
privileged: false
readOnlyRootFilesystem: false
seLinuxOptions: {}
workingDir: /var/www/html
- command:
- mysqld
env:
- name: PATH
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
- name: TERM
value: xterm
- name: HOSTNAME
value: my-pod
- name: MARIADB_MAJOR
value: "10.5"
- name: MARIADB_VERSION
value: 1:10.5.4+maria~focal
- name: MYSQL_ROOT_PASSWORD
value: myrootpass
- name: MYSQL_USER
value: wordpress
- name: GOSU_VERSION
value: "1.12"
- name: GPG_KEYS
value: 177F4010FE56CA3336300305F1656F24C74CD1D8
- name: MYSQL_PASSWORD
value: w0rdpr3ss
- name: MYSQL_DATABASE
value: wp
- name: container
value: podman
image: docker.io/library/mariadb:latest
name: wptest-db
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities: {}
privileged: false
readOnlyRootFilesystem: false
seLinuxOptions: {}
workingDir: /
status: {}
I found that this output needed some cleanup. I deleted a bunch of the env:
entries that I thought the container images would easily re-propagate, and in fact could cause conflicts if I were to build this again from scratch. Here's what I ended up with after editing:
# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-1.9.3
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2020-07-01T20:17:42Z"
labels:
app: my-pod
name: my-pod
spec:
containers:
- name: wptest-web
env:
- name: WORDPRESS_DB_NAME
value: wp
- name: WORDPRESS_DB_HOST
value: 127.0.0.1
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_PASSWORD
value: w0rdpr3ss
image: docker.io/library/wordpress:latest
ports:
- containerPort: 80
hostPort: 8080
protocol: TCP
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities: {}
privileged: false
readOnlyRootFilesystem: false
seLinuxOptions: {}
workingDir: /var/www/html
- name: wptest-db
env:
- name: MYSQL_ROOT_PASSWORD
value: myrootpass
- name: MYSQL_USER
value: wordpress
- name: MYSQL_PASSWORD
value: w0rdpr3ss
- name: MYSQL_DATABASE
value: wp
image: docker.io/library/mariadb:latest
resources: {}
securityContext:
allowPrivilegeEscalation: true
capabilities: {}
privileged: false
readOnlyRootFilesystem: false
seLinuxOptions: {}
workingDir: /
status: {}
You can directly copy and paste the above content into your own file, and then use Podman to bring up exactly the same pod.
Bring up a pod from the YAML
Next, use podman play kube
to start your pod from the defined YAML:
[gangrif@batou-lan ~]$ sudo podman pod ls
[gangrif@batou-lan ~]$ sudo podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[gangrif@batou-lan ~]$ sudo podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[gangrif@batou-lan ~]$ sudo podman play kube ./my-pod.yaml
Trying to pull docker.io/library/wordpress:latest...
Getting image source signatures
Copying blob 3a60f364b0c5 skipped: already exists
Copying blob e0276193a084 skipped: already exists
Copying blob 8559a31e96f4 skipped: already exists
Copying blob f54006e0dc29 skipped: already exists
Copying blob eb2d00c10344 skipped: already exists
Copying blob e0d3d1244592 skipped: already exists
Copying blob 8faf60068506 skipped: already exists
Copying blob 3e309988c00b skipped: already exists
Copying blob 42c09ef39fe7 skipped: already exists
Copying blob c59965a5777f skipped: already exists
Copying blob 2c289722ebb3 skipped: already exists
Copying blob db37570cfdf4 skipped: already exists
Copying blob 491a234e2c26 skipped: already exists
Copying blob 944a23d0ea39 skipped: already exists
Copying blob b83f4c8507f7 skipped: already exists
Copying blob e80a84c5a269 skipped: already exists
Copying blob 267943a2fe25 [--------------------------------------] 0.0b / 0.0b
Copying blob a7780c30584c [--------------------------------------] 0.0b / 0.0b
Copying blob ed59c3cd6acc [--------------------------------------] 0.0b / 0.0b
Copying blob a650de05eb1e [--------------------------------------] 0.0b / 0.0b
Copying config a5fef19f5a done
Writing manifest to image destination
Storing signatures
Trying to pull docker.io/library/mariadb:latest...
Getting image source signatures
Copying blob 42ed51adaf49 skipped: already exists
Copying blob 127c9761dcba skipped: already exists
Copying blob 4039240d2e0b skipped: already exists
Copying blob 1e18725209e8 skipped: already exists
Copying blob d13bf203e905 skipped: already exists
Copying blob 6518a50ecb7c skipped: already exists
Copying blob 67412c7f89bc skipped: already exists
Copying blob 7e2d48f22ade skipped: already exists
Copying blob a4a2a29f9ba4 skipped: already exists
Copying blob 58175d975ba9 skipped: already exists
Copying blob 0c6efbafd3cb skipped: already exists
Copying blob b5bc5a5c2503 [--------------------------------------] 0.0b / 0.0b
Copying config 22851c7fe9 done
Writing manifest to image destination
Storing signatures
Pod:
a91dc8859e85505b43f82f1be248880f76138f0ae86f12c3ffdea4d309d4eacf
Containers:
87a67588fbacf959c95f8f63f9f26ffa7b4fb3c7f8827aadd65bcd94de526e37
cc3f3e7255c8110826565673b5d30ebc223cbfcefa01597714231e1305382d3c
[gangrif@batou-lan ~]$ sudo podman pod ls
POD ID NAME STATUS CREATED # OF CONTAINERS INFRA ID
a91dc8859e85 my-pod Running 10 seconds ago 3 aa53c9308991
[gangrif@batou-lan ~]$ sudo podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cc3f3e7255c8 docker.io/library/mariadb:latest docker-entrypoint... 12 seconds ago Up 11 seconds ago 0.0.0.0:8080->80/tcp wptest-db
87a67588fbac docker.io/library/wordpress:latest docker-entrypoint... 13 seconds ago Up 12 seconds ago 0.0.0.0:8080->80/tcp wptest-web
And if you hit up port 8080, you should get the WordPress setup page, just like before.
Conclusion
I hope this post is helpful. It took me some time to figure out, so I thought it was worth sharing. Happy podmanning.
[ Getting started with containers? Check out this free course. Deploying containerized applications: A technical overview. ]
執筆者紹介
Nate is a Technical Account Manager with Red Hat and an experienced sysadmin with 20 years in the industry. He first encountered Linux (Red Hat 5.0) as a teenager, after deciding that software licensing was too expensive for a kid with no income, in the late 90’s. Since then he’s run everything from BBS’s (remember those?) to derby hat’s containing raspberry pi’s, to Linux systems in his basement, or in enterprise-class data-centers.
He runs his own blog at undrground.org, hosts the Iron Sysadmin Podcast, and when he’s not at a command line, he’s probably in the garage tinkering on his Jeep, or out on the trails.
チャンネル別に見る
自動化
テクノロジー、チームおよび環境に関する IT 自動化の最新情報
AI (人工知能)
お客様が AI ワークロードをどこでも自由に実行することを可能にするプラットフォームについてのアップデート
オープン・ハイブリッドクラウド
ハイブリッドクラウドで柔軟に未来を築く方法をご確認ください。
セキュリティ
環境やテクノロジー全体に及ぶリスクを軽減する方法に関する最新情報
エッジコンピューティング
エッジでの運用を単純化するプラットフォームのアップデート
インフラストラクチャ
世界有数のエンタープライズ向け Linux プラットフォームの最新情報
アプリケーション
アプリケーションの最も困難な課題に対する Red Hat ソリューションの詳細
オリジナル番組
エンタープライズ向けテクノロジーのメーカーやリーダーによるストーリー
製品
ツール
試用、購入、販売
コミュニケーション
Red Hat について
エンタープライズ・オープンソース・ソリューションのプロバイダーとして世界をリードする Red Hat は、Linux、クラウド、コンテナ、Kubernetes などのテクノロジーを提供しています。Red Hat は強化されたソリューションを提供し、コアデータセンターからネットワークエッジまで、企業が複数のプラットフォームおよび環境間で容易に運用できるようにしています。
言語を選択してください
Red Hat legal and privacy links
- Red Hat について
- 採用情報
- イベント
- 各国のオフィス
- Red Hat へのお問い合わせ
- Red Hat ブログ
- ダイバーシティ、エクイティ、およびインクルージョン
- Cool Stuff Store
- Red Hat Summit