Nach vielen vielen gebauten und gestarteten Containern m├Âchte ich ein paar praktische Tipps zum Image-Bau geben, die einem das Leben erleichtern. Entstehen soll ein ganzheitlicher Blick von der lokalen Entwicklungsumgebung, ├╝ber das Bauen und Testen in der Continuous Delivery Pipeline, bis zum Betrieb in Produktion.

Beware of the Layers

Eine der ersten Regeln, die man sich beim Bau von Images vor Augen f├╝hren sollte, ist das Kombinieren von Befehlen, um keine unn├Âtigen Layer zu erzeugen. Dateien, die in einem Layer erzeugt wurden, k├Ânnen in einem anderen Layer nicht mehr entfernt werden, um Speicherplatz zu reduzieren.

# Do
RUN apt-get update && \
    apt-get install -y \
    package-bar \
    && \
    rm -rf /var/lib/apt/lists/*

# Don't
RUN apt-get update
RUN apt-get install package-bar
RUN rm -rf /var/lib/apt/lists/*

Zudem spielen Layer eine wichtige Rolle, wenn es um die Build-Geschwindigkeit geht, da hier der Build Cache f├╝r Beschleunigung sorgt.

Ein Dockerfile dem Befehl ÔÇ×docker container commitÔÇť vorziehen

Man muss kein Dockerfile als Basis f├╝r seine Applikation nutzen. Stattdessen kann man auch einfach einen Basis-Container mit der Linux-Distribution meiner Wahl hochfahren, um ÔÇ×from scratchÔÇť zu starten. Anschlie├čend kann man manuell seine Applikation und Abh├Ąngigkeiten ├╝ber die Shell hinzuf├╝gen. Mit docker container commit l├Ąsst sich dann der Container wieder als Image verpacken. F├╝r ein erstes Rumprobieren halte ich diese Vorgehensweise f├╝r n├╝tzlich und zielf├╝hrend.

Im Gegensatz dazu, steht die M├Âglichkeit ein Image basierend auf einem Dockerfile zu bauen. In dieser textlichen Form ist es versionierbar, ist dadurch auch f├╝r die Kollegen nachvollziehbar und sorgt f├╝r eine bessere Automation und Reproduzierbarkeit, die im Umfeld von CI/CD sehr wichtig ist. Wer seit L├Ąngerem mit Continuous Delivery Pipelines zu tun hat, wei├č aber, dass externe Abh├Ąngigkeiten die Reproduzierbarkeit torpedieren k├Ânnen. Wenn der Server, von dem ich eine Abh├Ąngigkeit wie ein Tar-Ball oder dergleichen beziehe, offline ist, habe ich trotzdem ein Problem. Das sollte man im Hinterkopf behalten.

It runs on my laptop != It runs in production ÔÜá´ŞĆ

Wir alle kennen das: Das gerade frisch entwickelte neue Feature funktioniert tadellos auf der eigenen Maschine, aber nicht in Produktion. Die Docker-Container sollen dies aufgrund ihrer Parit├Ąt (Parity) jedoch minimieren. Ich schreibe bewusst minimieren, denn wir verfolgen mit den verschiedenen Umgebungen verschiedene Interessen. Lokal wollen wir schnell und komfortabel entwickeln. In der Test-Umgebung soll m├Âglichst automatisiert eine ├ťberpr├╝fung der Qualit├Ąt statttfinden. Auf dem Produktivsystem w├╝nschen wir uns, dass die Applikation m├Âglichst 24/7 erreichbar ist. Das fasst die Ziele nat├╝rlich nur ganz grob zusammen, aber ein grunds├Ątzlicher Unterschied, der dadurch entsteht, ist die Verwendung von Volumes in der Produktivumgebung, die nicht die Applikation, sondern nur die Daten persistieren sollen. Lokal arbeitet man in der Regel so, dass die komplette Applikation aus dem Git-Repository ausgecheckt wird und auf der Host-Maschine liegt. Host-Mounts, die unsere ├änderung in die gestarteten Container bringen, werden dadurch notwendig. Bereits das ist ein Unterschied, der nicht zu vernachl├Ąssigen ist und es geht weiter mit dem Orchestrator. Lokal arbeitet man unter Umst├Ąnden mit Docker-Compose und das verteilte Live-System wird mit Docker Swarm gesteurt. Eine Anwendungen zentral auf einer Entwicklungsmaschine zu betreiben unterscheidet sich vom Betrieb in verteilten Systemen.

In meinen Augen sollte man daher daf├╝r sorgen, dass zumindest die Images in allen Umgebungen identisch sind. Da ein Dockerfile die Basis f├╝r ein Image darstellt, ist es auch der Ausgangspunkt, um eine m├Âglichst gro├če Parit├Ąt zu erhalten. Ein Image muss daher ├╝ber die notwendige Intelligenz verf├╝gen, lokal in Debugging-Szenarien zur Seite zu stehen, was in Production mittels Environment Variable oder reingerichter Konfigurationsdatei deaktiviert wird. Das Image stellt also den gemeinsamen Nenner dar und kann mittels Host-Mounts, Ports, Env-Variablen, Secrets und Configs an die jeweilige Umgebung angepasst werden. Das Thema Sicherheit steckt hier nat├╝rlich die Grenzen ab. Am Ende wollen wir nicht den lokalen Komfort in der Entwicklung ├╝ber die Sicherheit im Produktivsystem stellen.

DevOps ­čĹ»ÔÇŹÔÖé´ŞĆ

Ich bin ein Freund des Ansatzes ÔÇ×You build it, you run it!ÔÇť, der im Zusammenhang mit dem DevOps-Ansatz immer wieder Erw├Ąhnung findet. Als Entwickler bekommt man mit Docker ein Werkzeug an die Hand, wodurch man sich zwangsl├Ąufig mehr mit der Laufzeitumgebung Gedanken machen muss, die man zuvor in Ops H├Ąnden gesehen hat. Dadurch stellt sich ein breiteres Wissen selbst bei den Technologien ein, die man bereits jahrelang genutzt hat. Als PHP-Entwickler lernt man, wie der FastCGI Process Manager (FPM) arbeitet, wo er seine Log-Dateien und sein Process File ablegt. In meinen Augen bekommt man einen ganzheitlicheren Blick, den man f├╝r das Betreiben in der Live-Umgebung auch wirklich ben├Âtigt. Menschen, die man klassischer Weise Operations zuordnen w├╝rde, fungieren mehr als Makro-Architekten und stecken die Grenzen f├╝r Entwickler ab, welche mit Containern sehr viel mehr Freiheiten bekommen.

Offizielle Images

Nutzt die offiziellen Images! Sie stellen ein absolut solides Fundament f├╝r eure Applikation dar und werden von Leuten betreut, die sich damit auskennen. Dinge selbst von der Pieke auf zu erstellen, sorgt f├╝r einen geh├Ârigen Aufwand, den man nicht untersch├Ątzen sollte. Schnell hat man sich ein Image aus dem Docker Hub geladen und zum Laufen gebracht, aber wer garantiert einem, dass dieses Image sicher ist oder in einem halben Jahr noch gepflegt wird? Mit den offiziellen Images haben die Verantwortlichen bereits ihren langen Atem bei der Pflege der Software-Pakete bewiesen.

Alpine Linux

Es lohnt sich mit dem kleinen Alpine Linux und dessen Paket Manager auseinander zu setzen. Meine ersten Docker-Gehversuche habe ich mit Debian gestartet, weil mir apt vertraut war. Nachdem ich mich mit Alpine auseinander gesetzt habe, kann ich euch nur w├Ąrmstens dazu raten, denn die kleinen Images machen beim Bewegen und Bauen extrem viel Spa├č. Die CI-/CD-Pipeline wird enorm beschleunigt und auch die lokale Umgebung eines Entwicklers ist in Nullkommanichts aufgesetzt. Spa├č beim Entwickeln ist in meinen Augen ein untersch├Ątztes Gut, was es als Verantwortlicher zu jeder Zeit zu wahren gilt. Das einzig Negative, was ich ├╝ber Alpine Linux berichten kann, ist der verwendete C Compiler, der in seltenen F├Ąllen f├╝r Probleme sorgt und das ein oder andere Derivat an CLI-Tools, was sich vielleicht doch etwas vom Original unterscheidet. Ein Blick wert ist es aber auf jeden Fall und wenn es nur der schmale Footprint ist. Ja auch als Entwickler kann man seinen Beitrag zum Umweltschutz leisten. ­čî│

Dockerfile Best Practices

Docker selbst hat in seiner Dokumentation Best Practices zum Schreiben von Dockerfiles definiert, die jeder mal gelesen haben sollte. Zur ├ťberpr├╝fung des Regelwerks wurden ein paar Linter-Projekte ins Leben gerufen. Eine Empfehlung daf├╝r kann ich bisher jedoch noch nicht aussprechen.

.dockerignore

Die Datei .dockerignore bekommt f├╝r mein Empfinden zu wenig Aufmerksamkeit, obwohl sie den Build-Prozess ├Ąhnlich stark beeinflussen kann, wie das schlanke Alpine Linux. Dazu muss man sich jedoch vor Augen f├╝hren, wie aus dem Dockerfile ein Image gebaut wird. Wichtig bei diesem Vorgang ist der sogenannte Kontext. Das ist ein Ort, aus dem sich Docker beim Bau von Images bedient, um Anweisungen wie COPY auszuf├╝hren. Dateien werden also nicht direkt vom lokalen Dateisystem in das Docker-Image kopiert, sondern gehen einen Umweg ├╝ber den Kontext. Der Kontext entspricht, wenn nicht anders definiert, dem Ordner in dem ich docker image build .ausf├╝hre. Alle Dateien und Ordner innerhalb dieses Verzeichnisses werden also vor dem eigentlichen Build-Prozess zum Docker Daemon ├╝bertragen, was bei gro├čen Projekten einige Zeit in Anspruch nimmt, nur um anschlie├čend Dinge aus diesem Kontext ins finale Image zu kopieren. Hier kommt die Datei .dockerignore ins Spiel. Damit kann man, ├Ąhnliche Gits ÔÇ×.gitignoreÔÇť, Dateien und Ordner ausnehmen, damit diese nicht zum Docker Daemon ├╝bertragen werden. Klassische Eintr├Ąge dieser Datei umfassen den Ordner .git, die Dokumentation des Projektes, Pipeline as Code, Infrastructure as Code, Secrets, die im finalen Container nichts zu suchen haben. Mit dieser Datei auseinander gesetzt, kann man die CI/CD Pipeline sp├╝rbar beschleunigen.

Build and Runtime Dependencies

Mach dir Gedanken dar├╝ber, welche Abh├Ąngigkeiten es zur Laufzeit und welche es zur Build Time gibt. Ein Beispiel w├Ąre make zum Kompilieren, welches zur Laufzeit nicht ben├Âtigt wird und entsprechend in einem Layer installiert, damit kompiliert und anschlie├čend wieder entfernt werden kann. Gleichzeitig sollte man einen Entwickler nicht beschneiden. Wenn er makeben├Âtigt, muss das Tool nat├╝rlich auch im Container zur Verf├╝gung stehen.

Labels

Das absolute Mindestma├č an Metadaten, das ein Image-Bauer zur Verf├╝gung stellen sollte, sind sein Name und die Kontaktdaten. So sieht man schnell, wer diese Datei verzapft hat und kann sich bei Fragen an ihn wenden. Wer sich hier mehr w├╝nscht, kann sich mit dem Label Schema besch├Ąftigen, welches daf├╝r eine Spezifikation liefert (veraltet). Alternativ kann man bei Project Atomic oder der Open Container Initiative in entsprechende Entw├╝rfe einarbeiten.

FROM golang:1.9.2-alpine3.7
LABEL maintainer "Patrick Baber <patrick.baber@ueber.io>"

Multi-Stage-Builds

Diesem Thema habe ich einen eigenen Artikel gewidmet, weil es sich um eine m├Ąchtige M├Âglichkeit handelt, ein Image basierend auf mehreren Base-Images zu erstellen. Assets bauen, mit Node, die man dann in ein nginx-Image kopiert. Solche Dinge werden damit m├Âglich.

Abschlie├čendes Gebrabbel

Es lohnt sich in schlanke und robuste Images zu investieren. Die Lernkurve ist meiner Meinung nach nicht besonders steil, da man sich den verschiedenen Themen peu ├á peu annehmen kann, ohne gezwungen zu sein, gleich alles zu erledigen. Im Fokus sollte die Verwendbarkeit des Images in den verschiedenen Umgebungen stehen, was zwar ein wenig zus├Ątzliche Logik erfordert, sich aber zur Freude von Dev und Ops lohnen wird.