# Lab 05: Containerization to be done ## Inhalt - [Lab 05: Containerization](#lab-05-containerization) - [Inhalt](#inhalt) - [Übung 01: Installation von Docker](#übung-01-installation-von-docker) - [Übung 02: Erstellung von einem Image](#übung-02-erstellung-von-einem-image) - [Aufgabe 02.1 HTTP-Service mittels eines NGINX-Basisimages](#aufgabe-021-http-service-mittels-eines-nginx-basisimages) - [Aufgabe 03.2: HTTP-Service mittels eines generellen Basisimages](#aufgabe-032-http-service-mittels-eines-generellen-basisimages) - [Übung 03: Vergleich von Imagegrößen](#übung-03-vergleich-von-imagegrößen) - [Aufgabe 03.1: Unnötige Dateien löschen](#aufgabe-031-unnötige-dateien-löschen) - [Aufgabe 03.2: Image Layer einsparen](#aufgabe-032-image-layer-einsparen) - [Aufgabe 03.3: Kleinere Basis Images nutzen](#aufgabe-033-kleinere-basis-images-nutzen) - [Übung 04: Push von einem Image in eine Registry](#übung-04-push-von-einem-image-in-eine-registry) - [Übung 05: Pipeline zum Bau und Test eines Images](#übung-05-pipeline-zum-bau-und-test-eines-images) - [Quellen und Referenzen](#quellen-und-referenzen) - [Was sollten Sie mitnehmen ...](#was-sollten-sie-mitnehmen-) ## Übung 01: Installation von Docker - [Installieren](https://docs.docker.com/engine/install/) Sie gem. den verlinkten Anweisungen Docker für Ihr System: - [Mac](https://www.docker.com/products/docker-desktop) - [Windows](https://www.docker.com/products/docker-desktop) - [Linux](https://docs.docker.com/engine/install/) - Prüfen Sie in Ihrer Konsole, ob die Installation erfolgreich war: ``` docker --version ``` Sie sollten eine Ausgabe mit der Versionsnummer und build id bekommen, z.B.: ``` Docker version 19.03.8, build afacb8b ``` ## Übung 02: Erstellung von einem Image Sie werden in diesem Teil sehen, wie man Images baut. Häufig benötigt man Images für spezfische Dienste, wie bspw. Datenbanken, Webserver, usw. Hierfür bieten die Hersteller meist vorkonfigurierte Images an, die man nur noch mit einer kleinen Konfiguration (z.B. Access Credentials, Dateipfade, etc.) auf die spezifischen Bedürfnisse anpassen muss. Man kann aber auch Images auf Basis einer Standard Linux-Distribution aufsetzen. Sie werden beides am Beispiel eines kleinen Webservers sehen. Klonen Sie sich bitte hierzu als Vorbereitung dieses Repository mittels: ``` git clone https://git.mylab.th-luebeck.de/cloud-native/lab-containerization.git cd lab-containerization ``` ### Aufgabe 02.1 HTTP-Service mittels eines NGINX-Basisimages 1. Öffnen Sie nun bitte die Datei `Dockerfile.nginx` (diese ist recht übersichtlich, versuchen Sie diese zu verstehen und nachzuvollziehen) 2. Erzeugen Sie aus dieser nun das für Docker verarbeitete Dockerfile mittles `cp Dockerfile.nginx Dockerfile`. 3. Bauen Sie nun ein Container-Image mittels `docker build -t web:nginx .` (vergessen Sie nicht die Punkt am Ende, der gibt das Current Directory an und ist wichtig!). 4. Prüfen Sie mittels `docker image list web*`, ob Ihr Image gebaut wurde. Sie sollten eine Ausgabe wie folgt (o. ähnl.) erhalten. ``` REPOSITORY TAG IMAGE ID CREATED SIZE web nginx ca0547d1d208 seconds ago 133MB ``` 5. Starten Sie dieses Image nun bitte mittels `docker run -p 8080:80 web:nginx` (Sie binden dadurch Ihren lokalen Port 8080 an den Port 80 des Containers). 6. Prüfen Sie, ob Ihre Website ausgeliefert wird, in dem Sie [http://localhost:8080](http://localhost:8080) aufrufen. Sie sollten dann folgende Webpage sehen. ![screenshot](index.html.png) 7. Beenden Sie den HTTP-Service in dem Sie in Ihre Konsole CTRL-C drücken (Sie senden damit das SIGTERM Signal an den NGINX Server-Prozess und der Container wird terminiert). Das war ja einfach. ### Aufgabe 03.2: HTTP-Service mittels eines generellen Basisimages In Fällen von weit verbreiteten Diensten und Produkten wie NGINX, Apache, Redis, Memcached, CouchDB, MySQL, usw. finden sich häufig solche Basisimages der entsprechenden Projekte. Das geht meist sehr schnell und ist unkompliziert, sie verlieren aber auch Kontrolle und Konfigurationsmöglichkeiten. In Fällen spezifischerer Produkte oder selbst geschriebener Software sind Sie sogar ggf. gezwungen selber ein Image zu erstellen und die entsprechende Software darauf zu installieren. Man geht in diesen Fällen üblicherweise von einem Distributions Basis-Image aus (wie bspw. dem Ubuntu 18.04 LTS Image). Wir wollen nun denselben Service mit einem anderen Image bauen, um diesen generelleren Ansatz zu demonstrieren. 1. `cp Dockerfile.ubuntu Dockerfile` 2. Öffen Sie das `Dockerfile` in einem Editor und versuchen Sie es zu verstehen. Sie sehen es ist etwas länger, als das vorherige Image, aber Sie sollten die Wirkungsweise mittels der Kommentare nachvollziehen können. 3. Bauen Sie nun das Image zu diesem Ubuntu-basierten `Dockerfile` mittels `docker build -t web:ubuntu .`. 4. Prüfen Sie wieder mittels `docker image list web*`, ob Ihr Image gebaut wurde. Sie sollten eine Ausgabe wie folgt (o. ähnl.) erhalten. ``` REPOSITORY TAG IMAGE ID CREATED SIZE web ubuntu 8032bb56c82d seconds ago 154MB web nginx ca0547d1d208 minutes ago 133MB ``` 5. Starten Sie dieses Image nun bitte mittels `docker run -p 8080:80 web:ubuntu`. 6. Prüfen Sie, ob Ihre Website ausgeliefert wird, in dem Sie [http://localhost:8080](http://localhost:8080) aufrufen. Sie sollten wieder dieselbe Webpage sehen, wie im vorherigen Teil. ## Übung 03: Vergleich von Imagegrößen Sie haben in Übung 02 einen einfachen Webserver samt Inhalt als Docker Image erzeugt. - Einmal haben Sie auf dem von NGINX selbst bereitgestellten Image nur Ihren Inhalt (`web`-Verzeichnis) hinzugefügt. - Im zweiten Fall haben Sie auf Basis eines Ubuntu 18.04 LTS Basis Images auch den Webserver NGINX installiert, den Inhalt hinzugefügt und den Entrypoint für den Container konfiguriert. Das war aufwändiger, Sie hatten aber dadurch letztlich mehr Kontrolle über die Konfiguration. Sie sehen also, dass bereitgestellte Community-Images häufig komfortabler sind. Doch wie sieht es mit dem Größenbedarf der resultierenden Images aus? Geben Sie dazu bitte folgendes in Ihrer Shell ein. ``` docker image list web* ``` Dies sollte folgende (o. ähnl.) Ausgabe erzeugen: ``` REPOSITORY TAG IMAGE ID CREATED SIZE web ubuntu 8032bb56c82d 57 minutes ago 154MB web nginx ca0547d1d208 14 hours ago 133MB ``` Sie sehen, Ihr selbst gebautes Ubuntu Image ist etwas größer. Das ist nicht wirklich erstaunlich, denn die NGINX Macher wissen vermutlich besser als Sie und ich, wie man eine NGINX-Basisinstallation effizient konfiguriert. Doch Images lassen sich auch "shrinken". Sie sollten sich dazu ins Bewusstsein rufen, dass Container Overlay-Dateisysteme nutzen, und jede Anweisungszeile eines Dockerfiles ein Image-Layer (mit Platzbedarf) erzeugt. Man kann sich das zu nutze machen und Images auf mehrere Arten "kleiner" machen: 1. Indem man für den Betrieb unnötige Dateien nicht in den Layer mit aufnimmt. 2. Indem man unnötige Image-Layer einspart. 3. Und indem man kleinere Basis-Images nutzt. Die folgenden Aufgaben dienen dazu, Ihnen zu zeigen, was für Effekte diese drei Möglichkeiten auf die resultierenden Imagegrößen haben. ### Aufgabe 03.1: Unnötige Dateien löschen Wir gehen von unserem `web:ubuntu` Image aus. 1. `cp Dockerfile.ubuntu Dockerfile` 2. Löschen Sie nun unnötige Package Manager Dateien mittels `apt-get clean` und `rm -rf /var/lib/apt/lists/*` in dem Sie die im Dockerfile dafür vorgesehenen Zeilen einkommentieren. 3. Bauen Sie nun ein neues Package mittels `docker build -t web:ubuntu-cleaned .` 4. Lassen Sie sich nun die Größen aller drei Packages mittels `docker image list web*` anzeigen. Sie sehen, dass dadurch Ihr Image bereits etwas kleiner geworden ist, aber noch größer als das NGINX-Image ist. ### Aufgabe 03.2: Image Layer einsparen In Linux/UNIX Shells kann man, um zwei Kommandos nacheinander auszuführen, entweder dies ``` cat /file/exists echo "success" ``` oder dies ``` cat /file/exists && echo "success" ``` schreiben. "Success" wird im zweiten Fall sogar nur dann ausgegeben, wenn das erste Kommando `cat /file/exists` mit einem Exit-Code von 0 (also erfolgreich) beendet werden konnte. Das entspricht exakt der Ausführungsweise sequentiell aufeinander folgender `RUN`-Anweisungen. Mittels des `&&` Operators lassen sich also mehrere Shell-Kommandos in einer `RUN`-Anweisung eines Dockerfiles unterbringen. Anstatt mehrere `RUN`-Anweisungen (also mehrere Image-Layers) benötigt man so nur eine `RUN`-Anweisung (und erzeugt nur einen Image-Layer). Dieses __RUN-Chaining__ genannte Prinzip werden wir anwenden und sehen welchen Effekt dies auf Image-Größen haben kann. 1. `cp Dockerfile.ubuntu Dockerfile` 2. Kommentieren Sie nun alle `RUN`-Anweisungen im Dockerfile aus. 3. Kommentieren Sie nun bitte die einzelne `RUN`-Anweisung unter dem `Image Shrinking Effect`-Kommentar ein. 4. Bauen Sie nun ein neues Package mittels `docker build -t web:ubuntu-shrinked .` 5. Lassen Sie sich nun die Größen aller vier Packages mittels `docker image list web*` anzeigen. Sie sollten etwa folgende (o. ähnl.) Ausgabe erhalten: ``` REPOSITORY TAG IMAGE ID CREATED SIZE web ubuntu-shrinked f5f33804dcf2 38 minutes ago 117MB web ubuntu-cleaned 4f345b6cac34 48 minutes ago 147MB web ubuntu 8032bb56c82d 57 minutes ago 154MB web nginx ca0547d1d208 14 hours ago 133MB ``` ### Aufgabe 03.3: Kleinere Basis Images nutzen Alle Images haben bislang Größen, die deutlich die 100 MB sprengen. Das ist zwar kleiner als viele VM-Images, aber immer noch recht groß, um ein paar HTML-Seiten von wenigen KBs auszuliefern. Aus diesem Grund gibt es Linux-Distributionen (Basis-Images), die deutlich kleiner sind, als die Standard-Distributionen. Diese beinhalten nur das absolut Wesentliche (quasi die POSIX-Schnittstelle) eines Linux-Betriebssystems. Hier gibt es mehrere Distributionen, die häufig verwendet werden. Bspw.: - [Alpine Linux](https://alpinelinux.org) - [Atomic](https://www.projectatomic.io) - [Busybox](https://busybox.net) - [RancherOS](https://rancher.com/rancher-os) - [Photon](https://vmware.github.io/photon) Wir werden die Übung 02 nun mit dem Alpine Linux Basis-Image wiederholen und sehen, wie sich diese Distribution auf die Image-Größen auswirkt. 1. `cp Dockerfile.alpine Dockerfile` 2. `docker build -t web:alpline .` 3. Kommentieren Sie dann folgende Zeile im Dockerfile ein: `RUN rm -rf /var/cache/apk/*` 4. `docker build -t web:alpine-cleaned .` 5. Kommentieren Sie nun alle `RUN` Commands im Dockerfile aus. 6. Kommenteren Sie dann das `RUN`-Chaining im Dockerfile ein. 7. `docker build -t web:alpine-shrinked .` 8. Lassen Sie sich nun mittels `docker image list web*` alle Image-Größen im Überblick anzeigen. Sie sollten nun folgende (o. ähnl.) Ausgabe erhalten: ``` REPOSITORY TAG IMAGE ID CREATED SIZE web alpine-shrinked 647de3450afe 2 seconds ago ??.??MB web alpine-cleaned 9a30127f52b1 About a minute ago ??.??MB web alpine b9f1decf5a22 2 minutes ago ??.??MB web ubuntu-shrinked f5f33804dcf2 38 minutes ago 117MB web ubuntu-cleaned 4f345b6cac34 48 minutes ago 147MB web ubuntu 8032bb56c82d 57 minutes ago 154MB web nginx ca0547d1d208 14 hours ago 133MB ``` __Beantworten Sie nun die Frage, um wieviel Prozent der Image-Wechsel das resultierende Container-Image reduziert hat?__ Sie können auch gerne prüfen, dass die Images alle laufen, indem Sie diese jeweils mit den folgenden Kommandos starten ``` docker run -p 8080:80 web:alpine docker run -p 8080:80 web:alpine-cleaned docker run -p 8080:80 web:alpine-shrinked ``` und danach [http://localhost:8080](http://localhost:8080) aufrufen. ## Übung 04: Push von einem Image in eine Registry ## Übung 05: Pipeline zum Bau und Test eines Images ## Quellen und Referenzen - [Docker Playground](https://labs.play-with-docker.com/) (Online Lab) - [Docker Get Started](https://docs.docker.com/get-started/) - [Docker Tutorials and Community Trainings](https://www.docker.com/play-with-docker) - [Best Practices for Writing Dockerfiles](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) ## Was sollten Sie mitnehmen ... 1. Mittels der den meisten Distributionen beiliegenden Package Managern ist es über `RUN`-Commands möglich in Basis-Images beliebige Software komfortabel und "non-interactive" zu installieren. 1. Eigener Code, Content oder Konfigurationen können mittels `ADD`-Commands dem Container-Image hinzugefügt werden (und danach mittels `RUN`-Commands auch kompiliert und installiert werden). 2. Sie können Server-Dienste mittels TCP-Ports nach außen `EXPOSE`n. 3. Über `ENTRYPOINT` können Sie hierzu den Prozess starten, der durch den Container bereitgestellt werden soll (vermeiden Sie dabei Prozesse als Daemons zu starten). 4. Image-Größen lassen sich mittels **RUN-Chaining** und der Wahl kleiner Basis-Images signifikant reduzieren.