An experiment running multiple Rootless containers in podman.
- Option 1: Port-forwarding
- Option 2: Podman Pods
Source Code
- UI app: Todos-WebUI - (Java) Spring Boot Web app that vendors a Vue.js frontend Todo(s) UI.
- Routing app: Todos-Edge - (Kotlin) Spring Cloud Gateway app used as a micro gateway between the UI and backend.
- Service API app: Todos-MySQL - (Kotlin) Spring Boot API and Data Access app.
- Database: MySQL Container - MySQL from softwarecollections.org.
Container Images
- UI container: Todos-WebUI on quay.io
- Routing container: Todos-Edge on quay.io
- Service API container: Todos-MySQL on quay.io
- Database container: MySQL Container on registry.redhat.io
1. Grab container images
podman login quay.io
podman login registry.redhat.io
podman pull registry.redhat.io/rhel8/mysql-80
podman pull quay.io/corbsmartin/todos-mysql
podman pull quay.io/corbsmartin/todos-webui
podman pull quay.io/corbsmartin/todos-edge
podman images
- Rootless containers lack privileges to create virtual network interfaces (vNICs) in the linux kernel, thus...
- They're isolated because there's no SDN.
- They don't have an IP Address and can't directly communicate with other containers.
- Networking between Rootless containers is accomplished by port-forwarding with the host.
1. Start the Database container
- Run MySQL container, map hostPort 3306 to containerPort 3306
podman run --name todos-db -d \
-p 3306:3306 \
-e MYSQL_USER=user1 \
-e MYSQL_PASSWORD=mysql123 \
-e MYSQL_DATABASE=todos \
-e MYSQL_ROOT_PASSWORD=mysql123 \
registry.redhat.io/rhel8/mysql-80
2. Start the Service API container
- Get your
HOST_IP
- Run Todos-MySQL container
- Map hostPort 8081 to containerPort 8081
- Set environment variables for MySQL, NOTE use
$HOST_IP
notlocalhost
.
# First get the IP of your host (not localhost)
# macOS
HOST_IP=$(ipconfig getifaddr `route get default | grep interface | awk '{print $2}'`); echo $HOST_IP
# RHEL/Fedora/Centos
HOST_IP=$(hostname -I | awk '{print $1}'); echo $HOST_IP
# Run todos-mysql container, setting env vars accordingly, especially MYSQL_HOST=${HOST_IP}
podman run --name todos-mysql -d -p 8081:8081 \
-e "SERVER_PORT=8081" \
-e "SPRING_PROFILES_ACTIVE=mysql" \
-e "MYSQL_USER=user1" \
-e "MYSQL_PASSWORD=mysql123" \
-e "MYSQL_HOST=${HOST_IP}" \
-e "MYSQL_DATABASE=todos" \
quay.io/corbsmartin/todos-mysql
3. Start the UI container
- Run Todos-WebUI, map hostPort 8080 to containerPort 8080
podman run --name todos-webui -d -p 8080:8080 \
-e "SERVER_PORT=8080" \
-e "SPRING_SECURITY_USER_NAME=Podman" \
-e "TODOS_WEBUI_PLACEHOLDER=Learn podman" \
quay.io/corbsmartin/todos-webui
4. Start the Routing container
- Get your
HOST_IP
- Run Todos-Edge container
- Publish hostPort 8000 and map to containerPort 8000
- Set environment variables for the UI (Todos-WebUI) and API (Todos-MySQL) endpoints
# First get the IP of your host (not localhost)
# macOS
HOST_IP=$(ipconfig getifaddr `route get default | grep interface | awk '{print $2}'`); echo $HOST_IP
# RHEL/Fedora/Centos
HOST_IP=$(hostname -I | awk '{print $1}'); echo $HOST_IP
# Run and pass in endpoints for the UI and API, map hostPort 8000 to containerPort 8000
podman run --name todos-edge -d -p 8000:8000 \
-e "SERVER_PORT=8000" \
-e "API_ENDPOINT=http://${HOST_IP}:8081" \
-e "UI_ENDPOINT=http://${HOST_IP}:8080" \
quay.io/corbsmartin/todos-edge
5. Grok
- Open UI: http://localhost:8000
- Call the Service API on hostPort 8081.
# Get all
curl http://localhost:8081/todos/
# Create todo
curl -X POST http://localhost:8081/todos/ \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"id":"1003", "title":"Learn podman"}'
# Get todo
curl http://localhost:8081/todos/1003
# Update todo complete
curl -X PATCH http://localhost:8081/todos/1003 \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"id":"1003", "complete":true}'
# Get complete todos
curl http://localhost:8081/todos/complete
# Delete todo
curl -X DELETE http://localhost:8081/todos/1003
# Get all
curl http://localhost:8081/todos/
- Execute SQL on the database container via podman
# Select all
podman exec -it todos-db /bin/bash \
-c 'mysql -uuser1 -pmysql123 -e "SELECT * FROM todos.todos;"'
# Insert todo
podman exec -it todos-db /bin/bash \
-c 'mysql -uuser1 -pmysql123 -e "INSERT INTO todos.todos(id, title, complete) VALUE (1005, \"Learn podman exec\", FALSE);"'
# Select todo
podman exec -it todos-db /bin/bash \
-c 'mysql -uuser1 -pmysql123 -e "SELECT * FROM todos.todos WHERE id=1005;"'
# Update todo
podman exec -it todos-db /bin/bash \
-c 'mysql -uuser1 -pmysql123 -e "UPDATE todos.todos SET complete=TRUE WHERE id=1005;"'
# Select complete todos
podman exec -it todos-db /bin/bash \
-c 'mysql -uuser1 -pmysql123 -e "SELECT * FROM todos.todos WHERE complete=TRUE;"'
# Delete todo
podman exec -it todos-db /bin/bash \
-c 'mysql -uuser1 -pmysql123 -e "DELETE FROM todos.todos WHERE id=1005;"'
# Select all
podman exec -it todos-db /bin/bash \
-c 'mysql -uuser1 -pmysql123 -e "SELECT * FROM todos.todos;"'
As mentioned above, Rootless containers don't run with enough privileges to create virtual network interfaces. This is often sighted as a "limitation" of Rootless containers but another view is it's a secure-by-default posture.
That's all well and good but we're still left with the problem of connecting containers, Option 1 - highlighted port-forwarding with the host interface. This works in small batches but a downside is each container becomes accessible from the host. Typically, not all containers need to be accessible from the outside, they require simple east-to-west container-to-container connectivity, where a smaller number (1 or 2) require north-to-south connectivity. That is to say, expose themselves externally to clients, in this case the host.
With the sample Todo apps, the only one that requires external exposure is the one users interact with. The rest can get by with east-to-west connectivity, and thus be fully encapsulated from a networking perspective in a "pod". Podman supports such a setup and thus gives Container developers tools to architect "well formed" deployment patterns. Incidentally, this is where the "pod" in Podman surfaces, which is similar to the "pod" abstraction in K8s. We simply toss all containers in a pod where they can communicate on localhost
, and allow external traffic by poking a hole in it. Let's grok this idea...
1. Create a pod and map hostPort 8000 to podPort 8000
# Create a pod and publish containerPort 8000 to hostPort 8000
podman pod create --name todos-pod -p 8000:8000
2. Run all the apps in the pod
# Create a mysql container, no need to publish ports,
# 3306 is accessible by other containers in the pod.
podman run --name todos-db --pod todos-pod -d \
-e "MYSQL_USER=user1" \
-e "MYSQL_PASSWORD=mysql123" \
-e "MYSQL_DATABASE=todos" \
-e "MYSQL_ROOT_PASSWORD=mysql123" \
registry.redhat.io/rhel8/mysql-80
# Create the todos-mysql Service API container, again no need to publish ports,
# this container can reach mysql on localhost:3306 by virtue of being in the same pod.
podman run --name todos-mysql --pod todos-pod -d \
-e "SERVER_PORT=8081" \
-e "SPRING_PROFILES_ACTIVE=mysql" \
-e "MYSQL_USER=user1" \
-e "MYSQL_PASSWORD=mysql123" \
-e "MYSQL_DATABASE=todos" \
quay.io/corbsmartin/todos-mysql
# Create the todos-webui container, you got it...no port publishing, this container
# can be accessed by others in the pod.
podman run --name todos-webui --pod todos-pod -d \
-e "SERVER_PORT=8080" \
-e "SPRING_SECURITY_USER_NAME=Podman" \
-e "TODOS_WEBUI_PLACEHOLDER=Learn podman pods" \
quay.io/corbsmartin/todos-webui
# Create the todos-edge container, this is the point of entry for external clients,
# as this container binds to containerPort 8000 and port 8000 is published on the pod.
# The default config for todos-edge is to proxy the UI on localhost:8080,
# and the API on localhost:8081, thus we don't need to explicitly set as before.
podman run --name todos-edge --pod todos-pod -d \
-e "SERVER_PORT=8000" \
quay.io/corbsmartin/todos-edge
3. Grok
- Open UI: http://localhost:8000
# follow container logs in the pod
podman pod logs -f -c todos-db todos-pod
podman pod logs -f -c todos-mysql todos-pod
podman pod logs -f -c todos-webui todos-pod
podman pod logs -f -c todos-edge todos-pod
podman pod ps --ctr-names --ctr-status
podman pod stats todos-pod
podman pod top todos-pod