Demystifying Containers and Container Images

Dan Čermák

CC BY 4.0

Follow Along

dcermak.github.io/container-images

who -u

Dan Čermák

Software Developer @SUSE
i3 SIG, Package maintainer
Developer Tools, Testing and Documentation, Home Automation
https://dancermak.name
dcermak
@Defolos@mastodon.social
@defolos.bsky.social

Agenda

Software Delivery: The Real Problem

  • Dev environment != Production environment
  • Deploy ⇒ 2 days of debugging 😡

Why can't we just ship the exact environment that works?

So you want a VM?

No…

Attempt #1: Bundle Everything 📦️

  1. Create dev: rsync -avz --exclude=/dev/ / /dev/
  2. Install app: chroot /dev make install
  3. Clean up: chroot /dev/ make clean
  4. Package: tar -czf app.tar.gz /dev

And deploy 🚀

  1. tar -xzf app.tar.gz -C /opt/
  2. chroot /opt/dev/ /usr/local/bin/app.bin

🎉 Success?

  • Manual process
  • Huge tar files (entire OS + app) are unwieldy
  • No process isolation
  • No resource limits
  • No network isolation

Attempt #2: Add Process Isolation (Linux Namespaces)

Linux Namespaces provide kernel-level resource isolation

  • user
  • mnt
  • pid
  • net
  • ipc
  • uts
  • cgroup
  • time
$ unshare --user --map-root-user \
      --pid --fork --mount-proc \
      /bin/bash
# whoami
root
# ps -a
    PID TTY          TIME CMD
      1 pts/8    00:00:00 bash
    104 pts/8    00:00:00 ps

Attempt #3: Add Resource Limits (cgroups)

cgroups (Control Groups) provide resource management:

  • Apply resource limits to processes
  • Measure resource usage
# cgcreate -g memory:memlimit
# cgset -r memory.max=1K memlimit
# cgexec -g memory:memlimit ls -al
Killed

The Manual Approach Doesn't Scale

We have:

  • ✔️ Filesystem isolation (chroot)
  • ✔️ Process isolation (namespaces)
  • ✔️ Resource limits (cgroups)

We need:

  • ❌ Standardized container build process
  • ❌ Easy sharing and distribution
  • ❌ Automated namespace/cgroup/FS setup
  • ❌ Simple command-line interface

Introducing: Docker

  • Standardized build processDockerfile
  • Easy sharing/distribution → Docker Registry
  • Automated setupdocker run
  • Simple interfacedocker CLI

Container Image Build

FROM registry.opensuse.org/opensuse/tumbleweed
RUN zypper -n in python3
COPY . /src/
RUN pip install .
RUN make test

and we need some CoW

UnionFS

mount -t overlay overlay \
      -o lowerdir=lower_3:lower_2:lower_1,\
         upperdir=upper,workdir=/work/ \
           merged

Container Image Build

docker build .
FROM registry.opensuse.org/opensuse/tumbleweed

COPY . /src/
WORKDIR /src/

RUN zypper -n in python3-pip; \
    pip install . ; \
    zypper -n rm --clean-deps gcc; zypper -n clean; \
    rm -rf {/target,}/var/log/{alternatives.log,lastlog,tallylog,zypper.log,zypp/history,YaST2}

EXPOSE 80
CMD ["/usr/bin/python", "-m", "my-app"]

Dockerfile

FROM registry.opensuse.org/opensuse/tumbleweed
COPY ./project/ /src/
ENV USER="geeko"
RUN zypper -n in openssh-clients; \
    ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N ""; \
    zypper -n rm --clean-deps openssh-clients; \
    zypper -n clean; rm -rf /var/log/lastlog;
VOLUME ["/src/data"]
WORKDIR /src/
EXPOSE 22
RUN useradd $USER
USER $USER
CMD ["echo hello"]
ENTRYPOINT ["/bin/bash", "-ce"]

Docker Registry

docker pull registry.opensuse.org/opensuse/leap
docker pull registry.opensuse.org/opensuse/leap:15.6
docker pull registry.opensuse.org/opensuse/leap:15.5@sha256:a5ecb8286a6a1b695acb17e63f2702be29f2a72615ec10cfb4e427e2ebc9e8ad

Volumes

docker run -v /vol/:/var/db/ -v logs:/var/log $img

Entrypoint

Networking

Best Practices

RUN zypper -n in python3-pip; \
    pip install . ; \
    zypper -n rm --clean-deps gcc; zypper -n clean; \
    rm -rf {/target,}/var/log/{alternatives.log,lastlog,tallylog,zypper.log,zypp/history,YaST2}
$ podman run -e POSTGRES_PORT=1234 \
             -e POSTGRES_USER=pg \
                 my-app
$ podman run my-app bash
#

or:

$ podman run my-app
#

Volumes are your friend:

VOLUME ["/var/db/"]
# /var/db/ is now erased after each step!

use the exec-form:

ENTRYPOINT ["/usr/bin/my-app", "-param", "value"]

Podman

Actually Docker

Podman

Rootless Containers

  • container runs as non-root or a sub-uid of your user
  • rootless networking runs in userspace

Security

  • container potentially as privileged as the user running it
  • container breakout attacks exist
  • SELinux is your friend

When to use Containers

  • Single-process applications
  • "Works on my machine" problems
  • Cloud/OS independent deployment
  • Reproducible environments

When NOT to Use Containers

  • High-performance I/O applications
  • Legacy multi-process applications
  • Desktop GUI applications

Container Orchestration

docker-compose

services:
  app:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - .:/src
    depends_on:
      db:
        condition: service_healthy
  db:
    image: registry.opensuse.org/opensuse/mariadb
    environment:
      - MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1
docker compose up

Quadlet / podman generate systemd

[Unit]
Description=TW container

[Container]
Image=registry.opensuse.org/opensuse/tumbleweed

# volume and network defined below in other configs
Volume=test.volume:/data
Network=test.network

Exec=sleep infinity

[Service]
Restart=always
TimeoutStartSec=900

[Install]
# Start by default on boot
WantedBy=multi-user.target default.target

Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-application
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: web-container
        image: nginx:latest
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: "0.5"
            memory: "512Mi"
          requests:
            cpu: "0.2"
            memory: "256Mi"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

Should I use Containers?

It depends

Ask yourself:

  1. Do I have environment consistency problems?
  2. Is my app a single process that I can isolate?
  3. Do I need to share/distribute my app environment?
  4. Can I separate data & code in deployment?
  5. Am I willing to learn new deployment patterns?

Questions?

dcermak.github.io/container-images