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:
-
Using docker-compose with Podman
-
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.
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.
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.
-
Podman Managing pods and containers in a local container runtime: https://www.redhat.com/sysadmin/podman-play-kube
-
Articles by a Principal Engineer leading the Container Runtimes team: https://www.redhat.com/sysadmin/users/brent-baude
-
Move to Kube & Docker Compose: https://move2kube.konveyor.io/tutorials/docker-compose/
-
For an in depth discussion, this video provides a good discussion to understand the hows and whys of supporting docker-compose with Podman. The Level Up Hour (E29): Docker Compose with Podman v3 - https://www.youtube.com/watch?v=hyOXwzvLXOM
If you try something that works with Docker and doesn’t work with non-root Podman, first try root with Podman. |