LT-142 - Container bauen und pflegen

Best Practices

Benjamin Schmid
Manager R&I

Docker in a nutshell

  • Isolierte Prozessausführung (Sandbox)
    auf Basis von »Linux Containers (LXC)«
  • Wesentlich effizienter als VMs/Hypervisoren
  • Portables Format für Container mit Versionierung
  • Leichtgewichtige Laufzeit- und Packaging-Tools
  • Cloud-Repository für Container-Vorlagen
**Build** Once, **Configure** Once and **Run Anywhere**
ContainerContainer vs. Virtualization

(Some) Key Objectives

für die Pflege von Container Images

1. Performance & Size 1. Security & Update-Management 1. Diagnostik & Robustheit 1. Einhaltung von Best Practices 1. Sicherer Betrieb
# Image-Diät

Images bauen: Dockerfile

FROM openjdk:slim
MAINTAINER Inspector Gadget

                            # Kommando im Container ausführen
RUN apt-get update && apt-get install unzip -y &&  \
    apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

                            # JAR und Config-File in Image aufnehmen
WORKDIR /opt
ADD target/rest-microservice-1.0.0.jar app.jar
ADD src/main/resources/example.yml     app.yml

                            # announce exported port 8080 and 8081
EXPOSE 8080 8081
                            # Wichtig: Separate Volumes für Daten
VOLUME ["/srv/"]

                            # JAR ausführen beim Start des Containers
ENTRYPOINT java -jar app.jar server
							
Docker Layers

Layers & Union File System

- Filesystem aus gestapelten **Layern** (Union File System) - **Images** sind schreibgeschützte Layers & Meta - Schreibbarer Layer dazu ➱ **Container**. - Dockerfile: Jeder Schritt = **neuer Layer** ➱ `docker history` - **Versionierung:** Layer-ID = Container/Image ID ➱ `ac408c338`
### Weitere Mittel Gewicht zu verlieren… * Größe des Basis-Image, z.B. **Google Project "Distroless"** `openjdk:slim` → 426 MB `gcr.io/distroless/java:11` → 228MB * Tools wie `docker-slim` oder `jlink` * Layers zusammenfassen mit `docker build --squash …` * **Multistage Builds** für _Build_- vs. _Run_-Container
# Sicher & Aktuell
## Neue Challenges Als Entwickler / DevOps Engineer Pflege-Verantwortung für **alle** Bestandteile des Images. → Prozess-Behandlung notwendig * Regelmäßige (Security)-Releases * (Daily) Dependency Scanning z.B. via `renovatebot` oder _Gitlab SAST_ * (Daily) Container Scanning mit `trivy` oder `grype` * Sorgfältige Auswahl des Basis-Image → _Weniger ist mehr!_
## Scanning mit `trivy` ```bash $ sudo docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /tmp/triycache/:/root/.cache/ aquasec/trivy \ -q my/microservice ```
# Diagnostik & Robustheit
## Logging & Konfiguration * Logging per Default _unbuffered_ nach `STDOUT` und `STDERR` * Abwägung: Logging als JSON * Konfiguration über Umgebungsvariablen (zur Not: Volumes) bzw. _secrets_ und _configs_ ```shell $ docker run --env VAR1=val1 --env VAR2=val2 … $ docker run --env-file env.list … ```
## Datenhaltung * Keine Datenhaltung im Container → Container sind Wegwerfware & zustandslos * Datenhaltung exklusiv in Volumes oder Services * Idealfall: Filesystem des Containers ist r/o: ``` docker run … --read-only --tmpfs /run:rw myservice ```
## Health-Checks Endpunkte für Health (`/health`), Ready (`/ready`) und ggf. Metriken (`/metrics`) anbieten. Anbindung z.B. im `Dockerfile`: ```Docker HEALTHCHECK --interval=60s --timeout=3s \ --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/v1/health || exit 1 ```
# Angreifer zähmen!
## Muss es immer `root` sein? * Docker-Daemon: völlige Kontrolle über den Host * Container: per Default viele Privilegien Um den Impact einer RCE zu begrenzen, Applikationen im Container mittels `USER` als _nicht-privilegiert, nicht `sudo`-fähig_ betreiben: ``` FROM alpine # Create user and set ownership and permissions as required RUN adduser -D myuser && chown -R myuser /myapp USER myuser ENTRYPOINT ["/myapp/app"] ```
## Augen auf bei der Image-Wahl! * _verfified_ und _official_? * Noch supported & regelmäßige Security Updates? * ggf. `Dockerfile` der Quelle geprüft? * Namen & Tags sind Schall & Rauch! → Trusted Sourcen oder SHA1 als Referenz → `latest` kann Pitfall sein
## PID 1, Signale & Zombie-Prozesse * Sterben Kind-Prozesse im Container, müssen diese abgeräumt werden → _init_-Prozess * `docker run --init …` * Nur ein Prozess direkt als `ENTRYPOINT` * Init-System wie _tini_ einbinden * Dieser Prozess sollte auch Signale wie `SIGTERM` & Co. behandeln! * Ideal: Ein Prozess pro Image
## Weitere Best-Practices * `Dockerfile` linten, z.B. via https://hadolint.github.io/hadolint/ * `.dockerignore` nutzen; `COPY` over `ADD` bevorzugen * Docker Build-Cache verstehen, nutzen & steuern * **Immutable** is key * Konfiguration _vollständig_ extern halten
**Microservice Frameworks** - **Quarkus.io** - **Micronaut.io** - **Helidon** - Spring Fu - Ktor - Spark Framework - Dropwizard - Spring Boot - Vert.x
**`Dockerfile`-Alternativen:** Paketo.io, jib, s2i (source-to-image), …
# «Ja, wo laufen sie denn?»
## Docker Daemon & `docker.sock` Zugriff auf `docker.socket` ist effektiv voller root-Zugriff * möglichst Zugriff _nur_ für `127.0.0.1` und _nur_ für `root` * Wenn's sein muss: TCP-Zugriff stabil absichern! * Alternative _podman_ für Rootless-Betrieb
## Privilegien & Limits By default weitreichende Privilegien & _Capabilities_ der Container über den Host! Zusätzliches `--privileged` ist quasi völlige Sandbox-Aufgabe. → Es empfiehlt sich daher Container stets mit **minimalen Capabilities** und **Ressource-Limits** zu starten: ```bash $ docker run --cap-drop all --cap-add … \ --security-opt="no-new-privileges" \ --memory="1g" --memory-swap="1g" \ --cpus="2.5" --cpu-shares="2048" ```
## `docker-bench` kann Tipps geben Im laufenden Betrieb z.B. Tools vie Docker-Bench nutzen, die über Heuristiken Empfehlungen für das Hardening anbieten ```bash $ sudo docker run --rm --net host --pid host \ --userns host --cap-add audit_control \ -v /etc:/etc:ro \ -v /lib/systemd/system:/lib/systemd/system:ro \ -v /usr/bin/containerd:/usr/bin/containerd:ro \ -v /usr/bin/runc:/usr/bin/runc:ro \ -v /usr/lib/systemd:/usr/lib/systemd:ro \ -v /var/lib:/var/lib:ro \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ --label docker_bench_security \ docker/docker-bench-security ```
# Vielen Dank!