Docker Compose & Pods

15 MINUTE EXERCISE

As soon as you start using one container, you start to recognise the need for multiple containers and get to the problem of scaling and orchestrating.

Podman exists to offer a daemonless container engine for managing OCI-compliant containers on your Linux system. Users love it for its ease of adoption as an alternative to Docker. However, many users and the broader container community have been telling us that one missing feature - compatibility with Docker Compose.

In this section, we are going to demonstrate the steps you need to orchestrate multiple containers on RHEL 8. Specifically how Podman can take you a step towards packaging containers for Kubernetes with the following:

  1. Using docker-compose with Podman

  2. Using Podman to auto-generate yaml for Kubernetes and play it for containers.

There is an open source project Podman Compose (https://github.com/containers/podman-compose) but its not (at this stage) supported by Red Hat and is not covered in this lab.

Why Docker Compose?

Many people use 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.

Examples of Docker compose files can be found here: https://github.com/docker/awesome-compose.git. Below is an example of docker compose yaml file which will be similar to what we use in this lab.

version: '3.7'
services:
  gitea:
    image: gitea/gitea:latest
    environment:
      - DB_TYPE=postgres
      - DB_HOST=db:5432
      - DB_NAME=gitea
      - DB_USER=gitea
      - DB_PASSWD=gitea
    restart: always
    volumes:
      - git_data:/data
    ports:
      - 3000:3000
  db:
    image: postgres:alpine
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gitea
    restart: always
    volumes:
      - db_data:/var/lib/postgresql/data
    expose:
      - 5432
volumes:
  db_data:
  git_data:

Podman support with Docker Compose

Up to now, support for Docker Compose, the command-line utility that orchestrates multiple Docker containers for local development, was missing. With Podman 3, we have begun to support Compose.

Docker Compose is a python based application which is used to co-ordinate multiple containers. It communicates with docker behind the scenes via an API layer. With RHEL 8 and the removal of docker as a package, this restful API backend has now been implemented to use daemonless Podman. Docker compose requires root access and this experience of Docker-compose with root access is now available in Podman.

The api requires the podman-docker package to be installed and the podman api to be enabled which we have seen with RHEL Cockpit.

Setup Docker Compose with Podman

The first step is to ensure that all the required packages are installed and set up the Podman (3.0 or greater) system service using systemd and api is enabled. The docker-compose executable is downloaded directly but this can also be installed with pip on some systems if a build is unavailable.

sudo yum install podman-docker
curl -L "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o docker-compose

Once the binary is downloaded, we move it into /usr/local/bin and we make it executable:

sudo mv docker-compose /usr/local/bin && sudo chmod +x /usr/local/bin/docker-compose

After installing the packages, start the Podman systemd socket-activated service using the following command:

sudo systemctl start podman.socket

Verify the system service is running by hitting the ping endpoint and see if we get a response. This step needs to be successful before we can proceed further. Confirm "OK" is returned.

sudo curl -H "Content-Type: application/json" --unix-socket /var/run/docker.sock http://localhost/_ping

We can now confidently run Compose knowing the RESTful API is working.

Using Docker Compose with Podman

The following exercise demonstrates how to use Compose by using two examples that Docker has curated and maintained in the awesome-compose (https://github.com/docker/awesome-compose) Git repository. If git is not installed, then use yum to install it.

sudo yum install git

There are many examples in this repository, but we will use a base setup for the project Gitea, which describes itself as a community-managed lightweight code hosting solution written in Golang.

cd awesome-compose/gitea-postgres

The docker-compose up command looks for the docker-compose.yaml within the directory. Run the following to bring up the containers.

sudo env "PATH=$PATH" docker-compose up

The README for this docker-compose setup says to visit localhost:3000 in your browser to verify it is working. By the Compose output, you should see that docker-compose has created a network, two volumes, and two containers. We can observe the two containers in another terminal with the podman ps command. In a new terminal run the following podman command

sudo podman ps

The network can be seen with podman network ls.

sudo podman network ls

Lastly, the volumes can be displayed with podman volume ls.

sudo podman volume ls

To bring down the Docker Compose containers, we just need to interrupt docker-compose with a Ctrl+C.

^CGracefully stopping... (press Ctrl+C again to force)
Stopping gitea-postgres_gitea_1 ... done
Stopping gitea-postgres_db_1    ... done
$

Using Podman to bridge to Kubernetes

In this exercise we will look out how Podman can provide a bridge to Kubernetes by generating or using kubernetes based yaml files rather than Docker compose yaml files. With Docker and Podman we have been running containers so far, but with this step we will use "pods" which are the lowest schedulable unit in kubernetes. Pods can contain one or more containers.

Podman pods are similar 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.

Gitea!

Every Podman pod includes an "infra" container. Its purpose is to hold the namespaces associated but it does nothing, but go to sleep. Its purpose is to hold the namespaces associated with the pod and allow podman to connect other containers to the pod. This allows you to start and stop containers within the POD and the pod will stay running, whereas if the primary container controlled the pod, this would not be possible.

With our previous docker compose file run docker-compose and find the container names which are running.

sudo env "PATH=$PATH" docker-compose up

In a new terminal run the following podman command

sudo podman ps
[student2@ansible-1 ~]$ sudo podman ps
CONTAINER ID  IMAGE                              COMMAND               CREATED         STATUS             PORTS                   NAMES
7997ea39059d  docker.io/library/postgres:alpine  postgres              7 hours ago     Up 28 seconds ago                          gitea-postgres_db_1
6a5500ebf45e  docker.io/gitea/gitea:latest       /bin/s6-svscan /e...  28 seconds ago  Up 28 seconds ago  0.0.0.0:3000->3000/tcp  gitea-postgres_gitea_1

Now we have two containers running. Lets export the kuubernetes yaml associated, replace gitea-postgres_db_1 and gitea-postgres_gitea_1 with names

sudo podman generate kube gitea-postgres_db_1 > gitea-postgres_db.yml
sudo podman generate kube gitea-postgres_gitea_1 > gitea-postgres_gitea.yml

Take a look at the files generated to familiarise what has been generated

# 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-3.0.2-dev
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-06-17T01:01:28Z"
  labels:
    app: gitea-postgresdb1
  name: gitea-postgresdb1
spec:
  containers:
  - args:
    - postgres
    command:
    - docker-entrypoint.sh
    env:
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: container
      value: podman
    - name: LANG
      value: en_US.utf8
    - name: PG_MAJOR
      value: "13"
    - name: PGDATA
      value: /var/lib/postgresql/data
    - name: POSTGRES_PASSWORD
      value: gitea
    - name: PG_VERSION
      value: "13.3"
    - name: PG_SHA256
      value: 3cd9454fa8c7a6255b6743b767700925ead1b9ab0d7a0f9dcb1151010f8eb4a1
    - name: POSTGRES_USER
      value: gitea
    - name: POSTGRES_DB
      value: gitea
    image: docker.io/library/postgres:alpine
    name: gitea-postgresdb1
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_AUDIT_WRITE
      privileged: false
      readOnlyRootFilesystem: false
      seLinuxOptions: {}
    workingDir: /
  dnsConfig: {}
status: {}
# 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-3.0.2-dev
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-06-17T01:01:34Z"
  labels:
    app: gitea-postgresgitea1
  name: gitea-postgresgitea1
spec:
  containers:
  - args:
    - /bin/s6-svscan
    - /etc/s6
    command:
    - /usr/bin/entrypoint
    env:
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: container
      value: podman
    - name: GITEA_CUSTOM
      value: /data/gitea
    - name: DB_NAME
      value: gitea
    - name: DB_USER
      value: gitea
    - name: DB_PASSWD
      value: gitea
    - name: USER
      value: git
    - name: DB_TYPE
      value: postgres
    - name: DB_HOST
      value: db:5432
    image: docker.io/gitea/gitea:latest
    name: gitea-postgresgitea1
    ports:
    - containerPort: 3000
      hostPort: 3000
      protocol: TCP
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_AUDIT_WRITE
      privileged: false
      readOnlyRootFilesystem: false
      seLinuxOptions: {}
    workingDir: /
  dnsConfig: {}
status: {}

Now stop the gitea process by stopping the docker compose or using podman stop on both containers. To bring down the Docker Compose containers, we just need to interrupt docker-compose with a Ctrl+C.

^CGracefully stopping... (press Ctrl+C again to force)
Stopping gitea-postgres_gitea_1 ... done
Stopping gitea-postgres_db_1    ... done
$

So lets now run the gitea process as a pod with 2 containers rather than the docker compose file. The following file has been created from the generated yaml file by podman on both containers.

cat <<EOF > gitea_pod.yml
# 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-3.0.2-dev
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-06-17T01:01:28Z"
  labels:
    app: gitea-postgresdb1
  name: gitea-postgresdb1
spec:
  containers:
  - args:
    - /bin/s6-svscan
    - /etc/s6
    command:
    - /usr/bin/entrypoint
    env:
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: container
      value: podman
    - name: GITEA_CUSTOM
      value: /data/gitea
    - name: DB_NAME
      value: gitea
    - name: DB_USER
      value: gitea
    - name: DB_PASSWD
      value: gitea
    - name: USER
      value: git
    - name: DB_TYPE
      value: postgres
    - name: DB_HOST
      value: localhost:5432
    image: docker.io/gitea/gitea:latest
    name: gitea-postgresgitea1
    ports:
    - containerPort: 3000
      hostPort: 3000
      protocol: TCP
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_AUDIT_WRITE
      privileged: false
      readOnlyRootFilesystem: false
      seLinuxOptions: {}
    workingDir: /
  - args:
    - postgres
    command:
    - docker-entrypoint.sh
    env:
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    - name: TERM
      value: xterm
    - name: container
      value: podman
    - name: LANG
      value: en_US.utf8
    - name: PG_MAJOR
      value: "13"
    - name: PGDATA
      value: /var/lib/postgresql/data
    - name: POSTGRES_PASSWORD
      value: gitea
    - name: PG_VERSION
      value: "13.3"
    - name: PG_SHA256
      value: 3cd9454fa8c7a6255b6743b767700925ead1b9ab0d7a0f9dcb1151010f8eb4a1
    - name: POSTGRES_USER
      value: gitea
    - name: POSTGRES_DB
      value: gitea
    image: docker.io/library/postgres:alpine
    name: gitea-postgresdb1
    ports:
    - containerPort: 5432
      hostPort: 5432
      protocol: TCP
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities:
        drop:
        - CAP_MKNOD
        - CAP_AUDIT_WRITE
      privileged: false
      readOnlyRootFilesystem: false
      seLinuxOptions: {}
    workingDir: /
  dnsConfig: {}
status: {}
EOF

Copy the above and save it to gitea_pod.yml

podman play kube will read in a structured file of Kubernetes YAML. It will then recreate the containers, pods or volumes described in the YAML.

sudo podman play kube gitea_pod.yml

Navigate to localhost:3000 in your local browser and check the gitea is now running now in a pod rather than through Docker compose.

Gitea!

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

sudo podman pod list

Congratulations you have now completed this task and understood how Podman can support multiple containers.

Further examples using pods with Podman

Take a look at the help and the options you get with podman pod command.

sudo podman pod --help

If you need to rsh into a pod you can always use this command

sudo podman exec -it <pod_id/name> /bin/bash

The following commands are useful to understand and try with your new pod running.

sudo podman pod stop <pod_name>
sudo podman pod rm <pod_name>
sudo podman pod create -n my-app -p <port>:<port>

Example wordpress site - Create a pod manually and add 2 containers

#create pod and add maridb container in one go
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 9080:80 \
 --name=wptest-db mariadb

# add wordpress container to pod wpapp_pod
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

Running in OpenShift

The generated yaml here is still in an early phase but it can provide you the basis to develop your yaml when migrating from Docker to Kubernetes. Note we have generated yaml for a Pod and typically you may want to build yaml for Deployment/ConfigMap/Secret etc. You can stil try to apply this yaml and get a running Pod in OpenShift, but may need some tweaks which we found like removing the "seLinuxOptions" line.

In the next section you will use OpenShift. Outside of this lab you are welcome to sign up for a free Developer sandbox environment. https://developers.redhat.com/developer-sandbox/activities/get-started-with-your-developer-sandbox

If you are trying to deploy in the sandbox from docker.io container registry , there can be errors if docker pull limits are reached due to the popularity of the sandboxes. In this case you can adjust the image in the yaml to the following

Wrap up

The Red Hat engineers have taken a step towards support of Docker compose with Podman. It has been a Minimum Viable Product (MVP) approach which brings the same experience (running as root). This is an area that will continue to develop given some of the Podman fundamentals such as not running as privileged is not yet 100%. However it should enable the ability for organisations to move their Docker compose setups to Podman and embrace Kubernetes standards.

One known caveat is that Podman has not and will not implement the Swarm function. Therefore, if your Docker Compose instance uses Swarm, it will not work with Podman. With the 3.0 release, Podman can now work nicely with Docker Compose to orchestrate containers, which is a huge step toward daemonless container management on Linux.

The following links to various articles provide further reading and reference to the Move to Kube project which helps to move docker compose files as well.

If you try something that works with Docker and doesn’t work with non-root Podman, first try root with Podman.