Docker und Kubernetes: Die absoluten Grundlagen gleichzeitig lernen
Erstmal Tacheles
Es gibt tatsächlich, junge, schlaue und hübsche Entwickler, die in ihrem bisherigen Leben erfolgreich Docker vermieden haben.
Dazu gehöre auch ich, und vielleicht auch du – aber das soll sich ändern.
Warum sollst du dich also mit Docker beschäftigen, wenn doch bisher auch alles irgendwie ohne lief? Und warum spricht die ganze Welt nicht nur davon, sondern auch von Kubernetes?
All das kauen wir jetzt mal durch. Und wer weiß, vielleicht änderst du ja sogar deinen Entwickleralltag und stehst mal früh auf!
Docker: Basics
Vielleicht geht es dir wie mir: Zum Entwickeln hast du dein Windows-System, oder eine virtuelle (oder echte) Linux-Maschine und die Reste von deinem ersten XAMPP, Version 1.8, liegen noch auf deiner Platte rum. Das führt dann dazu, dass dein Visual Studio Code denkt, du hättest noch PHP 5.6.
Aber irgendwie läuft doch alles… Bis es das nicht mehr tut, weil ein Projekt PHP 5.6 benötigt (ja, solche Projekte gibt es noch), und das andere PHP 7.4.
Jetzt hast du zwei Möglichkeiten:
- Ein paralleles System aufsetzen
- Dich erschießen, weil du was mit PHP 5.6 machen musst
Nr. 2 beendet den Entwicklungszyklus und behindert die Agilität, deshalb ärgerst du dich und wählst Nr. 1. Oder Stopp! Du nimmst Docker!
Mit Docker lädst du dir vorgefertigte Images runter, die du als eigenen Container auf deinem Rechner laufen lässt. Dabei ist es egal, ob du Windows, Linux oder einen Abakus benutzt. Docker läuft überall.
Mit Docker sagst du also: “Lade dir das Image von PHP 5.6 mit Apache runter, starte es und lass es auf Port 7382 laufen! Außerdem hätte ich gerne PHP 7.4 auf Port 2341.“
Docker sagt dann: “Jo klar, voll gerne, mach ich.”
Und in (ungelogen) wenigen Sekunden ist dein System einsatzbereit. Wir lernen also: Ein Image ist eine Basis-Datei, aus der der Container entsteht. Der Container selbst ist eigentlich auch nur eine Datei – und ein lebendes Abbild des Images.
Die Vorteile von Docker
Das Image ist eine Grundlage für alle Programme, die du laufen lassen möchtest – z.B. Apache, MySQL oder MongoDB. Viele offizielle Images findest du auf dem Docker-Hub.
Docker startet diese dann in einem eigenen Container, d.h. in einer Kopie des Images. Üblicherweise hast du einen eigenen Container für deine Dienste, also einen für Apache, für PHP und für MySQL.
Solltest du es nicht anders konfigurieren, werden alle Daten (z.B. die Datenbankdateien und Logs) auch im Container gespeichert, denn dieser ist nichts anderes als eine eigene virtuelle Linux-Maschine, in die du dich auch einloggen kannst. Löschst du den Container, gehen auch deine Daten verloren – aber dafür gibt es die Volumes; dazu in einem anderen Artikel mehr.
Es ist also möglich, deinen kompletten Software-Stack in wenigen Dateien zu definieren, so dass ein zweiter Entwickler nur einen Befehl eingeben muss, um exakt die gleiche Umgebung wie du zu bekommen.
Auch wenn du mehrere Instanzen deiner Software brauchst: Mach doch einfach selbst ein Image und starte es auf anderen Servern!
Die Nachteile von Docker
Wenn man mal ehrlich ist, eignet sich Docker doch alleine nicht so wirklich für deine Live-Server. Zum Entwickeln ist es wunderbar, einen Webserver mal schnell hochzufahren, aber im echten Betrieb? Da möchtest du vielleicht skalieren oder besondere Einstellungen vornehmen.
Denn eins ist klar: Auch, wenn du einen MySQL-Server mit einem Docker-Befehl starten kannst, heißt das nicht, dass du dich nicht mit MySQL und dem darunter liegenden Betriebssystem auseinandersetzen musst. Denn tatsächlich ist ein MySQL-Server auch mit ein paar Befehlen mehr ohne Docker installiert.
Dennoch: Der Charme, die ganze Umgebung mit Skripten und Config-Dateien vorzukonfigurieren und einfach starten zu können, hat was!
Kubernetes: Basics
Ganz simpel gesagt: Mit Kubernetes steuerst du deine Docker-Container auf vielen Servern, um eine hohe Ausfallsicherheit zu gewährleisten.
Du sagst Kubernetes: “Ich habe hier ein Apache-Docker-Image. Kannst du bitte dafür sorgen, dass davon immer drei Stück laufen?”
Kubernetes sagt dann: “Sehr geehrter Herr, es würde mir eine Freude sein, Ihnen diesen Wunsch zu erfüllen!” (Kubernetes ist höflicher)
Aber es kann noch viel mehr: Es betreut nicht nur einen Server, sondern eine ganze Armada davon. Du hast 10 Server und willst auf jedem davon einen Docker-Container laufen lassen? Kein Problem. Du bekommst sogar automatisch dein eigenes IP-Netz und einen übergreifenden Speicher.
Und das beste: Auch Kubernetes ist mit Skripten konfigurierbar. Du kannst also mal eben dutzende eigene gigantische Server-Applikationen mit einem Befehl steuern oder deine App doppelt hochskalieren.
Ans Eingemachte
OK, das reicht erstmal als Grundlage. Wir machen jetzt folgendes:
- Docker installieren
- Kubernetes installieren
- Einen NGINX Webserver mit Docker aufsetzen
- Einen NGINX Webserver mit Kubernetes aufsetzen
Docker installieren
Docker gibt es für Windows, Mac und Linux. Für Ubuntu 20 sind folgende Befehle nötig:
sudo apt install apt-transport-https ca-certificates curl software-properties-common -y sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable" sudo apt update sudo apt install docker-ce docker-compose -y
Kubernetes installieren
Es gibt viele verschiedene “Kubernetes-Apps”, z.B. microk8s oder die “offizelle Version“. Ich empfehle das schlanke K3s. Das wird so installiert:
curl -sfL https://get.k3s.io | sh -s - --docker # Service überprüfen sudo systemctl status k3s # Service starten sudo systemctl start k3s
Alle Versionen liefern das Programm kubectl
mit, mit dem du den Cluster steuerst.
Achtung: Genaugenommen benötigst du für dieses Tutorial Docker, um mit Kubernetes zu arbeiten. Du hast aber später die Möglichkeit, auch einen anderen Container-Dienst zu nutzen.
Verzeichnisse anlegen
Als nächstes legst du in einem beliebigen Verzeichnis folgende Verzeichnisstruktur an:
docker
: Hier landet die Docker-Version des Projektskubernetes
: Hier landet die Kubernetes-Version des Projekts
NGINX mit Docker starten
Du kannst Docker direkt über die Shell steuern. Theoretisch würde folgender Befehl reichen:
docker run -p 7080:80 nginx
Damit kannst du in deinem Browser http://<Server IP>:7080 aufrufen und die Willkommensnachricht von NGINX genießen.
Aber! Wir nutzen die Config-Datei docker-compose.yml
, um alles zu konfigurieren. Docker-Compose hilft dir, mehrere Container gleichzeitig zu starten – genau wie Kubernetes, nur eben auf nur einem Server, anstatt in einem Cluster.
Also lege die Datei docker/docker-compose.yml
mit folgendem Inhalt an:
# Wir benutzen folgende Docker-Compose Version version: '3' # Hier werden die Container aufgelistet services: # Der NGINX-Container nginx: # Das grundlegende Image, das von Docker-Hub geladen wird image: nginx # Der Port 80 des Containers soll auf den Port 7080 unseres Servers # gemappt werden ports: - "7080:80"
Wechsel nun in das Verzeichnis /docker
und starte den Dienst:
sudo docker-compose up
Das Ergebnis sollte so aussehen:
Folgendes ist passiert: Docker hat das aktuellste Image von NGINX runtergeladen und den Container docker_nginx_1
gestartet.
Der Webserver sollte jetzt unter http://<Server IP>:7080
erreichbar sein.
In diesem Fall läuft Docker nicht als Daemon, d.h. du kannst den Container mit Strg+C beenden.
Images und Container prüfen
Hier ein paar wichtige Befehle, um dir alle Images und Container anzeigen zu lassen. Außerdem eine Möglichkeit alle zu entfernen – was besonders beim Lernen hilfreich sein kann. Es gibt übrigens viele Aliases, die genau das gleiche tun.
# Alle Images anzeigen docker image ls # Alle Container anzeigen (auch inaktive) docker container ls -a # Alle Container löschen docker rm -vf $(docker ps -a -q) # Alle Images löschen docker rmi -f $(docker images -a -q)
NGINX mit Kubernetes starten
Um es nochmal zu betonen: Kubernetes ist kein Ersatz für Docker, sondern benötigt es in unserem Fall sogar, um das gleiche zu tun, was wir oben getan haben. Tatsächlich sind die Container, die Kubernetes erstellt, auch mit den Docker-Befehlen sichtbar. Kubernetes ist sozusagen Docker Compose auf Steroiden.
Allerdings muss man zunächst verstehen, wie Kubernetes die Docker-Container startet, bzw. worauf wir achten müssen.
Als erstes erstellen wir ein Deployment. Damit sagen wir Kubernetes: “Hey, erstelle bitte einen Container aus dem Image NGINX”. Folgendes muss also in die Datei kuberenetes/nginx-deployment.yml
:
# Wir nutzen die erste Version apiVersion: apps/v1 # Diese Datei beschreibt ein Deploment kind: Deployment # Name und Label dieses Deployments metadata: name: nginx labels: app: nginx # Weitere Spezifikationen spec: # Anzahl der Container replicas: 1 # Das Deployment soll das Template weiter unten finden selector: matchLabels: app: nginx # Infos zum Image template: # Alle Container haben folgende Labels metadata: labels: app: nginx spec: containers: # Image und (native) Ports der Container - name: nginx image: nginx ports: - containerPort: 80
OK, das ist etwas umfangreicher. Bevor wir das Deployment starten, ein paar kurze Hinweise:
- Es gibt kein Portmapping, d.h. wir geben hier nur die Ports an, die das Image freigibt (bei MySQL wäre das z.B 3306).
- Es gibt eine Menge von Labels. Labels werden benutzt, um Container, Images, Deployments und alles andere, was Kubernetes hat, zu kennzeichnen. Dabei kann man den Key (“app”) und den Value (“nginx”) selbst wählen.
- Das, was wir hier definieren ist ein sogenannter Pod. Ein Pod kann mehrere Container beinhalten (unter
containers
). - Weitere Details findest du in der offizielen Doku.
Analog zu docker-compose
starten wir doch einfach mal das Deployment mit:
sudo kubectl apply -f nginx-deployment.yaml
Die Antwort ist folgende:
deployment.apps/nginx configured
OK… Was heißt das jetzt?
Docker hat einen Container gestartet, der aber noch nicht von außen erreichbar ist (wir haben ja noch nicht gesagt, auf welchem Port er hören soll). Schauen wir mal rein:
sudo kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE nginx 1/1 1 1 1min
Das heißt, es läuft 1 von 1 Container. Das gleiche sagt auch Docker:
Gut, es laufen noch mehr Container – aber die beachten wir mal nicht.
Wie können wir denn jetzt auf den NGINX Container von außen zugreifen?
Die Antwort ist ein Service. Wenn du versuchst, jetzt über Port 80 auf NGINX zuzugreifen, führt das ins Leere. Kubernetes nutzt nämlich selbst die Ports 80 und 443, um mit seinen Pods zu kommunizieren. Das ist ein ziemlich wichtiger Punkt, denn wie soll ein normaler User auf den Webserver zugreifen, wenn er mit Kubernetes läuft? Die Antwort darauf ist ein LoadBalancer, aber dazu später.
Kümmern wir uns erstmal darum, den NGINX-Container auf Port 30007 erreichen zu können. Ein Service, genauer gesagt vom Typ NodePort, ist dafür zuständig.
Dazu legst du die Datei kubernetes/nginx-service.yml
an:
apiVersion: v1 # Diese Datei beschreibt einen Service kind: Service # Name dieses Services metadata: name: ngnix-service # Infos zum Service spec: # Dieser Service betrifft alle Container mit # folgendem Label selector: app: nginx # Typ des Services (es gibt noch andere) type: NodePort # Die Ports, die wir mappen wollen ports: - name: http protocol: TCP # Port des Containers port: 80 # Port der von außen erreichbar sein soll nodePort: 30007
Den Service richten wir dann genauso ein wie das Deployment:
sudo kubectl apply -f nginx-service.yaml
Auch analog zum Deployment listen wir alle Services auf:
sudo kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 6d19h ngnix-service NodePort 10.43.122.70 <none> 80:30007/TCP 6d19h
Da isser! Hier sieht man auch gut, dass Kubernetes ein eigenes kleines Subnetz aufgebaut hat. Jeder Pod, Service und Co. hat seine eigene IP.
Stunde der Wahrheit: Rufe http://<Server IP>:30007
auf. NGINX sollte die bekannte Meldung ausgeben.
Rekapitulation
Wir haben jetzt das gleiche mit Kubernetes gemacht, was wir auch mit Docker Compose erreicht haben. Der Nachteil: Momentan können wir den Webserver nicht auf den Ports 80 und 443 erreichen, was für Live-Apps ja grundlegend wichtig ist. Mit Docker, bzw. Docker Compose kein Problem, denn diese Ports sind ja frei und nicht von Kubernetes belegt.
Das ist aber kein wirkliches Problem – denn Kubernetes ist ja dazu gedacht, in einem Server-Cluster zu laufen, der einiges an Replikation liefert. Wir brauchen nur einen LoadBalancer, der die Requests von außen auf Port 80/443 empfängt und dann intern an den NGNIX-Container weiterleitet. Auch das kann Kubernetes! Es gibt sogar Cloud-Hoster (wie AWS oder Azure), die die Hardware dazu bereitstellen – kann man aber auch selbst machen.
6. Februar 2021
Alle meine Artikel entstehen mit bestem Wissen und Gewissen, sind aber nicht perfekt und sollten immer nur als Ausgangspunkt für deine eigenen Recherchen bilden.
Sollte dir etwas Fehlerhaftes auffallen, freue ich mich über deine Nachricht!