Stor IT Back - Ihr Speicherspezialist
Container Virtualisierung V1.2 (c) Stor IT Back 2021
Ein wichtiger Vertreter der Container Virtualisierung ist Docker. Aber was sind Container eigentlich? Fangen wir mit der klassischen Virtualisierung an, es wird ein komplettes Betriebssystem auf
einem anderen Betriebssystem gestartet. Das Betriebssystem auf dem physikalischen Host ist der Hypervisor und das andere darauf laufende Betriebssystem ist die virtuelle Maschine (VM). Das verbraucht
sehr viele Ressourcen, da ja ein zweites komplettes Betriebssystem mit allen Prozessen laufen muss. Weiterhin braucht eine VM deutlich mehr Speicherplatz, neben RAM und CPU.
Das macht der Container jetzt anders, er nutzt einfach das schon laufende Betriebssystem mit. Der Container läuft als einzelne Prozesse auf dem Host. Sie können es sich als eine
Applikation vorstellen, die aber gegenüber dem Betriebssystem abgeschottet ist. In einem Container muss also immer eine Applikation laufen, wird die Applikation beendet, dann
beendet sich auch der Container.
Eine bildliche Darstellung von Server Virtualisierung (wie zum Beispiel VMware ESXi oder Microsoft Hyper-V) und Container (Docker oder Linux Container) verdeutlicht die Unterschiede:
Bei der Server Virtualisierung wird immer ein Betriebssystem mit virtueller Hardware benötigt und zwar für jede virtuelle Maschine ein eigenes. Allerdings sind hier auch die virtuellen Maschinen
komplett voneinander getrennt, sie können komplett unterschiedliche Betriebssysteme nutzen. Eine Migration von einem physikalischen Betriebssystem auf eine virtuelle Maschine ist einfach
möglich. Wie man aber auch sieht, ist der Overhead bei der Server Virtualisierung deutlich größer.
Kommen wir zu den Containern: Hier läuft auf einem Betriebssystem die Docker Engine (Beispiel bei der Docker Software) und darauf die Container. Der Container hat keine virtuelle Hardware und
auch kein eigenes Betriebssystem. Er nutzt Hardware und Betriebssystem vom Host OS mit. Aber was bedeutet dies für die Kompatibilität? Der große Vorteil von Containern soll ja die
flexible Nutzung auf unterschiedlichen Systemen sein, ohne groß etwas anpassen zu müssen. Nehmen wir einmal an, wir hätten einen Container auf einem Linux-System entwickelt, eine Webapplikation
auf einem Debian System. Nun möchten wir diesen Container auf einem Windows System, ebenfalls unter Docker starten. Dafür wird auf Windows Hyper-V benötigt, also die Server
Virtualisierung von Microsoft. Dort wird ein Linux gestartet, ab da wird alles ganz einfach ... Aber ich kann ja auch einen Windows Container
(ein Container, der Windows als Basis hat) erstellen. Die läuft dann in Microsoft Windows Container, die Linux Container in Docker for Windows.
Die Konfiguration und Anpassung von Docker-Containern mit der Command-Line ist nicht besonders komfortabel. Weiterhin bringt Docker keine Funktionen zum Management der Umgebung (Server, Cluster, Überwachung) mit.
Kubernetes ermöglicht genau dies und vieles mehr, alles von der Bereitstellung, der Verwaltung und Überwachungen einer Container Umgebung bis zur Anpassung der Hardware-Nutzung.
Ein Kubernetes-Cluster besteht aus verschiedenen Compute-Nodes, auf welchem die Container ausgeführt werden.
Die kleinste Einheit in einem Kubernetes-Cluster ist der Pods, der im Normalfall eine Applikation enthält. Diese Pods können aus einem Container, aber auch aus mehreren Containern bestehen.
Auf den Compute-Nodes laufen Agenten, die diese Pods überwachen und bei einem Ausfall diese auch wieder auf anderen Nodes starten können.
Was enthält Kubernetes an Funktionen?
- Einen Scheduler zur Verteilung der Pods bzw. Container auf den Nodes.
- Dynamische Allokation von zusätzlichen Compute-Nodes aufgrund von Laständerungen.
- Überwachung von Pods / Containern und ein möglicher Neustart bei Bedarf.
- Alle Pods und Services werden im Cluster intern registriert und verwaltet.
- Rolling Upgrades von Pods.
- Storage Verwaltung von Storage Lösungen wie AWS EBS, S3, Ceph, NFS und iSCSI.
Kubernetes ist also eine Management-Anwendung für Docker Container. Je größer und umfangreicher eine Umgebung wird, desto wichtiger werden Lösungen wie Kubernetes. Einzelne Container
lassen sich aber auch ohne Kubernetes verwalten. Der Aufwand für Kubernetes in kleinen Umgebungen wäre deutlich zu groß.
Docker basiert auf verschiedenen Linux-Technologien wie zum Beispiel Cgroups und Namespaces. Mit einer Programmierschnittstelle libcontainer, LXC (Linux Containers) zur Prozess-Isolation und
dem Overlay-Dateisystem AuFS (oder auch btrfs) stellt Docker die Container zur Verfügung. Weitere Begriffe:
Image
Das Image ist das Speicherabbild eines Containers. Es besteht aus mehreren Layern, die schreibgeschützt sind. Das Image muss nicht installiert werden,
ein einfaches Kopieren reicht für die Installation aus. Das Image kann auch in Repositories gespeichert und von dort verteilt werden.
Container
Von einem Container spricht man, wenn ein Image aktiv ausgeführt wird. Ist quasi mit einer laufenden virtuellen Maschine vergleichbar. Der Container kann gestoppt werden bzw.
wird nach Ablauf des Programmes automatisch gestoppt.
libcontainer
Dies ist die Schnittstelle zu allen Grundfunktionen eines Docker Containers
libswarm
Dies ist die Schnittstelle zur Steuerung von Docker Containern
libchan
Ermöglicht eine einfache Kommunikation zwischen Prozessen und Prozessteilen von Docker
LXC
LXC ist die Basis für Container, es stellt eine Programmbibliothek, APIs, Containervorlagen und Werkzeuge zur Kontrolle der Container. Weiterhin ermöglicht LXC die Container
unter anderen Usern als root zu starten. Eine wichtige Sicherheitsfunktion. Container können damit nicht mehr so einfach auf das Hostsystem zugreifen.
Cgroups
Die cgroups sind für die Ressourcenverwaltung der Container zuständig. Sie begrenzen den Speicher, den Durchsatz im Netzwerk und auf die Festplatten.
Layer
Ein Layer ist ein Teil eines Images, der Befehle oder Dateien enthält, die einem Image hinzugefügt wurden. Damit lassen sich verschiedene Container aus einem einzelnen Image erstellen.
Dockerfile
Das Dockerfile ist eine Textdatei, die Befehle enthält, die bei der Ausführung des Containers abgearbeitet werden. Für jeden Befehl in dem Dockerfile wird ein Layer angelegt.
Repository
Ein Repository enthält verschiedene Images, die zum Beispiel unterschiedliche Versionen oder Entwicklungsschritte sein können
Das folgende Beispiel basiert auf einem Debian Stretch System, Docker wird dort über die Paketverwaltung installiert.
Wichtiger Hinweis:
Dies soll keine
vollständige Anleitung für die Installation und den Betrieb von Docker sein, sondern nur einen Eindruck über die Funktion vermitteln.
Installation zusätzlich benötigte Software:
sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
Einzufügen der Installationsquellen für die Paketverwaltung:
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
Installation von Docker:
sudo apt-get update sudo apt-get install docker-ce
Damit ist die Docker-Software auf dem Debian System installiert und der Daemon gestartet. Für eine einfachere Verwaltung der Images und Container kann ein normaler User
der Gruppe docker hinzugefügt werden, damit wird sudo beim Aufruf der Docker-Kommandos nicht mehr benötigt.
Die Überprüfung der Installation ist sehr einfach, es wird ein Container gestartet.
docker run hello-world
admin@debian:~$ docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.
Aber woher kommt jetzt dieser Container? Docker versucht als erstes das Image auf der lokalen Platte zu finden, da wir Docker aber gerade erst installiert haben, haben wir noch
keine Images. Also sucht Docker als nächstes im Docker Repository und lädt das Image dann auf die lokale Platte herunter und startet den Container. Im Docker Repository sind
sehr viele Images vorhanden, einige zum Testen, aber viele auch mit kompletten Anwendungen und Applikationen. Diese Images können direkt genutzt werden, aber auch den eigenen
Anforderungen angepasst werden. Es ist sogar möglich, das eigene Image, zum Beispiel mit einer eigenen Anwendung, auf das Docker Repository hochzuladen. Andere können dann dieses
Image selbst nutzen.
Nehmen wir ein anderes Beispiel:
docker run -it ubuntu bash
admin@debian:~$ docker run -it ubuntu bash root@f6255860bef0:/# id uid=0(root) gid=0(root) groups=0(root) root@f6255860bef0:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var root@f6255860bef0:/# ps -efa UID PID PPID C STIME TTY TIME CMD root 1 0 0 10:52 pts/0 00:00:00 bash root 12 1 0 10:53 pts/0 00:00:00 ps -efa root@f6255860bef0:/# exit exit
Jetzt wird wieder ein Image heruntergeladen und ein Container mit Ubuntu gestartet mit der Anwendung bash. Durch die Parameter -it befinden wir uns dann direkt im Container
auf der bash Shell. Dort können wir jetzt zum Beispiel den Ubuntu Container durch weitere Software erweitern.
Ergebnis:
Auf einem installierten Linux System kann innerhalb von weniger als 5 Minuten der erste Container gestartet werden.
Container mit Docker - Installation auf Debian
Einen wichtigen Befehl von Docker haben wir ja schon kennengelernt, es ist run, er startet einen Container (und lädt das Image, wenn es nicht schon vorhanden ist,
auch gleich runter). Dem run Befehl können auch weitere Parameter mitgegeben werden. Auch da haben wir schon -it gesehen. Dies ruft den Container interaktiv auf und nutzt eine
virtuelle TTY Schnittstelle. Damit wird man direkt auf dem Container angemeldet.
Sie können aber auch ein Image einfach nur herunterladen, das geht mit docker pull nginx. Dieser Befehl lädt das Image nginx (ein einfacher Webserver) herunter. Damit ist das
Image auf der lokalen Festplatte, wird aber nicht gestartet. Durch docker run -d --name test-nginx -p 80:80 nginx wird das Image als Container mit dem Namen test-nginx gestartet.
Der Parameter -p gibt den Port auf dem Host-Betriebssystem bzw. dem Container an. Der Webserver wird also auf dem Port 80 des Hosts hören. Ein Browser auf die IP des Hosts zeigt dann
die Test-Seite von nginx an. Der Parameter -d startet den Container im Hintergrund und zeigt die ID an. Die ID wird später für das Beenden des Containers benötigt.
admin@debian:~$ docker run -d --name test-nginx -p 80:80 nginx 432689b0af02f0cada32d76c185423f97422c6652c1e6b6dade86caacf232c18 docker: Error response from daemon: driver failed programming external connectivity on endpoint test-nginx (203ecee9df85dfae49658453037c8bb6e789e2c30306c7236d1e2d1f825feae5): Error starting userland proxy: listen tcp 0.0.0.0:80: listen: address already in use. admin@debian:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Also das war jetzt nicht erfolgreich. Was ist passiert? Ganz einfach, der Port 80 wird schon auf dem Host genutzt, dort läuft ein Apache Webserver. Also wurde der Container nicht gestartet. Mit dem Kommando docker ps können wir uns die laufenden Container anzeigen lassen. Es läuft kein Container also war der Start nicht erfolgreich. Korrigieren wir unsere Fehler und nutzen den Port 8080:
admin@debian:~$ docker run -d --name test-nginx -p 8080:80 nginx docker: Error response from daemon: Conflict. The container name "/test-nginx" is already in use by container "432689b0af02f0cada32d76c185423f97422c6652c1e6b6dade86caacf232c18". You have to remove (or rename) that container to be able to reuse that name. See 'docker run --help'. admin@debian:~$ docker rm test-nginx test-nginx admin@debian:~$ docker run -d --name test-nginx -p 8080:80 nginx 5d75bc4aaa730689ad9854b1be57bf4b16c9466a244b2006639b01234de69268 admin@debian:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5d75bc4aaa73 nginx "nginx -g 'daemon of" 18 seconds ago Up 16 seconds 0.0.0.0:8080->80/tcp test-nginx
Schon wieder ein Fehler beim Aufruf des run Kommandos. Wenn der Container mit unserem Namen schon existiert (auch wenn er nicht läuft), dann müsste er entweder einen
anderen Namen bekommen oder wir löschen den ersten Versuch. Und genau das haben wir mit dem Kommando docker rm gemacht. Danach konnte der Container gestartet werden. Mit
dem Kommando docker ps kann man sich die laufenden Container anschauen. Jetzt klappt natürlich auch der Browser-Aufruf und es wird die "Welcome" Seite angezeigt.
Jetzt kommt der große Vorteil der Container: Wenn ich den Webserver jetzt auch noch auf Port 8081 und 8082 benötige, dann starte ich den Container mit neuem Namen und dem
entsprechenden Port einfach noch zwei Mal.
Werden die Container nicht mehr benötigt, so lassen sie sich einfach stoppen und bei Bedarf wieder starten.
admin@debian:~$ docker run -d --name test-nginx1 -p 8081:80 nginx 606b541324e53b5ff8f4909c5bd4b7e86c7e46f81b9f40930991116fd232d55f admin@debian:~$ docker run -d --name test-nginx2 -p 8082:80 nginx 7793025d7cd86b190ea94c8490d9416901ac8abe4cef4b4c51299696ed86637c admin@debian:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7793025d7cd8 nginx "nginx -g 'daemon of" 6 seconds ago Up 4 seconds 0.0.0.0:8082->80/tcp test-nginx2 606b541324e5 nginx "nginx -g 'daemon of" 14 seconds ago Up 12 seconds 0.0.0.0:8081->80/tcp test-nginx1 5d75bc4aaa73 nginx "nginx -g 'daemon of" 8 minutes ago Up 8 minutes 0.0.0.0:8080->80/tcp test-nginx admin@debian:~$ docker stop test-nginx test-nginx1 test-nginx2 test-nginx test-nginx1 test-nginx2 admin@debian:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Die Container können natürlich auch verändert und die Veränderungen dann wieder in neue Images geschrieben werden. Bei docker ist die Hilfefunktion gleich mit eingebaut. Ein docker --help zeigt alle Kommandos an und ein docker Kommando --help zeigt dann alle Parameter zu dem Kommando an.
Wichtig ist die Unterscheidung zwischen Image und Container in Docker. Das Image enthält alle Programme, Libraries, Tools, Config-Dateien für den zu startenden Container.
Neben dem Image, welches im Normalfalle aus verschiedenen Layern besteht, wird auch noch eine Beschreibungsdatei, das sogenannte Dockerfile benötigt. In diesem Dockerfile stehen Informationen
zu dem Aufbau der Layer der Images und auch Anweisung zum Beispiel für die Netzwerkkonfiguration.
Schauen wir uns erst mal das Images an: Wie würde man einen Container bauen, der die Applikation apache (also den Webserver) nutzt? Nehmen wir einmal an, ein fertiges Image auf dem Docker Hub
würde es nicht geben. Dann starten wir mit einem Basis-Image zum Beispiel einer CentOS Basisinstallation. Auf diesem Image sind grundlegende Programme und Libraries enthalten und die bash als Shell.
Dieses Image könnte man dann als Container starten und meldet sich mit dem -it Paramter auf der bash an. Jetzt kann man wie üblich den apache Webserver installieren, also so, als wäre
man auf einem richtigen Server. Läuft der Container jetzt wie gewünscht, dann sind die Änderungen nur im Container, nicht aber im Basis-Image.
Danach kann aus diesem Container
ein neues Image gebaut werden. Es wird dann zum Basis-Image ein neuer Layer für den Apache. Aus diesem neuen Image (Basis Image und Apache Image) können Sie jetzt wieder einen neuer Container
startet. Möchten Sie jetzt eine Webseite oder Anwendung hinzufügen (Container starten und Webseite hinein kopieren), so können Sie auch dann wieder ein neues Image
(Basis Image und Apache Image und Webseite Image) daraus erstellen. Möchten Sie eine andere Webseite in einem Container erstellen, so starten Sie einfach das Basis und Apache Image neu und
fügen die neue Webseite hinzu. Sie können also Images und Container immer wieder verwenden.
Damit sind die Images immer Read Only, bis auf den obersten Layer, der ist immer Read Write. Dieser oberste Layer (oberstes Image) gehört zur Laufzeit des Containers nicht zum eigentlichen Image,
sondern zum laufenden Container. Wenn Sie also den Container stoppen und dann löschen, so sind alle Änderungen am Image verschwunden. Sie waren im obersten Image Read Write enthalten. Damit können
Container immer wieder frische aus einem Image gestartet werden. Ein Container besteht aus als dem Image und dem beschreibbaren obersten Layer.
Eine Besonderheit bei Docker ist, dass ein Volume in mehrere Container hinein gemountet werden kann. Bei den Images ist es quasi immer so, wenn ein Layer schon im Cache ist, also in einem
Container läuft, dann wird es bei einem anderen Container mit dem gleichen Layer einfach wiederverwendet. Ein Ansatz um Speicherplatz im RAM und Filesystem zu sparen.
Die größte Akzeptanz finden Container bei Software-Entwicklern, die verschiedene Versionen ihrer Software auf unterschiedlichen Systemen entwickeln und
testen müssen. Was sind dort die konkreten Vorteile der Container? Die Entwickler können auf einem Basis-System (dem Docker-Host) verschiedene Versionen von
Betriebssystemen mit verschiedenen Versionen ihrer Software kombinieren und dann testen. Da die Container aus einzelnen Layern aufgebaut sind, lassen sie sich einfach
und schnell kombinieren und starten.
Aber nicht nur bei der Software-Entwicklung spielen Container eine immer größere Rolle, auch bei Administratoren in Unternehmen. In Containern können einfach und
schnell Anwendungen den Nutzern zur Verfügung gestellt werden, für die eigentlich keine Betriebssysteme vorhanden sind oder Voraussetzungen nicht erfüllt sind. Aber auch
ein Update der Anwendung wird deutlich einfacher. Der Container wird ja aus einem Image gestartet und das kann für verschiedene Nutzer immer das gleiche sein (siehe das Beispiel
mit dem Webserver oben). Das Update muss also nur auf dem einen "golden" Image durchgeführt werden und nach einem Neustart der Container haben alle Anwender die neue Version. Sie stellen
einen Fehler in der neuen Applikation fest, aber haben es schon an alle Nutzer verteilt? Kein Problem, das alte Image wieder herstellen und alle Container neu starten. Mit
einer klassischen Softwareverteilung ist das nicht möglich, bzw. mit deutlich mehr Aufwand.
Eine Anwendung wäre zum Beispiel der Webbrowser im Container für privates Surfen im Büro. Sie möchten Ihren Mitarbeitern das private Surfen erlauben, aber nicht direkt auf dem Firmenrechner?
Auch das könnte im Container laufen. Was sind Probleme beim privaten Surfen in der Firma? Eine Forderung, gerade von der Geschäftsführung, ist häufig die zeitliche Begrenzung der Nutzung,
also nur in den Pausen und nach der Arbeit. Aber auch ein Security Problem taucht auf, die Verbindungen sind ja alle verschlüsselt, also kann der Firmenproxy den Datenverkehr nicht
kontrollieren, er findet keine Viren und sonstige Bedrohungen. Wenn der Mitarbeiter zum privaten Surfen aber einen Container nutzt, dann laufen evtl. Programme (und auch die Viren) in dem
Container. Wenn der Download von Dateien aus dem Container nicht möglich ist und der Container regelmäßig neu gestartet wird ist dies eine recht sichere Möglichkeit.
Container mit Docker - Sicherer Browser im Container
Wie können Container einfach und schnell gesichert werden? Die Basis dafür haben Sie schon kennengelernt. Wenn ein Container läuft, dann kann er mit dem Befehl docker commit im aktuellen
Zustand auf ein neues Image kopiert werden. Damit haben wir eine schnelle und einfache Sicherung von laufenden Containern. Eines ist dabei aber zu beachten: Die Daten in dem Container
müssen konsistent sein. Was bedeutet dies? Wenn zum Beispiel eine Datenbank in dem Container läuft und die Sicherung wird bei laufender Datenbank über Transaktionen hinweg
durchgeführt, so lässt sich entweder die Datenbank aus der Sicherung nicht starten oder die Daten sind nicht konsistent. In beiden Fällen ist das Backup nicht brauchbar. Dafür gibt es
aber eine einfache Lösung: docker commit -p pausiert den Container für den commit Befehl, also für unsere Datenbank.
Damit ist eine einfache Datensicherung möglich, aber wie läuft die Recovery? Eigentlich noch einfacher. Wichtig ist natürlich vorher die Sicherung auf einen anderen Server und andere
Plattensysteme zu verschieben. Das kann ganz Docker-Konform mit einem privaten Docker Repository erledigt werden. Mit dem Befehlt docker push kann das Image der Datensicherung verschoben
werden. Aber kommen wir zur Recovery, das Image liegt auf einem Repository und von dort kann der Container mit docker load auf den gleichen oder einen anderen Docker Host geladen
und mit docker run gleich gestartet werden. Viel einfacher geht es nicht und Sie benötigen nicht einmal eine Backup-Software.
Kann der Ablauf noch verbessert werden oder wo liegen die Nachteile? Nehmen wir einmal an, wir starten viele Container aus dem gleichen Image. Somit ist der Unterschied zwischen den
Containern nur der obere Read Write Layer. Die restlichen Layer sind ja immer gleich. Also warum nicht nur den oberen Layer sichern? Dies spart Plattenplatz in der Sicherung und
Zeit beim Backup und auch beim Restore.
Was natürlich auch immer geht, aber nicht die eleganteste Lösung ist: Einfach eine Sicherung innerhalb des Containers erstellen und per
NFS, SMB oder Backup-Client außerhalb des
Containers sichern. Also so, als wäre man auf einer physikalischen Maschine.