Spinning up and Managing Pods with multiple containers with Podman

docker-compose is a popular choice to spin up applications needing multiple containers and run them. It allows to define the configuration in a simple yaml format and with few commands, you can create/manage all your application containers at once. Podman does not have a counterpart to the docker-compose command. There is a project in the works called podman-compose, which is supposed to do the same basic thing as docker-compose.

However Podman uses a concept called pods. Pods are a way of grouping containers together inside their own namespace, network, and security context. You can start and stop the whole pod at once to manage your application.

Podman Pods – A quick Intro

Podman pods are similiar to kubernetes pods in the sense that they can contain one or more containers at a time. With podman play command, you can import kubernetes pod definitions in yaml format.

Every podman pod includes an infra container by default. Its purpose is to hold the namespaces associated with the pod and allow podman to connect other containers to the pod. This also lets pods live, if the pod is not running any application containers.

The default infra container is based on the k8s.gcr.io/pause image,  Unless stated otherwise, all pods will have container based on the default image.

Most of the attributes assigned to pod are actually assigned to the infra container as essentially it holds all namespaces. So all port bindings, cgroup-parent values, kernel namespace, network namespace, user namepace etc. This is more important as all these namespaces are created as part of podman pod creation. So these attributes cannot be changed later in the pod lifecycle. For example, if you create a pod and then later decide you want to add a container that binds new ports, Podman will not be able to do this. You would need to recreate the pod with the additional port bindings before adding the new container.

Installing Podman

We had earlier discussed the basics of installing and working with Podman in our previous blog post. Alternatively, follow instructions to install podman for your specific linux distribution.

Create Podman Pod

The first thing we need to do is create a pod using podman pod create command. In its most basic context, you can simply issue podman pod create and Podman will create a pod without extra attributes. A random name will also be assigned to the pod:

[cloud_user@5ecaa360f91c ~]$ sudo podman pod create
2a70ca9995f6f8be1582492915866a32b67f3e9671335b84981c98adf0540fdd

We can list the pods using podman pod ls or podman pod list command:

[cloud_user@5ecaa360f91c ~]$ sudo podman pod ls
POD ID        NAME                     STATUS   CREATED        INFRA ID      # OF CONTAINERS
2a70ca9995f6  condescending_aryabhata  Created  5 seconds ago  95db0b375e17  1

[cloud_user@5ecaa360f91c ~]$ sudo podman pod list
POD ID        NAME                     STATUS   CREATED             INFRA ID      # OF CONTAINERS
2a70ca9995f6  condescending_aryabhata  Created  About a minute ago  95db0b375e17  1

We may however, not want pods with random ids, and instead give it a meaningful name. For this, we can use --name parameter:

[cloud_user@5ecaa360f91c ~]$ sudo podman pod create --name my_pod
6625710c4ec936fe5339d9fd960713e85ef3ee8460b30d691331faf36c4e725e

[cloud_user@5ecaa360f91c ~]$ sudo podman pod list
POD ID        NAME                     STATUS   CREATED        INFRA ID      # OF CONTAINERS
6625710c4ec9  my_pod                   Created  5 seconds ago  5a1d462d5954  1
2a70ca9995f6  condescending_aryabhata  Created  3 minutes ago  95db0b375e17  1

We can seek additional options using --help:

[cloud_user@5ecaa360f91c ~]$ podman pod create --help
Create a new empty pod

Description:
  After creating the pod, the pod ID is printed to stdout.

  You can then start it at any time with the  podman pod start <pod_id> command. The pod will be created with the initial state 'created'.

Usage:
  podman pod create [options]

Options:
      --add-host strings              Add a custom host-to-IP mapping (host:ip) (default [])
      --cgroup-parent string          Set parent cgroup for the pod
      --dns strings                   Set custom DNS servers
      --dns-opt strings               Set custom DNS options
      --dns-search strings            Set custom DNS search domains
      --hostname string               Set a hostname to the pod
      --infra                         Create an infra container associated with the pod to share namespaces with (default true)
      --infra-command string          The command to run on the infra container when the pod is started
      --infra-conmon-pidfile string   Path to the file that will receive the POD of the infra container's conmon
      --infra-image string            The image of the infra container to associate with the pod (default "k8s.gcr.io/pause:3.2")
      --ip string                     Specify a static IPv4 address for the container
  -l, --label strings                 Set metadata on pod (default [])
      --label-file strings            Read in a line delimited file of labels
      --mac-address string            Container MAC address (e.g. 92:d0:c6:0a:29:33)
  -n, --name string                   Assign a name to the pod
      --network string                Connect a container to a network (default "slirp4netns")
      --network-alias strings         Add network-scoped alias for the container
      --no-hosts                      Do not create /etc/hosts within the container, instead use the version from the image
      --pod-id-file string            Write the pod ID to the file
  -p, --publish strings               Publish a container's port, or a range of ports, to the host (default [])
      --replace                       If a pod with the same name exists, replace it
      --share string                  A comma delimited list of kernel namespaces the pod will share (default "cgroup,ipc,net,uts")

To view the infra container, we can use podman ps -a --pod command:

[cloud_user@5ecaa360f91c ~]$ sudo podman ps -a --pod
[sudo] password for cloud_user: 
CONTAINER ID  IMAGE                 COMMAND  CREATED         STATUS   PORTS   NAMES               POD ID        PODNAME
5a1d462d5954  k8s.gcr.io/pause:3.2           8 minutes ago   Created          6625710c4ec9-infra  6625710c4ec9  my_pod
95db0b375e17  k8s.gcr.io/pause:3.2           12 minutes ago  Created          2a70ca9995f6-infra  2a70ca9995f6  condescending_aryabhata

Note that above output shows two infra containers, one belonging to pod my_pod and another to pod condescending_aryabhatta.

Add application container to Pod

You can add a container to a pod using the --pod option in the podman create and podman run commands.  For example, here we add a container running alpine image to the newly created my_pod:

[cloud_user@5ecaa360f91c ~]$ sudo podman run -dt --pod my_pod docker.io/library/alpine:latest top
Trying to pull docker.io/library/alpine:latest...
Getting image source signatures
Copying blob ba3557a56b15 done  
Copying config 28f6e27057 done  
Writing manifest to image destination
Storing signatures
b04464126cac0861ad0285ec697f87f21b629ce9c3c338ce58028f82c4507d4e

--dt allows pod to run in detached mode. Now if we list containers, we can see that my_pod contains two containers:

[cloud_user@5ecaa360f91c ~]$ sudo podman ps -a --pod
CONTAINER ID  IMAGE                            COMMAND  CREATED         STATUS            PORTS   NAMES               POD ID        PODNAME
b04464126cac  docker.io/library/alpine:latest  top      6 minutes ago   Up 6 minutes ago          compassionate_boyd  6625710c4ec9  my_pod
5a1d462d5954  k8s.gcr.io/pause:3.2                      38 minutes ago  Up 6 minutes ago          6625710c4ec9-infra  6625710c4ec9  my_pod

One single command to create pod and add application container

We can use podman pod run to create a pod as well as run the application container inside it, rather than using two step process. For this, we have to supply the pod name while using podman pod run command. Consider the below command to run a mariadb container:

[cloud_user@5ecaa360f91c ~]$ sudo podman run \
> -d --restart=always --pod new:myapp_pod \
> -e MYSQL_ROOT_PASSWORD="myrootpass" \
> -e MYSQL_DATABASE="wp-db" \
> -e MYSQL_USER="wp-user" \
> -e MYSQL_PASSWORD="w0rdpr3ss" \
> --name=wptest-db mariadb
4223590ae7962edc43f5c19c20af4da32d649ee666c171c9e4b60137c445d653

[cloud_user@5ecaa360f91c ~]$ sudo podman pod list
POD ID        NAME       STATUS   CREATED            INFRA ID      # OF CONTAINERS
65fb277514bc  myapp_pod  Running  12 seconds ago     c300c18d7301  2
6625710c4ec9  my_pod     Running  About an hour ago  5a1d462d5954  2

Notice how we have mentioned that podman needs to create new pod while using --pod parameter. The use of new: indicates to Podman that we want to create a new pod rather than attempt to assign the container to an existing pod. In this context, --name becomes name of the container inside pod:

[cloud_user@5ecaa360f91c ~]$ sudo podman ps -a --pod
[sudo] password for cloud_user: 
CONTAINER ID  IMAGE                             COMMAND  CREATED            STATUS             PORTS   NAMES               POD ID        PODNAME
4223590ae796  docker.io/library/mariadb:latest  mysqld   9 minutes ago      Up 9 minutes ago           wptest-db           65fb277514bc  myapp_pod
c300c18d7301  k8s.gcr.io/pause:3.2                       9 minutes ago      Up 9 minutes ago           65fb277514bc-infra  65fb277514bc  myapp_pod

Creating Application Stack with Pods

Let’s say we want to run a wordpress container connected to mariadb container inside our pod. For this, we need to run two containers in the pod. Lets name this pod as wpapp_pod:

# create pod and add maridb container in one go
[cloud_user@5ecaa360f91c ~]$ sudo podman run \
> -d --restart=always --pod new:wpapp_pod \
> -e MYSQL_ROOT_PASSWORD="myrootpass" \
> -e MYSQL_DATABASE="wp-db" \
> -e MYSQL_USER="wp-user" \
> -e MYSQL_PASSWORD="w0rdpr3ss" \
> -p 8080:80 \
> --name=wptest-db mariadb
5d4bbfcfb8187ea29480acc50593db87d54ac2af1a75fdd9ef591962462ed5c1

# add wordpress container to pod wpapp_pod
[cloud_user@5ecaa360f91c ~]$ sudo podman run \
> -d --restart=always --pod=wpapp_pod \
> -e WORDPRESS_DB_NAME="wp-db" \
> -e WORDPRESS_DB_USER="wp-user" \
> -e WORDPRESS_DB_PASSWORD="w0rdpr3ss" \
> -e WORDPRESS_DB_HOST="127.0.0.1" \
> --name wptest-web wordpress
ea906e2215774a27c9d7ca847dd7a1e1f7a7fa91f91a237da3091467d8b69a46

# list pods
[cloud_user@5ecaa360f91c ~]$ sudo podman pod list
POD ID        NAME       STATUS   CREATED         INFRA ID      # OF CONTAINERS
05272954b454  wpapp_pod  Running  39 seconds ago  8bb0b400a7c6  3

# list all containers in the pod
[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE                               COMMAND               CREATED         STATUS             PORTS                 NAMES
ea906e221577  docker.io/library/wordpress:latest  apache2-foregroun...  16 seconds ago  Up 15 seconds ago  0.0.0.0:8080->80/tcp  wptest-web
5d4bbfcfb818  docker.io/library/mariadb:latest    mysqld                45 seconds ago  Up 44 seconds ago  0.0.0.0:8080->80/tcp  wptest-db
8bb0b400a7c6  k8s.gcr.io/pause:3.2                                      45 seconds ago  Up 44 seconds ago  0.0.0.0:8080->80/tcp  05272954b454-infra

We can now try to connect to our application from the server running pod:

[cloud_user@5ecaa360f91c ~]$ curl http://localhost:8080/wp-admin/install.php
<!DOCTYPE html>
<html lang="en-US" xml:lang="en-US">
<head>
        <meta name="viewport" content="width=device-width" />
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="robots" content="noindex,nofollow" />
        <title>WordPress &rsaquo; Installation</title>
        <link rel='stylesheet' id='dashicons-css'  href='http://localhost:8080/wp-includes/css/dashicons.min.css?ver=5.7' type='text/css' media='all' />
<link rel='stylesheet' id='buttons-css'  href='http://localhost:8080/wp-includes/css/buttons.min.css?ver=5.7' type='text/css' media='all' />
<link rel='stylesheet' id='forms-css'  href='http://localhost:8080/wp-admin/css/forms.min.css?ver=5.7' type='text/css' media='all' />
<link rel='stylesheet' id='l10n-css'  href='http://localhost:8080/wp-admin/css/l10n.min.css?ver=5.7' type='text/css' media='all' />
<link rel='stylesheet' id='install-css'  href='http://localhost:8080/wp-admin/css/install.min.css?ver=5.7' type='text/css' media='all' />
</head>
<body class="wp-core-ui language-chooser">
<p id="logo">WordPress</p>

        <form id="setup" method="post" action="?step=1"><label class='screen-reader-text' for='language'>Select a default language</label>
<select size='14' name='language' id='language'>
<option value="" lang="en" selected="selected" data-continue="Continue" data-installed="1">English (United States)</option>
<option value="af" lang="af" data-continue="Gaan voort">Afrikaans</option>
<option value="ar" lang="ar" data-continue="المتابعة">العربية</option>
<option value="ary" lang="ar" data-continue="المتابعة">العربية المغربية</option>
<option value="as" lang="as" data-continue="Continue">অসমীয়া</option>
...
...
<p class="step"><span class="spinner"></span><input id="language-continue" type="submit" class="button button-primary button-large" value="Continue" /></p></form><script type="text/javascript">var t = document.getElementById('weblog_title'); if (t){ t.focus(); }</script>
        http://localhost:8080/wp-includes/js/jquery/jquery.min.js?ver=3.5.1
http://localhost:8080/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.3.2
http://localhost:8080/wp-admin/js/language-chooser.min.js?ver=5.7
<script type="text/javascript">
jQuery( function( $ ) {
        $( '.hide-if-no-js' ).removeClass( 'hide-if-no-js' );
} );
</script>
</body>
</html>

If you want to publish to external world, you might need to configure firewall rules on server and network accordingly.

Stop/Start Containers in the Pod

In Podman, the status of the pod and its containers can be exclusive to each other meaning that containers within pods can be restarted, stopped, and started without impacting the status of the pod. Since infra container is responsible to keep alive pod, it will keep it alive.

Stop Individual Container

This needs use of the container name in the pod along with podman stop command. Continuing from our previous example, we can stop wordpress container wptest-web like this:

[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE                               COMMAND               CREATED         STATUS                 PORTS                 NAMES
ea906e221577  docker.io/library/wordpress:latest  apache2-foregroun...  53 minutes ago  Up About a minute ago  0.0.0.0:8080->80/tcp  wptest-web
5d4bbfcfb818  docker.io/library/mariadb:latest    mysqld                53 minutes ago  Up About a minute ago  0.0.0.0:8080->80/tcp  wptest-db
8bb0b400a7c6  k8s.gcr.io/pause:3.2                                      53 minutes ago  Up About a minute ago  0.0.0.0:8080->80/tcp  05272954b454-infra

[cloud_user@5ecaa360f91c ~]$ sudo podman stop wptest-web
ea906e2215774a27c9d7ca847dd7a1e1f7a7fa91f91a237da3091467d8b69a46

[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE                             COMMAND  CREATED         STATUS                 PORTS                 NAMES
5d4bbfcfb818  docker.io/library/mariadb:latest  mysqld   54 minutes ago  Up About a minute ago  0.0.0.0:8080->80/tcp  wptest-db
8bb0b400a7c6  k8s.gcr.io/pause:3.2                       54 minutes ago  Up About a minute ago  0.0.0.0:8080->80/tcp  05272954b454-infra

Start individual container

This needs use of the container name in the pod along with podman start command. Continuing from our previous example, we can start wordpress container wptest-web like this:

[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE                             COMMAND  CREATED         STATUS                 PORTS                 NAMES
5d4bbfcfb818  docker.io/library/mariadb:latest  mysqld   54 minutes ago  Up About a minute ago  0.0.0.0:8080->80/tcp  wptest-db
8bb0b400a7c6  k8s.gcr.io/pause:3.2                       54 minutes ago  Up About a minute ago  0.0.0.0:8080->80/tcp  05272954b454-infra

[cloud_user@5ecaa360f91c ~]$ sudo podman start wptest-web
wptest-web

[cloud_user@5ecaa360f91c ~]$ sudo podman pod ps
POD ID        NAME       STATUS   CREATED         INFRA ID      # OF CONTAINERS
05272954b454  wpapp_pod  Running  55 minutes ago  8bb0b400a7c6  3
[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE                               COMMAND               CREATED         STATUS             PORTS                 NAMES
ea906e221577  docker.io/library/wordpress:latest  apache2-foregroun...  54 minutes ago  Up 20 seconds ago  0.0.0.0:8080->80/tcp  wptest-web
5d4bbfcfb818  docker.io/library/mariadb:latest    mysqld                55 minutes ago  Up 3 minutes ago   0.0.0.0:8080->80/tcp  wptest-db
8bb0b400a7c6  k8s.gcr.io/pause:3.2                                      55 minutes ago  Up 3 minutes ago   0.0.0.0:8080->80/tcp  05272954b454-infra

Stop all containers at once

This needs use of the pod name along with podman pod stop command. Continuing from our previous example, we can stop all containers in pod wpapp_pod like this:

[cloud_user@5ecaa360f91c ~]$ sudo podman pod list
POD ID        NAME       STATUS   CREATED         INFRA ID      # OF CONTAINERS
05272954b454  wpapp_pod  Running  58 minutes ago  8bb0b400a7c6  3

[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE                               COMMAND               CREATED         STATUS            PORTS                 NAMES
ea906e221577  docker.io/library/wordpress:latest  apache2-foregroun...  58 minutes ago  Up 3 minutes ago  0.0.0.0:8080->80/tcp  wptest-web
5d4bbfcfb818  docker.io/library/mariadb:latest    mysqld                58 minutes ago  Up 6 minutes ago  0.0.0.0:8080->80/tcp  wptest-db
8bb0b400a7c6  k8s.gcr.io/pause:3.2                                      58 minutes ago  Up 6 minutes ago  0.0.0.0:8080->80/tcp  05272954b454-infra

[cloud_user@5ecaa360f91c ~]$ sudo podman pod stop wpapp_pod
05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a

[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE   COMMAND  CREATED  STATUS  PORTS   NAMES

[cloud_user@5ecaa360f91c ~]$ sudo podman pod list
POD ID        NAME       STATUS  CREATED         INFRA ID      # OF CONTAINERS
05272954b454  wpapp_pod  Exited  59 minutes ago  8bb0b400a7c6  3

Start all containers at once

This needs use of the pod name along with podman pod start command. Continuing from our previous example, we can start all containers in pod wpapp_pod like this:

[cloud_user@5ecaa360f91c ~]$ sudo podman pod start wpapp_pod
[sudo] password for cloud_user: 
05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a

[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE                               COMMAND               CREATED            STATUS            PORTS                 NAMES
ea906e221577  docker.io/library/wordpress:latest  apache2-foregroun...  About an hour ago  Up 7 seconds ago  0.0.0.0:8080->80/tcp  wptest-web
5d4bbfcfb818  docker.io/library/mariadb:latest    mysqld                About an hour ago  Up 7 seconds ago  0.0.0.0:8080->80/tcp  wptest-db
8bb0b400a7c6  k8s.gcr.io/pause:3.2                                      About an hour ago  Up 8 seconds ago  0.0.0.0:8080->80/tcp  05272954b454-infra

Remove Containers and Pods

Once you are ready to wrap up your application, you can use podman pod rm to remove the pod and all containers within it. This is designed to make sure that you would not be able to remove pod containing active containers. So you need to first stop all containers and then remove the pod:

# Cannot remove pods containing active application containers
[cloud_user@5ecaa360f91c ~]$ sudo podman pod rm wpapp_pod
Error: pod 05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a has containers that are not ready to be removed: cannot remove container 5d4bbfcfb8187ea29480acc50593db87d54ac2af1a75fdd9ef591962462ed5c1 as it is running - running or paused containers cannot be removed without force: container state improper

# Stop all application containers at once 
[cloud_user@5ecaa360f91c ~]$ sudo podman pod stop wpapp_pod
05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a

# verify that no application containers are running
[cloud_user@5ecaa360f91c ~]$ sudo podman ps
CONTAINER ID  IMAGE   COMMAND  CREATED  STATUS  PORTS   NAMES

# remove the pod 
[cloud_user@5ecaa360f91c ~]$ sudo podman pod rm wpapp_pod
05272954b454c5ab4d5a7f4de83e18dcad901f58b5604544bb8522399887f15a

2 thoughts on “Spinning up and Managing Pods with multiple containers with Podman

  1. […] This blog post continues from where we left in our earlier blog post, where we discussed how systemd and podman fits in together to run and manage containers as systemd services. We discussed how we can do the same for a specific containers and learned to create generic systemd unit files. We also discussed few use cases where this integration is useful. As we know, pods in podman are a way to group and manage multiple application containers as one. So we can start them together, manage them together and then remove them together once done. If we do need, we can manage them individually as well. Basics of pods are covered here. […]

    Like

Leave a Reply to Working with pods with podman generate and podman play – mohitgoyal.co Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s