Wenn Sie dies lesen, haben Sie wahrscheinlich etwas über Kubernetes gehört (und wenn nicht, wie sind Sie hierher gekommen?). Aber was genau ist Kubernetes? Ist das "Industrial Grade Container Orchestration" ? Oder "Cloud-natives Betriebssystem" ? Was bedeutet das überhaupt?
Um ehrlich zu sein, bin ich mir nicht 100% sicher. Aber ich denke, es ist interessant, in die Interna zu graben und zu sehen, was in Kubernetes unter seinen vielen Abstraktionsebenen tatsächlich passiert. Lassen Sie uns zum Spaß sehen, wie ein minimaler „Kubernetes-Cluster“ tatsächlich aussieht. (Dies wird viel einfacher sein als Kubernetes The Hard Way .)
Ich gehe davon aus, dass Sie Grundkenntnisse in Kubernetes, Linux und Containern haben. Alles, worüber wir hier sprechen werden, ist nur für Forschung / Studie bestimmt. Führen Sie nichts davon in der Produktion aus!
Überblick
Kubernetes enthält viele Komponenten. Laut Wikipedia sieht die Architektur folgendermaßen aus:
Hier werden mindestens acht Komponenten gezeigt, die meisten werden wir jedoch ignorieren. Ich möchte feststellen, dass das Kleinste, das vernünftigerweise als Kubernetes bezeichnet werden kann, drei Hauptkomponenten hat:
- Kubelet
- kube-apiserver (abhängig von etcd - seiner Datenbank)
- Container-Laufzeit (in diesem Fall Docker)
Mal sehen, was die Dokumentation über jeden von ihnen sagt ( Russisch , Englisch ). Zuerst das Kubelet :
Ein Agent, der auf jedem Knoten im Cluster ausgeführt wird. Er stellt sicher, dass die Container in der Kapsel laufen.
Klingt einfach genug. Was ist mit den Laufzeitcontainern (Container-Laufzeit)?
Die Container-Laufzeit ist ein Programm zum Ausführen von Containern.
Sehr informativ. Wenn Sie jedoch mit Docker vertraut sind, sollten Sie ein grundlegendes Verständnis dessen haben, was es tut. (Die Details der Trennung von Bedenken zwischen der Container-Laufzeit und dem Kubelet sind eigentlich ziemlich subtil und ich werde hier nicht darauf eingehen.)
Und die Server-API ?
API-Server - Eine Kubernetes-Dashboard-Komponente, die die Kubernetes-API darstellt. Der API-Server ist das Front-End des Kubernetes-Dashboards.
Jeder, der jemals etwas mit Kubernetes getan 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 minimalen Konfiguration vorhanden sein sollte.
Voraussetzungen
- Verwurzelte virtuelle oder physische Linux-Maschine (ich verwende Ubuntu 18.04 in einer virtuellen Maschine).
- Und das ist alles!
Langweilige Installation
Docker muss auf dem Computer installiert sein, den wir verwenden werden. (Ich werde nicht ins Detail gehen, wie Docker und Container funktionieren; es gibt großartige Artikel , wenn Sie interessiert sind ). Lassen Sie es uns einfach installieren mit
apt:
$ sudo apt install docker.io
$ sudo systemctl start docker
Danach müssen wir die Kubernetes-Binärdateien abrufen. Tatsächlich benötigen
kubeletwir für den ersten Start unseres "Clusters" nur , da wir damit andere Serverkomponenten starten können kubelet. Um mit unserem Cluster zu interagieren, nachdem er betriebsbereit ist, werden wir auch verwenden kubectl.
$ curl -L https://dl.k8s.io/v1.18.5/kubernetes-server-linux-amd64.tar.gz > server.tar.gz
$ tar xzvf server.tar.gz
$ cp kubernetes/server/bin/kubelet .
$ cp kubernetes/server/bin/kubectl .
$ ./kubelet --version
Kubernetes v1.18.5
Was passiert, wenn wir gerade starten
kubelet?
$ ./kubelet
F0609 04:03:29.105194 4583 server.go:254] mkdir /var/lib/kubelet: permission denied
kubeletsollte als root laufen. Es ist logisch genug, da er den gesamten Knoten verwalten muss. Werfen wir einen Blick auf die Parameter:
$ ./kubelet -h
< , >
$ ./kubelet -h | wc -l
284
Wow, es gibt so viele Möglichkeiten! Zum Glück brauchen wir nur ein paar davon. Hier ist einer der Parameter, an denen wir interessiert sind:
--pod-manifest-path string
Der Pfad zu dem Verzeichnis, das die Dateien für die statischen Pods enthält, oder der Pfad zu der Datei, die die statischen Pods beschreibt. Dateien, die mit Punkten beginnen, werden ignoriert. (DEPRECATED: Dieser Parameter sollte 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
diesem Parameter können wir statisch ausführen Pods - Pods, die nicht über die Kubernetes-API verwaltet werden. Statische Pods werden selten verwendet, sind jedoch sehr praktisch, um einen Cluster schnell zu erstellen, und genau das benötigen wir. Wir werden diese laute Warnung ignorieren (wieder nicht in der Produktion ausführen!) Und sehen, ob wir unter laufen können.
Zuerst erstellen wir ein Verzeichnis für statische Pods und führen Folgendes aus
kubelet:
$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods
Dann erstellen wir in einem anderen Terminal / tmux-Fenster / woanders ein Pod-Manifest:
$ cat <<EOF > pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
name: hello
spec:
containers:
- image: busybox
name: hello
command: ["echo", "hello world!"]
EOF
kubeletbeginnt einige Warnungen zu schreiben und es scheint, dass nichts passiert. Aber das ist nicht so! Werfen wir einen Blick auf Docker:
$ 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!
kubeletLesen Sie das Pod-Manifest und weisen Sie Docker an, einige Container gemäß unserer Spezifikation auszuführen. (Wenn Sie neugierig auf den "Pause" -Container sind , handelt es sich um Kubernetes-Hacking . Weitere Informationen finden Sie in diesem Blog .) Kubelet startet unseren Container busyboxmit dem angegebenen Befehl und startet ihn auf unbestimmte Zeit neu, bis der statische Pod entfernt wird.
Gratuliere dir. Wir haben uns gerade eine der kompliziertesten Möglichkeiten ausgedacht, um Text an das Terminal auszugeben!
Führen Sie etcd aus
Unser oberstes Ziel ist es, die Kubernetes-API auszuführen, aber dafür müssen wir zuerst etcd ausführen . Beginnen wir einen minimalen etcd-Cluster, indem wir seine Einstellungen im Pods-Verzeichnis ablegen (zum Beispiel
pods/etcd.yaml):
apiVersion: v1
kind: Pod
metadata:
name: etcd
namespace: kube-system
spec:
containers:
- name: etcd
command:
- etcd
- --data-dir=/var/lib/etcd
image: k8s.gcr.io/etcd:3.4.3-0
volumeMounts:
- mountPath: /var/lib/etcd
name: etcd-data
hostNetwork: true
volumes:
- hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
name: etcd-data
Wenn Sie jemals mit Kubernetes gearbeitet haben, sollten Ihnen diese YAML-Dateien vertraut sein. Hier sind nur zwei Dinge zu beachten:
Wir haben den Host-Ordner
/var/lib/etcdim Pod bereitgestellt, damit die etcd-Daten nach einem Neustart gespeichert werden (wenn dies nicht erfolgt, wird der Cluster-Status bei jedem Neustart des Pods gelöscht, was selbst für eine minimale Kubernetes-Installation schlecht ist).
Wir haben installiert
hostNetwork: true. Es überrascht nicht, dass diese Option etcd so konfiguriert, dass das Host-Netzwerk anstelle des internen Netzwerks des Pods verwendet wird (dies erleichtert dem API-Server das Auffinden des etcd-Clusters).
Eine einfache Überprüfung zeigt, dass etcd tatsächlich auf localhost ausgeführt wird und die 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
Das Starten des Kubernetes API-Servers ist noch einfacher. Der einzige Parameter, der übergeben werden muss
--etcd-servers, macht das , was Sie erwarten:
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --etcd-servers=http://127.0.0.1:2379
image: k8s.gcr.io/kube-apiserver:v1.18.5
hostNetwork: true
Legen Sie diese YAML-Datei in das Verzeichnis
podsund der API-Server wird gestartet. Die Überprüfung mit Hilfe curlzeigt, dass die Kubernetes-API Port 8080 mit vollständig offenem Zugriff überwacht - keine Authentifizierung erforderlich!
$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/pods",
"resourceVersion": "59"
},
"items": []
}
(Wiederum nicht in der Produktion ausführen! Ich war ein wenig überrascht, dass die Standardeinstellung so unsicher ist. Aber ich vermute, dies dient der einfachen Entwicklung und dem Testen.)
Und erfreulicherweise funktioniert kubectl sofort ohne Extras. die 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 Sie etwas tiefer graben, scheint etwas schief zu gehen:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.
Die statischen Pods, die wir erstellt haben, sind weg! Tatsächlich wird unser Kubelet-Knoten überhaupt nicht angezeigt:
$ ./kubectl get nodes
No resources found in default namespace.
Was ist los? Wenn Sie sich erinnern, haben wir vor einigen Absätzen kubelet mit einem extrem einfachen Satz von Befehlszeilenparametern gestartet, sodass kubelet nicht weiß, wie er den API-Server kontaktieren und ihn über seinen Status informieren soll. Nach Prüfung der Dokumentation finden wir das entsprechende Flag:
--kubeconfig string
Der Pfad zur Datei
kubeconfig, der angibt, wie eine Verbindung zum API-Server hergestellt werden soll. Die Anwesenheit --kubeconfigaktiviert den API-Servermodus, die Abwesenheit --kubeconfigaktiviert den Offline-Modus.
Die ganze Zeit, ohne es zu wissen, haben wir kubelet im "Offline-Modus" ausgeführt. (Wenn wir pedantisch wären, könnten wir den eigenständigen Kubelet-Modus als "minimal lebensfähige Kubernetes" betrachten, 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. Glücklicherweise ist dies ziemlich einfach (da wir kein Problem mit der Authentifizierung oder den Zertifikaten haben):
apiVersion: v1
kind: Config
clusters:
- cluster:
server: http://127.0.0.1:8080
name: mink8s
contexts:
- context:
cluster: mink8s
name: mink8s
current-context: mink8s
Speichern Sie dies als
kubeconfig.yaml, beenden Sie den Prozess kubeletund starten Sie mit den erforderlichen Parametern neu:
$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml
(Übrigens, wenn Sie versuchen, mit Curl auf die API zuzugreifen, wenn das Kubelet nicht verfügbar ist, werden Sie feststellen, dass es immer noch funktioniert! Kubelet ist nicht das "Elternteil" seiner Pods, wie Docker, sondern eher ein "Kontrolldämon". Die vom Kubelet verwalteten Container laufen, bis das Kubelet sie stoppt.)
Nach einigen Minuten
kubectlsollten wir die Pods und Knoten wie erwartet anzeigen :
$ ./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 diesmal wirklich gratulieren (ich weiß, ich habe bereits gratuliert) - wir haben einen minimalen Kubernetes "Cluster", der mit einer voll funktionsfähigen API arbeitet!
Laufen Sie unter
Nun wollen wir sehen, wozu die API in der Lage ist. Beginnen wir mit dem Nginx-Pod:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
Hier bekommen 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 schrecklich unvollständig unsere Kubernetes-Umgebung ist - wir haben keine Dienstkonten. Versuchen wir es erneut, 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 kein Authentifizierungstoken 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 ziemlich minimalistisch, da die meisten automatischen Optimierungen in verschiedenen Controllern und Hintergrundjobs vorgenommen werden, die noch nicht ausgeführt werden.
Wir können dieses Problem umgehen, indem
automountServiceAccountTokenwir eine Option für das Dienstkonto festlegen (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 erschien unter! Tatsächlich wird es jedoch nicht gestartet, da wir keinen Scheduler (Scheduler) haben - eine weitere wichtige Komponente von Kubernetes. Wiederum können wir sehen, dass die Kubernetes-API überraschend dumm ist - wenn Sie einen Pod in der API erstellen, registriert er ihn, versucht jedoch nicht herauszufinden, auf welchem Knoten er ausgeführt werden soll.
Sie benötigen keinen Scheduler, um einen Pod auszuführen. Sie können den Knoten manuell zum Manifest im Parameter hinzufügen
nodeName:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
nodeName: mink8s
(Durch
mink8sden Hostnamen ersetzen .) Nach dem Löschen und Anwenden sehen wir, dass nginx gestartet wurde und eine interne IP-Adresse abhört:
$ ./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 zu überprüfen, ob das Netzwerk zwischen den Pods ordnungsgemäß funktioniert, können Sie 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 macht ziemlich viel Spaß, in diese Umgebung einzutauchen und zu sehen, was funktioniert und was nicht. Ich habe festgestellt, dass ConfigMap und Secret wie erwartet funktionieren, Service und Bereitstellung jedoch nicht.
Erfolg!
Dieser Beitrag wird immer umfangreicher, daher werde ich den Sieg verkünden und erklären, dass dies eine praktikable Konfiguration ist, um "Kubernetes" aufzurufen. Zusammenfassend: vier Binärdateien, fünf Befehlszeilenparameter und "nur" 45 Zeilen YAML (standardmäßig nicht viele) Kubernetes) und wir haben eine Menge Dinge zu tun:
- Pods werden mithilfe der regulären Kubernetes-API verwaltet (mit einigen Hacks).
- Sie können Bilder von öffentlichen Containern hochladen und verwalten
- Pods bleiben am Leben und werden automatisch neu gestartet
- Die Vernetzung zwischen Pods innerhalb eines einzelnen Knotens funktioniert ziemlich gut
- ConfigMap, geheime und einfachste Bereitstellung von Repositorys funktioniert wie erwartet
Aber die meisten Dinge, die Kubernetes wirklich nützlich machen, fehlen immer noch, zum Beispiel:
- Pod Planer
- Authentifizierung / Autorisierung
- Mehrere Knoten
- Servicenetz
- Clustered Internal DNS
- Controller für Servicekonten, Bereitstellungen, Cloud-Provider-Integrationen und die meisten anderen Extras, die Kubernetes mitbringt
Was haben wir eigentlich bekommen? Die eigenständig arbeitende Kubernetes-API ist eigentlich nur eine Plattform für die Containerautomatisierung . Es macht nicht viel - es funktioniert für die verschiedenen Controller und Bediener, die die API verwenden - aber es bietet einen konsistenten Rahmen für die Automatisierung.
Erfahren Sie mehr über den Kurs in einem kostenlosen Webinar.