Docker Images sicher bauen mit Google Jib & Kaniko

Keine Kommentare

Während es im Betrieb von Containern in den letzten Jahren sehr viele Entwicklungen gab, ist die Erstellung von Images im Prinzip gleich geblieben. Viele benutzen nach wie vor Docker zur Erstellung von Images. In diesem Artikel wollen wir u. a. mit Google Jib und Kaniko ein paar Alternativen dazu aufzeigen, die das Erstellen von Images deutlich erleichtern können.

Am Einsatz von Docker zur Erstellung von Images ist erst einmal nichts auszusetzen. Allerdings gibt es hierbei einige Randbedingungen architektonischer und sicherheitstechnischer Art, die den Einsatz zum Beispiel in Kubernetes erschweren. Der Docker-Daemon, der dafür genutzt wird, ist eine monolithische Anwendung. Dadurch müssen diesem immer die Rechte gegeben werden, die für alle möglichen Operationen nötig sind. Des Weiteren benötigt er einen privilegierten Zugang zum Linux Kernel zur Erstellung von Images. Wer sich an dieser Stelle für die technischen Hintergründe interessiert, sei auf einen Artikel von Jessie Frazelle dazu verwiesen.

Aufgrund der etwas unhandlichen Konstruktion von Docker mit einem Daemon als ausführender Einheit stellt sich die Frage, wie man diesen nun für die Erstellung von Images aus einem Docker-Container innerhalb eines Kubernetes-Clusters benutzen kann. Eine Variante ist es, über den Volumes-Mechanismus den Socket für die Kontrolle des Daemons in den Build-Container weiterzureichen.

Dies hat natürlich Folgen für die Systemsicherheit, da man hier die Kontrolle über Teile des Host-Systems in Container abgibt. Von daher ist dies in vielen Umgebungen keine Option. Eine weitere Möglichkeit ist die Nutzung von Docker-in-Docker-Images. Dabei wird ein Docker Daemon in einem separaten Container gestartet und aus dem Build-Container heraus angesteuert. In einem Kubernetes-Cluster kann dies zum Beispiel als ein weiterer Container desselben Pods erfolgen. Hiermit hat man die Abgabe von Kontrolle über das Host-System entschärft. Allerdings benötigt auch die Docker-in-Docker-Konstruktion privilegierten Zugriff auf den Linux-Kernel.

Beim Betrieb innerhalb einer Docker-Infrastruktur wie Kubernetes muss man daher abwägen, ob das Risiko, Containern privilegierten Zugang zu gewähren, tragbar ist. Eine organisatorische Lösung kann darin bestehen, seine Infrastruktur in mehrere Cluster aufzuspalten oder für den Build der Images eigene Infrastruktur auf getrennten Hosts vorzusehen. Zum Teil stellen Cloud-Provider mit Produkten wie Google Cloud Container Builder oder Azure Container Registry Build diese Funktionalität auch als Service bereit.

Docker Layers

Die Begriffe “Container” und “Image” vermitteln stets den Eindruck eines einheitlichen Binärformats, das am Stück ausgeliefert wird. In Wirklichkeit besteht ein Image aus mehreren Schichten. Intern sind dies Tarballs mit jeweils einem Teil des Image-Dateisystems. Wenn man mit Docker eine Dockerfile baut, entspricht eine Schicht einem Befehl innerhalb der Dockerfile und der daraus resultierende Blob (Tarball) enthält jeweils die Änderungen, die im Rahmen dieses Befehls am Image vorgenommen werden. Docker gibt die angelegten Layer dabei auch aus:

Docker Layers

Neben den Schichten enthält ein Image noch Metadaten in JSON-Dateien. Diese enthalten u. a. die Architektur und das Betriebssystem, für die die jeweiligen Layer bestimmt sind. Layer werden durch diese Metadaten anhand ihrer Hashsumme referenziert und man findet diese auch in der Verzeichnisstruktur von Docker wieder.

docker inspect

ordnerstruktur docker daemon

Da es sich letztendlich nur um Tarballs und JSON-Dateien handelt, lässt sich ein großer Teil der Erstellung eines Images völlig ohne Docker-Daemon und erhöhte Privilegien im Userspace erstellen – vor allem, wenn es sich lediglich um die Installation eines JARs für eine Java-Applikation in ein vorbereitetes Image handelt. Hier kommt Jib ins Spiel.

Jib

Das neue Tool von Google besteht aus einer Bibliothek sowie Plugins für Maven und Gradle, die bestimmte, auf viele Java-Applikationen anwendbare Annahmen trifft und damit basierend auf einem vom Entwickler bestimmbaren Java-Basisimage Applikationen containerisiert und zu einer Registry hochlädt, ohne dass die Build-Umgebung dafür lokal oder remote Zugriff auf einen Docker-Daemon benötigt.
Die Plugins sind denkbar einfach in eine reguläre Java-Build zu integrieren sind, wie hier am Beispiel des Maven-Plugins gezeigt. Mit diesem Schnipsel im POM eines Projekts

<build>
  <plugins>
    <plugin>
      <groupId>com.google.cloud.tools</groupId>
      <artifactId>jib-maven-plugin</artifactId>
      <version>0.9.6</version>
      <configuration>
        <to> <!-- Name des zu erzeugenden Images -->
          <image>zielRegistry/repository/meinimage</image>
        </to>
      </configuration>
    </plugin>
  </plugins>
</build>

lässt sich mittels

mvn compile jib:build

ein Image bauen und direkt an die angegebene Zielregistry senden, ohne dass ein Docker-Daemon im Hintergrund läuft. Möchte man doch gegen einen lokalen Docker-Daemon bauen, um mit dem Image zu spielen, lässt sich dies mit

mvn compile jib:dockerBuild

bewerkstelligen. Als Basisimage wird hierbei ein Java-Image aus Googles distroless-Familie von minimalen Docker-Containern verwendet. Nebenbei spart man sich somit im Übrigen auch komplett das Schreiben einer eigenen Dockerfile. Weitere Details zur Verwendung eines eigenen Basisimages sowie zur Authentifizierung gegenüber einer Docker Registry lassen sich den Seiten der Plugins entnehmen.

Anstatt sich für das zu verpackende Java-Artefakt einfach auf eine Fat Jar zu verlassen, wie sie zum Beispiel bei Spring Boot beliebt ist, packt Jib Abhängigkeiten, Ressourcen wie HTML oder CSS und die eigentliche Applikation in getrennte Layer des Images. Dadurch können bei Änderungen an nur einer dieser Schichten die Uploads an die Docker-Registry kleiner gehalten werden.

Was sind also die Einschränkungen, die sich daraus ergeben? Nun, Jib kann zwar eine Java-App zusammenpacken und entsprechende Layer schreiben, nicht aber Befehle im Kontext des Containers ausführen. Besitzt die eigene Applikation zum Beispiel noch externe Abhängigkeiten, erwartet z. B. die Präsenz bestimmter nativer Binaries im Image, so müssen diese bereits im Basisimage vorhanden sein. Befehle im Kontext eines Containers ausführen kann Jib mangels Docker-Daemon nicht. Außerdem geht Jib im Moment fest von Applikationen in ausführbaren JAR-Dateien aus. Das Containerisieren von WARs wird im Moment nicht unterstützt, jedoch hat das Team um Jib dies bereits auf dem Schirm.

Kaniko

Wer allerdings erweiterte Ansprüche an den Buildprozess seiner Docker-Images hat oder sich nicht im Java-Universum bewegt, ist an dieser Stelle nicht auf den Docker-Daemon angewiesen. Für den Build von Docker-Images hat Google im Frühjahr Kaniko veröffentlicht. Bei der Benutzung von Kaniko kann wie gewohnt mit Dockerfiles weiter gearbeitet werden. Durch die Konzentration auf die Erstellung von Images konnte eine Vielzahl nötiger Rechte auf dem Linux-Kernel entfernt werden.

Um dies zu erreichen, haben die Entwickler die Befehle aus den Dockerfiles für ihre Umgebung nach implementiert. Nach der Ausführung jedes Befehls wird dann ein Snapshot des Dateisystems erzeugt. Durch einen Vergleich mit dem letzten Snapshot können dann die geänderten Dateien gefunden werden und daraus die entsprechenden Image Layer erzeugt werden.

Aktuell befindet sich Kaniko noch in einem relativ frühen Stadium, weswegen die einfachste Nutzung sicherlich in der Kombination mit Google Cloud und dem Google Container Builder liegt. Aktuell befinden sich in diesem Bereich viele interessante Neuerungen in Entwicklung, so dass hier in Zukunft sicherlich noch mit der einen oder anderen interessanten Idee gerechnet werden kann.

Nicolas Byl

Nicolas Byl sammelte bereits während des Studiums der Medizinischen Informatik erste Erfahrungen im Umfeld Java-basierter Webportale und entdeckte seine Leidenschaft für verteilte Systeme. Bei der codecentric AG beschäftigt er sich mit mit skalierbaren cloud-nativen Infrastrukturen für die Applikationsentwicklung und die Verarbeitung von Datenströmen.

Sebastian Jackel ist IT-Consultant mit dem Schwerpunkt DevOps. Wenn er nicht an Continuous-Delivery-Pipelines, Container-basierter Infrastruktur oder Monitoring von Services arbeitet, beschäftigt er sich gerne mit funktionaler Programmierung und Typsystemen.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.