Container Management (namespaces,containerfile)
Skill Level: Advanced
1. Overview
Podman (the POD manager) is a tool for developing, managing, and running containers on your Linux systems.
In this unit, we will get familiar with application containers and the podman CLI.
2. Getting Started
For these exercises, you will be using the host node3
as user root
.
From host bastion
, ssh to node3
.
ssh node3
Use sudo
to elevate your privileges.
[[ "$UID" == 0 ]] || sudo -i
Verify that you are on the right host for these exercises.
workshop-podman-checkhost.sh
You are now ready to proceed with these exercises.
3. Core Concepts
In this unit you will be exposed to some advanced concepts involving containers:
-
how they behave,
-
how they are built, and
-
how they can be customized
These exercises build upon the material learned in the intermediate exercises for container management.
There are no dependencies on prior work so you can proceed without having completed the intermediate activities.
4. Exercise: Exploring Container Namespaces
Namespaces are used to isolate data that is specific to each container like hostname, process, network, and mount/filesystem data. Let’s explore how.
4.1. Clean Up
Let’s start by ensuring we have a clean environment.
podman kill --all
podman rm --all
podman rmi --all
4.2. Pull Required Images
Time to pull a container from the Red Hat registry. For these exercises we will use a RHEL 10 Universal Base Image (UBI).
podman search ubi10
From the command above, we see there are many versions of the UBI image available. Let’s grab the standard image.
podman pull registry.access.redhat.com/ubi10/ubi:latest
Trying to pull registry.access.redhat.com/ubi10/ubi:latest... Getting image source signatures Checking if image destination supports signatures Copying blob 7fdd59f6557b done | Copying config da862ffa17 done | Writing manifest to image destination Storing signatures da862ffa17875f5980832d6d8cd545f75e7cf3175a710b6529d7f7fc5fd650d1
4.3. UTS Namespace / Hostname
Run the following command inside a UBI container instance.
podman run ubi cat /proc/sys/kernel/hostname
6cea3acc210d
As you can see, the hostname in the container’s namespace is NOT the same as the host platform (node3.example.com). It is unique and is by default identical to the container’s ID. You can verify this with 'podman ps -a'.
podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6cea3acc210d registry.access.redhat.com/ubi10/ubi:latest cat /proc/sys/ker... 15 seconds ago Exited (0) 15 seconds ago friendly_stonebraker
4.4. PID Namespace / Process ID
Let us have a look at the process table from within the container’s namespace.
podman run ubi ps -ef
Error: crun: executable file 'ps' not found in $PATH: No such file or directory: OCI runtime attempted to invoke a command that was not found
What just happened?
For the most part, containers are not meant for interactive (user) sessions. In this instance, the image that we are using (ie: ubi) does not have the traditional commandline utilities a user might expect. Common tools to configure network interfaces like 'ip' or even 'ps' simply aren’t there.
So for this exercise, we leverage something called a 'Bind-Mount' to effectively mirror a portion of the host’s filesystem into the container’s namespace. A Bind-Mount is declared using the '-v' option. In the example below, /usr/bin and /usr/lib64 from the host will be exposed and accessible to the containers namespace mounted at '/usr/bin' (ie: /usr/bin:/usr/bin).
The use of Bind-Mounts is generally suitable for debugging, but not a good practice as a design decision for enterprise container strategies. After all, creating dependencies between applications and host operating systems is what we are trying to get away from. |
podman run -v /usr/bin:/usr/bin -v /usr/lib64:/usr/lib64 ubi /bin/ps -ef
UID PID PPID C STIME TTY TIME CMD root 1 0 0 17:23 ? 00:00:00 /bin/ps -ef
Notice that all the process belonging to host itself are absent. The programs running in the container’s namespace are isolated from the rest of the host. From the container’s perspective, the process in the container is the only process running.
4.5. Network Namespace / Network & IP
Now let us run a command to report the network configuration from within the container’s network namespace. Note that the command is in /usr/sbin this time.
podman run -v /usr/sbin:/usr/sbin ubi /usr/sbin/ip addr show eth0
2: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 6a:65:6c:a9:79:62 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.88.0.5/16 brd 10.88.255.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::6865:6cff:fea9:7962/64 scope link tentative proto kernel_ll valid_lft forever preferred_lft forever
A couple more commands to understand the network setup.
Let us begin by examining the '/etc/hosts' file.
Note that we introduce the '--rm' flag to our podman command. This tells podman to automatically cleanup after the container exists |
podman run --rm ubi cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 10.88.0.1 host.containers.internal host.docker.internal 10.88.0.6 7b6141248f39 beautiful_swanson
How does the container resolve hostnames (ie: DNS)?
podman run --rm ubi cat /etc/resolv.conf
search sandbox-rh9rs-ocp4-cluster.svc.cluster.local svc.cluster.local cluster.local ocpv08.dal10.infra.demo.redhat.com nameserver 172.30.0.10
Take a look at the routing table. Let’s take a look at the routing table for the container. Again the command is in /usr/sbin
podman run -v /usr/sbin:/usr/sbin --rm ubi route -n
Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.88.0.1 0.0.0.0 UG 100 0 0 eth0 10.88.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
4.6. Mount Namespace / Filesystem
Finally, look at the filesystems in the container’s namespace. 'df' is included in the container image so no Bind-Mount is required.
podman run ubi df -h
Filesystem Size Used Avail Use% Mounted on overlay 50G 4.1G 46G 9% / tmpfs 64M 0 64M 0% /dev tmpfs 1.4G 15M 1.4G 2% /etc/hosts shm 63M 0 63M 0% /dev/shm devtmpfs 4.0M 0 4.0M 0% /proc/keys
Now let’s examine what the filesystems look like with an active Bind-Mount.
podman run -v /usr/bin:/usr/bin ubi df -h
Filesystem Size Used Avail Use% Mounted on overlay 50G 4.1G 46G 9% / tmpfs 64M 0 64M 0% /dev shm 63M 0 63M 0% /dev/shm tmpfs 1.4G 16M 1.4G 2% /etc/hosts /dev/vda3 50G 4.1G 46G 9% /usr/bin devtmpfs 4.0M 0 4.0M 0% /proc/keys
Notice above how there is now a dedicated mount point for /usr/bin. Bind-Mounts can be a very powerful tool (primarily for diagnostics) to temporarily inject tools and files that are not normally part of a container image. Remember, using Bind-Mounts as a design decision for enterprise container strategies is folly.
Let us clean up your environment before proceeding
podman kill --all
podman rm --all
5. Exercise: Container from a Containerfile
5.1. Setup
A configuration file for a podman build has already been supplied for your system. Have a look at the contents of that config.
cat /root/custom_image.OCIFile
FROM registry.access.redhat.com/ubi10/ubi:latest #NOTE: Until RHEL10 is GA, we can only use the local hosts repos to augment the # the container image with additional content. # Once GA, we can utilize the public ubi repos # #RUN dnf --disablerepo=* --enablerepo=ubi-10-baseos-rpms --enablerepo=ubi-10-appstream-rpms install -y httpd RUN dnf install -y httpd RUN dnf clean all RUN echo "The Web Server is Running" > /var/www/html/index.html EXPOSE 80 CMD ["-D", "FOREGROUND"] ENTRYPOINT ["/usr/sbin/httpd"]
Notice a few things about the configuration:
-
that our image is based on
ubi10/ubi:latest
-
the build process will install an additional package
httpd
along with its dependencies -
httpd is configured by default to run on port 80 so that is the port that is exposed (note that the EXPOSE keyword is purely informational; the port must still be published by the container runtime in order to be used)
-
the build will create a file
/var/www/html/index.html
with the contents "The Web Server is Running"
5.2. Build
Now it’s time to build the new container image.
podman build -t custom_image --file custom_image.OCIFile
Once this completes, run:
podman images
REPOSITORY TAG IMAGE ID CREATED SIZE localhost/custom_image latest d59222b092bf 3 seconds ago 257 MB registry.access.redhat.com/ubi10/ubi latest da862ffa1787 2 days ago 216 MB
5.3. Deploy
Time to deploy the image. A few things to note here:
-
we are going to name the container instance "webserver"
-
we are publishing port 80 of the container instance to port 8080 on the host
-
the container instance will run in 'detached' mode
podman run -d --name="webserver" -p 8080:80 custom_image
5.4. Inspect
To view some facts about the running container, you use 'podman inspect'.
podman inspect webserver
This reveals quite a bit of information which you can drill in to using additional format arguments. For example, let us locate the IP address for the container.
podman inspect --format '{{ .NetworkSettings.IPAddress }}' webserver
You can see the IP address that was assigned to the container.
We can apply the same filter to any value in the json output. Try a few different ones.
5.5. Validation
curl http://localhost:8080/
The Web Server is Running
Let us look at the processes running on the host.
pgrep -laf httpd
48787 /usr/sbin/httpd -D FOREGROUND 48789 /usr/sbin/httpd -D FOREGROUND 48790 /usr/sbin/httpd -D FOREGROUND 48791 /usr/sbin/httpd -D FOREGROUND 48792 /usr/sbin/httpd -D FOREGROUND
And finally let’s look at some networking info.
netstat -utlpn | grep 8080
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 48784/conmon
Now let’s introduce a command-line utility 'lsns' to check out the namespaces.
lsns
NS TYPE NPROCS PID USER COMMAND 4026531834 time 107 1 root /usr/lib/systemd/systemd nofb --switched-root --system --deserialize 18 4026531835 cgroup 102 1 root /usr/lib/systemd/systemd nofb --switched-root --system --deserialize 18 4026531836 pid 102 1 root /usr/lib/systemd/systemd nofb --switched-root --system --deserialize 18 4026531837 user 107 1 root /usr/lib/systemd/systemd nofb --switched-root --system --deserialize 18 4026531838 uts 100 1 root /usr/lib/systemd/systemd nofb --switched-root --system --deserialize 18 4026531839 ipc 102 1 root /usr/lib/systemd/systemd nofb --switched-root --system --deserialize 18 4026531840 mnt 92 1 root /usr/lib/systemd/systemd nofb --switched-root --system --deserialize 18 4026531860 mnt 1 24 root kdevtmpfs 4026531992 net 102 1 root /usr/lib/systemd/systemd nofb --switched-root --system --deserialize 18 4026532252 mnt 1 640 root /usr/lib/systemd/systemd-udevd 4026532253 uts 1 640 root /usr/lib/systemd/systemd-udevd 4026532308 mnt 2 745 root /sbin/auditd 4026532309 mnt 1 792 chrony /usr/sbin/chronyd -F 2 4026532310 mnt 1 772 root /usr/sbin/irqbalance --foreground 4026532311 mnt 1 790 root /usr/lib/systemd/systemd-logind 4026532312 uts 1 790 root /usr/lib/systemd/systemd-logind 4026532313 mnt 2 802 dbus /usr/bin/dbus-broker-launch --scope system --audit 4026532314 mnt 1 804 root /usr/sbin/NetworkManager --no-daemon 4026532316 net 5 48787 root /usr/sbin/httpd -D FOREGROUND 4026532375 mnt 5 48787 root /usr/sbin/httpd -D FOREGROUND 4026532376 uts 5 48787 root /usr/sbin/httpd -D FOREGROUND 4026532377 ipc 5 48787 root /usr/sbin/httpd -D FOREGROUND 4026532378 pid 5 48787 root /usr/sbin/httpd -D FOREGROUND 4026532379 cgroup 5 48787 root /usr/sbin/httpd -D FOREGROUND
We see that the httpd processes running are using the mnt uts ipc pid and net namespaces.
Since we explored namespaces earlier, we may as well have a look at the control-groups aligned with our process.
systemd-cgls --no-pager
... SNIP ... └─machine.slice (#7107) → trusted.invocation_id: aaf8887d115a4205a876885134f5b7c3 ├─libpod-2a60daa6c3abb5d5a7282598f2747999c0c71807752911b831a4e66743f084b8.scope … (#11452) │ → trusted.delegate: 1 │ → trusted.invocation_id: 49c9ef47d6e04e6abc3bbb20a9943692 │ └─container (#11505) │ ├─48787 /usr/sbin/httpd -D FOREGROUND │ ├─48789 /usr/sbin/httpd -D FOREGROUND │ ├─48790 /usr/sbin/httpd -D FOREGROUND │ ├─48791 /usr/sbin/httpd -D FOREGROUND │ └─48792 /usr/sbin/httpd -D FOREGROUND └─libpod-conmon-2a60daa6c3abb5d5a7282598f2747999c0c71807752911b831a4e66743f084b8.scope … (#11399) → trusted.delegate: 1 → trusted.invocation_id: e0b9d07bb47a4af7a859e492a86b85c0 └─48784 /usr/bin/conmon --api-version 1 -c 2a60daa6c3abb5d5a7282598f2747999c0c71807752911b831a4e66743f084b8 -u 2a60daa6>
What we can tell is that our container is bound by a cgroup called "machine.slice". Otherwise, nothing remarkable to discern here.