Container-Images: Abschied vom Dockerfile

Cloud Native Buildpacks, Paketo.io und Spring Boot Layered Jars machen das manuelle Schreiben und Pflegen von Dockerfiles in der Praxis obsolet.

Lesezeit: 7 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen 87 Beiträge
Abschied vom Dockerfile

(Bild: Shutterstock)

Von
  • Jonas Hecht
Inhaltsverzeichnis

Viele Entwicklerinnen und Entwickler erinnern sich sicherlich an das Glücksgefühl bei den ersten Gehversuchen mit Docker: Endlich ließen sich alle Abhängigkeiten eigener Anwendungen in Code gießen. Ohne Docker fiel das Dependency-Management oft schwer, da eigene Software in der Regel doch von mehr Dingen abhängt, als es auf den ersten Blick erscheint. So ist eine Java-Anwendung direkt an eine spezifische Version der Java Virtual Machine (JVM) gebunden, und die JVM wiederum basiert auf einer speziellen Betriebssystemversion und so weiter.

Mit Docker – und insbesondere dem Beschreibungsformat der Dockerfiles – lassen sich nun alle diese Abhängigkeiten festhalten, um jederzeit Klarheit darüber zu haben, ob ein Docker-Container auch so in Produktion deployt wurde, wie in der Entwicklung vorgesehen, und ob sich die Ausführungsumgebungen in Produktion sowie Entwicklungsphase auch entsprechen. Wo also liegt das Problem mit selbstgeschriebenen Dockerfiles?

Um das zu verstehen, gilt es, die Welt durch die Betriebsbrille zu betrachten. Am Anfang des Docker-Hypes haben sich die Verantwortlichen in vielen Projekten sicherlich nicht allzu viele Gedanken über den späteren Produktivbetrieb gemacht. Allerdings ist beispielsweise das Härten von Docker-Images für die Produktion keine "Nebensache" – es rächt sich häufig, diesen Punkt außer Acht gelassen zu haben. Denn hierfür sind meist umfangreiche Anpassungen an den Dockerfiles nötig. Doch im Entwicklungsalltag fällt dieser Punkt allzu oft unter den Tisch. Schon allein aus Budgetgründen wird diesem Thema meist wenig Zeit und Aufmerksamkeit im Entwicklungsprojekt eingeräumt – wenn das Thema denn überhaupt bekannt ist.

Der zweite Teil des Problems zeigt sich meist in der Nutzung der Docker-Container innerhalb von Continuous-Integration-Pipelines (CI). Auch hier lohnt ein tieferer Blick in den Aufbau der Dockerfiles. Als anschauliches Beispiel lässt sich eine Anwendung auf Basis von Spring Boot heranziehen. In der ursprünglichen Dokumentation auf spring.io zur "Dockerisierung" von Spring Boot-Anwendungen war das Dockerfile einfach aufgebaut: Als Basis diente ein OpenJDK-Docker-Image, dem die bereits gebaute ausführbare jar-Datei hinzugefügt und ein ENTRYPOINT definiert wurde. Mit ihm sollte die Spring-Boot-Anwendung genauso starten, wie das auch ohne Docker auf der Kommandozeile per java -jar-Befehl üblich ist.

Dieser Ansatz klingt nach einer guten Idee. Allerdings bleiben dabei viele Aspekte unberücksichtigt, die dem Projekt in der CI-Pipeline oder später im Betrieb auf die Füße fallen können. Um Sicherheitsrisiken zu reduzieren, sollte beispielsweise der Start der Anwendung im Docker-Container nicht mit dem root-Nutzer erfolgen. Auch ist es keine gute Idee, die als sogenanntes Fat Jar verpackte Anwendung innerhalb des Containers eins zu eins zu verwenden. Denn darin enthalten sind Teile der Anwendung, die seltener Änderungen unterworfen sind als andere. Beispielsweise wird sich der Anwendungscode deutlich häufiger ändern (bis zu mehrmals täglich).

Die genutzte Spring-Boot-Version dagegen ändert sich nur, wenn das Projekt auf eine neuere Version aktualisiert wird (also vielleicht nur ein paar Mal pro Monat). Auch die Version des Datenbank-Frameworks wird sich typischerweise nicht allzu oft ändern. Diese sich verschieden oft ändernden Teile der Anwendung sollten Entwicklerinnen und Entwickler jeweils separat behandeln und idealerweise in separaten Docker-Image-Layern unterbringen. So lassen sich in der Regel auch die Durchlaufzeiten der CI-Pipelines stark beschleunigen.

Im Rahmen dieses Beitrags lassen sich jedoch nur Teilaspekte des Problems der manuellen Pflege von Dockerfiles darstellen. In realen Projekten zwingen diese Themen verantwortliche Entwickler immer wieder dazu, Problemlösungen zu erarbeiten, die aus Sicht der Auftraggeber keinerlei Nutzen erkennen lassen. Die Probleme zu ignorieren, ist hingegen auch keine sinnvolle Option, denn das führt häufig nur zu lang laufenden CI-Pipelines und Security-Problemen, die Entwicklungsteams am Ende massiv ausbremsen oder gar in unkalkulierbare Risiken laufen lassen.

Um das manuelle Schreiben und Pflegen von Dockerfiles überflüssig zu machen, stehen mittlerweile eine Reihe von Werkzeugen parat. Dazu gehören unter anderen spotify/dockerfile-maven, Googles jib oder Red Hats source-to-image (s2i) aus dem OpenShift-Umfeld. Darüber hinaus lässt sich auch mit einem Configuration-Management-Werkzeug wie Ansible in Kombination mit automatischen Dependency-Update-Tools wie Renovate die Pflege automatisieren. Eine Standardvorgehensweise hat sich bisher aber nicht herauskristallisiert. In der Praxis werden Dockerfiles weiterhin meist manuell erstellt.

Mit der Vorstellung der Cloud Native Buildpacks (CNB) und Paketo.io sowie deren Integration in Spring Boot kündigt sich jedoch eine Zeitenwende im Containermarkt an. Ursprünglich 2011 von Heroku eingeführt, findet sich das Konzept der Buildpacks heute in vielen PaaS- oder CI-Pipeline-Werkzeugen – darunter Google App Engine, CloudFoundry, GitLab, Knative und Deis. Nach dem Zusammenschluss von Heroku und Pivotal 2018 übergaben beide das Cloud Native Buildpacks-Projekt in die "Sandbox" der Cloud Native Computing Foundation (CNCF).

Die Cloud Native Buildpacks Specification v3 lässt sich in jedem beliebigen Projekt als eine Art Interface für ein Buildpack-konformes Tooling nutzen. Praktischerweise hat das CloudFoundry Buildpack Engineering Team mit Paketo.io auch gleich eine Implementierung der Spezifikation veröffentlicht, die auf der langjährigen Erfahrung mit der Nutzung des Buildpack-Konzepts in CloudFoundry basiert.

Seit das Technical Oversight Commitee (TOC) der CNCF im November 2020 CNBs von der "Sandbox" in den Status "Incubating" befördert hat, scheint sich damit ein Standard zur Abschaffung der manuellen Pflege von Dockerfiles zu etablieren, auf den sich ein genauerer Blick lohnt.