Ehrlich gesagt bin ich mir nicht hundertprozentig sicher. Aber ich denke, es ist interessant, in die Interna einzutauchen und zu sehen, was in Kubernetes unter seinen vielen Abstraktionsebenen wirklich vor sich geht. Werfen wir also zum Spaß einen Blick darauf, wie ein minimaler „Kubernetes-Cluster“ tatsächlich aussieht. (Das wird viel einfacher sein als Kubernetes auf die harte Tour.)
Ich gehe davon aus, dass Sie über Grundkenntnisse in Kubernetes, Linux und Containern verfügen. Alles, worüber wir hier sprechen, dient nur Forschungs-/Lernzwecken, bringen Sie nichts davon in die Produktion!
Beschreibung
Kubernetes enthält viele Komponenten. Entsprechend Wikipedia, die Architektur sieht so aus:
Hier werden mindestens acht Komponenten gezeigt, die meisten davon ignorieren wir jedoch. Ich möchte darauf hinweisen, dass das Minimum, was man vernünftigerweise Kubernetes nennen kann, aus drei Hauptkomponenten besteht:
Kubelet
kube-apiserver (abhängig von etcd – seiner Datenbank)
Containerlaufzeit (in diesem Fall Docker)
Mal sehen, was die Dokumentation zu jedem von ihnen sagt (Russisch., Englisch.). Anfangs Kubelet:
Ein Agent, der auf jedem Knoten im Cluster ausgeführt wird. Es stellt sicher, dass Container im Pod ausgeführt werden.
Klingt einfach genug. Wie wäre es mit Containerlaufzeiten (Containerlaufzeit)?
Eine Containerlaufzeit ist ein Programm zum Ausführen von Containern.
Sehr informativ. Wenn Sie jedoch mit Docker vertraut sind, sollten Sie eine allgemeine Vorstellung davon haben, was es tut. (Die Details der Aufgabentrennung zwischen der Container-Laufzeit und dem Kubelet sind eigentlich recht subtil und ich werde hier nicht näher darauf eingehen.)
И API-Server?
API Server ist die Kubernetes-Systemsteuerungskomponente, die die Kubernetes-API verfügbar macht. Der API-Server ist die Clientseite des Kubernetes-Kontrollfelds
Jeder, der jemals etwas mit Kubernetes gemacht hat, musste entweder direkt oder über kubectl mit der API interagieren. Dies ist das Herzstück dessen, was Kubernetes zu Kubernetes macht – das Gehirn, das die Berge von YAML, die wir alle kennen und lieben (?), in eine funktionierende Infrastruktur verwandelt. Es scheint offensichtlich, dass die API in unserer Minimalkonfiguration vorhanden sein sollte.
Voraussetzungen
Virtuelle oder physische Linux-Maschine mit Root-Zugriff (ich verwende Ubuntu 18.04 auf einer virtuellen Maschine).
Und das ist alles!
Langweilige Installation
Wir müssen Docker auf der Maschine installieren, die wir verwenden werden. (Ich werde nicht im Detail auf die Funktionsweise von Docker und Containern eingehen; wenn Sie interessiert sind, gibt es eine wunderbare Artikel). Lass es uns einfach mit installieren apt:
Danach müssen wir die Kubernetes-Binärdateien abrufen. Tatsächlich brauchen wir für den ersten Start unseres „Clusters“ nur kubelet, da wir andere Serverkomponenten ausführen können, die wir verwenden können kubelet. Um mit unserem Cluster zu interagieren, nachdem er ausgeführt wurde, verwenden wir auch kubectl.
kubelet muss als Root ausgeführt werden. Ganz logisch, da er den gesamten Knoten verwalten muss. Schauen wir uns seine Parameter an:
$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284
Wow, so viele Möglichkeiten! Zum Glück brauchen wir nur ein paar davon. Hier ist einer der Parameter, die uns interessieren:
--pod-manifest-path string
Pfad zum Verzeichnis, das Dateien für statische Pods enthält, oder Pfad zu einer Datei, die statische Pods beschreibt. Dateien, die mit einem Punkt beginnen, werden ignoriert. (VERALTET: Diese Option muss in der Konfigurationsdatei festgelegt werden, die über die Option --config an Kubelet übergeben wird. Weitere Informationen finden Sie unter kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)
Mit dieser Option können wir ausführen statische Pods – Pods, die nicht über die Kubernetes-API verwaltet werden. Statische Pods werden selten verwendet, aber sie sind sehr praktisch, um schnell einen Cluster aufzubauen, und das ist genau das, was wir brauchen. Wir werden diese große Warnung ignorieren (auch hier gilt: Führen Sie dies nicht in der Produktion aus!) und schauen, ob wir den Pod zum Laufen bringen können.
Zuerst erstellen wir ein Verzeichnis für statische Pods und führen es aus kubelet:
kubelet fängt an, einige Warnungen zu schreiben, und es scheint, als ob nichts passiert. Aber das ist nicht so! Schauen wir uns Docker an:
$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8c8a35e26663 busybox "echo 'hello world!'" 36 seconds ago Exited (0) 36 seconds ago k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f k8s.gcr.io/pause:3.2 "/pause" 2 minutes ago Up 2 minutes k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!
kubelet Ich habe das Pod-Manifest gelesen und Docker den Befehl gegeben, ein paar Container gemäß unseren Spezifikationen zu starten. (Wenn Sie sich über den „Pause“-Container wundern: Es handelt sich um einen Kubernetes-Hack – siehe dieser Blog.) Kubelet wird unseren Container starten busybox mit dem angegebenen Befehl und startet ihn auf unbestimmte Zeit neu, bis der statische Pod gelöscht wird.
Gratuliere dir selbst. Wir haben uns gerade eine der verwirrendsten Möglichkeiten ausgedacht, Text auf dem Terminal auszugeben!
Starten Sie etcd
Unser oberstes Ziel ist es, die Kubernetes-API auszuführen, aber dazu müssen wir sie zuerst ausführen usw. Beginnen wir einen minimalen etcd-Cluster, indem wir seine Einstellungen im Pods-Verzeichnis ablegen (z. B. pods/etcd.yaml):
Wenn Sie schon einmal mit Kubernetes gearbeitet haben, dürften Ihnen diese YAML-Dateien bekannt sein. Hier sind nur zwei Punkte erwähnenswert:
Wir haben den Host-Ordner gemountet /var/lib/etcd im Pod, damit die etcd-Daten nach einem Neustart erhalten bleiben (wenn dies nicht geschieht, wird der Clusterstatus bei jedem Neustart des Pods gelöscht, was nicht einmal für eine minimale Kubernetes-Installation gut ist).
Wir haben installiert hostNetwork: true. Wenig überraschend konfiguriert diese Einstellung etcd so, dass es das Host-Netzwerk anstelle des internen Netzwerks des Pods verwendet (dies erleichtert dem API-Server das Auffinden des etcd-Clusters).
Eine einfache Überprüfung zeigt, dass etcd tatsächlich auf localhost läuft und Daten auf der Festplatte speichert:
$ curl localhost:2379/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree /var/lib/etcd/
/var/lib/etcd/
└── member
├── snap
│ └── db
└── wal
├── 0.tmp
└── 0000000000000000-0000000000000000.wal
Starten des API-Servers
Noch einfacher ist der Betrieb eines Kubernetes-API-Servers. Der einzige Parameter, der übergeben werden muss, ist --etcd-servers, macht, was Sie erwarten:
Platzieren Sie diese YAML-Datei im Verzeichnis pods, und der API-Server wird gestartet. Überprüfen mit curl zeigt, dass die Kubernetes-API Port 8080 mit vollständig offenem Zugriff überwacht – keine Authentifizierung erforderlich!
(Noch einmal: Führen Sie dies nicht in der Produktion aus! Ich war ein wenig überrascht, dass die Standardeinstellung so unsicher ist. Ich vermute aber, dass dies dazu dient, die Entwicklung und das Testen einfacher zu machen.)
Und, angenehme Überraschung, kubectl funktioniert sofort nach dem Auspacken ohne zusätzliche Einstellungen!
$ ./kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
$ ./kubectl get pod
No resources found in default namespace.
Problem
Aber wenn man etwas tiefer gräbt, scheint etwas schief zu laufen:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.
Die von uns erstellten statischen Pods sind weg! Tatsächlich wurde unser Kubelet-Knoten überhaupt nicht entdeckt:
$ ./kubectl get nodes
No resources found in default namespace.
Was ist los? Wenn Sie sich vor ein paar Absätzen erinnern, haben wir das Kubelet mit einem äußerst einfachen Satz von Befehlszeilenparametern gestartet, sodass das Kubelet nicht weiß, wie es den API-Server kontaktieren und ihn über seinen Status informieren soll. Nach dem Studium der Dokumentation finden wir die entsprechende Flagge:
--kubeconfig string
Der Pfad zur Datei kubeconfig, der angibt, wie eine Verbindung zum API-Server hergestellt wird. Verfügbarkeit --kubeconfig Aktiviert den API-Servermodus, nein --kubeconfig Aktiviert den Offline-Modus.
Die ganze Zeit über haben wir das Kubelet, ohne es zu wissen, im „Offline-Modus“ ausgeführt. (Wenn wir pedantisch wären, könnten wir uns ein eigenständiges Kubelet als „Minimum Viable Kubernetes“ vorstellen, aber das wäre sehr langweilig.) Damit die „echte“ Konfiguration funktioniert, müssen wir die kubeconfig-Datei an das Kubelet übergeben, damit es weiß, wie es mit dem API-Server kommuniziert. Zum Glück ist es ganz einfach (da wir keine Authentifizierungs- oder Zertifikatsprobleme haben):
(Übrigens, wenn Sie versuchen, über Curl auf die API zuzugreifen, während das Kubelet nicht läuft, werden Sie feststellen, dass es immer noch läuft! Kubelet ist kein „Elternteil“ seiner Pods wie Docker, sondern eher ein „Steuerelement“. Daemon.“ Von einem Kubelet verwaltete Container werden weiter ausgeführt, bis das Kubelet sie stoppt.)
In ein paar Minuten kubectl sollte uns die Pods und Knoten wie erwartet zeigen:
$ ./kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default hello-mink8s 0/1 CrashLoopBackOff 261 21h
kube-system etcd-mink8s 1/1 Running 0 21h
kube-system kube-apiserver-mink8s 1/1 Running 0 21h
$ ./kubectl get nodes -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
mink8s Ready <none> 21h v1.18.5 10.70.10.228 <none> Ubuntu 18.04.4 LTS 4.15.0-109-generic docker://19.3.6
Lasst uns uns dieses Mal wirklich gratulieren (ich weiß, dass ich uns bereits selbst gratuliert habe) – wir haben einen minimalen Kubernetes-„Cluster“, der mit einer voll funktionsfähigen API läuft!
Wir starten unter
Sehen wir uns nun an, wozu die API in der Lage ist. Beginnen wir mit dem Nginx-Pod:
Hier erhalten wir einen ziemlich interessanten Fehler:
$ ./kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is
forbidden: error looking up service account default/default: serviceaccount
"default" not found
$ ./kubectl get serviceaccounts
No resources found in default namespace.
Hier sehen wir, wie erbärmlich unvollständig unsere Kubernetes-Umgebung ist – wir haben keine Konten für Dienste. Versuchen wir es noch einmal, indem wir manuell ein Dienstkonto erstellen und sehen, was passiert:
$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: default
EOS
serviceaccount/default created
$ ./kubectl apply -f nginx.yaml
Error from server (ServerTimeout): error when creating "nginx.yaml": No API
token found for service account "default", retry after the token is
automatically created and added to the service account
Selbst wenn wir das Dienstkonto manuell erstellt haben, wird das Authentifizierungstoken nicht generiert. Wenn wir weiter mit unserem minimalistischen „Cluster“ experimentieren, werden wir feststellen, dass die meisten nützlichen Dinge, die normalerweise automatisch passieren, fehlen. Der Kubernetes-API-Server ist recht minimalistisch, wobei der Großteil der aufwändigen Arbeit und automatischen Konfiguration in verschiedenen Controllern und Hintergrundjobs erfolgt, die noch nicht ausgeführt werden.
Wir können dieses Problem umgehen, indem wir die Option festlegen automountServiceAccountToken für das Dienstkonto (da wir es sowieso nicht verwenden müssen):
$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configured
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 0/1 Pending 0 13m
Endlich ist die Kapsel erschienen! Aber tatsächlich wird es nicht starten, weil wir es nicht haben Planer (Scheduler) ist eine weitere wichtige Komponente von Kubernetes. Auch hier sehen wir, dass die Kubernetes-API überraschend „dumm“ ist – wenn Sie einen Pod in der API erstellen, registriert sie ihn, versucht aber nicht herauszufinden, auf welchem Knoten sie ausgeführt werden soll.
Tatsächlich benötigen Sie keinen Planer, um einen Pod auszuführen. Sie können im Parameter manuell einen Knoten zum Manifest hinzufügen nodeName:
(Ersetzen mink8s auf den Namen des Knotens.) Nach dem Löschen und Anwenden sehen wir, dass nginx gestartet ist und auf die interne IP-Adresse lauscht:
$ ./kubectl delete pod nginx
pod "nginx" deleted
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 30s 172.17.0.2 mink8s <none> <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
Um sicherzustellen, dass das Netzwerk zwischen den Pods ordnungsgemäß funktioniert, können wir Curl von einem anderen Pod aus ausführen:
$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: curl
spec:
containers:
- image: curlimages/curl
name: curl
command: ["curl", "172.17.0.2"]
nodeName: mink8s
EOS
pod/curl created
$ ./kubectl logs curl | head -6
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
Es ist sehr interessant, in diese Umgebung einzutauchen und zu sehen, was funktioniert und was nicht. Ich habe festgestellt, dass ConfigMap und Secret wie erwartet funktionieren, Service und Deployment jedoch nicht.
Erfolg!
Dieser Beitrag wird langsam länger, also erkläre ich den Sieg und sage, dass dies eine brauchbare Konfiguration ist, die man „Kubernetes“ nennen kann. Zusammenfassend: vier Binärdateien, fünf Befehlszeilenparameter und „nur“ 45 Zeilen YAML (nicht). so viel nach Kubernetes-Standards) und wir haben einiges am Laufen:
Pods werden mithilfe der regulären Kubernetes-API verwaltet (mit ein paar Hacks).
Sie können öffentliche Containerbilder hochladen und verwalten
Pods bleiben aktiv und werden automatisch neu gestartet
Die Vernetzung zwischen Pods innerhalb desselben Knotens funktioniert recht gut
ConfigMap, Secret und einfache Speichermontage funktionieren wie erwartet
Aber vieles von dem, was Kubernetes wirklich nützlich macht, fehlt noch, wie zum Beispiel:
Pod-Planer
Authentifizierung/Autorisierung
Mehrere Knoten
Netzwerk von Dienstleistungen
Geclustertes internes DNS
Controller für Dienstkonten, Bereitstellungen, Integration mit Cloud-Anbietern und die meisten anderen Extras, die Kubernetes bietet
Was haben wir also eigentlich bekommen? Die allein laufende Kubernetes-API ist eigentlich nur eine Plattform für Containerautomatisierung. Es macht nicht viel – es ist eine Aufgabe für verschiedene Controller und Bediener, die die API verwenden –, aber es bietet eine konsistente Umgebung für die Automatisierung.