Minimal lebensfähiges Kubernetes

Der Artikel wurde im Vorfeld des Kurses erstellt „DevOps-Praktiken und Werkzeuge“.

Minimal lebensfähiges Kubernetes

Wenn Sie das hier lesen, haben Sie wahrscheinlich schon von Kubernetes gehört (und wenn nicht, wie sind Sie dann hierher gekommen?). Aber was ist Kubernetes eigentlich? Das ist „Container-Orchestrierung auf Industrieebene“? Или „Cloud-Native-Betriebssystem“? Что вообще это значит?

Ehrlich gesagt, bin ich mir nicht zu 100% sicher. Aber es ist interessant, in die Tiefen einzutauchen und zu sehen, was in Kubernetes unter seinen vielen Abstraktionsschichten tatsächlich passiert. Lassen Sie uns also aus Neugier ansehen, wie ein minimaler „Kubernetes-Cluster“ tatsächlich aussieht. (Das wird viel einfacher sein als Kubernetes The Hard Way.)

Ich gehe davon aus, dass Sie über grundlegende Kenntnisse von Kubernetes, Linux und Containern verfügen. Alles, worüber wir hier sprechen, ist nur für Forschungs-/Lernzwecke gedacht, starten Sie davon nichts in einer Produktionsumgebung!

Übersicht

Kubernetes enthält viele Komponenten. Laut Wikipedia, sieht die Architektur folgendermaßen aus:

Minimal lebensfähiges Kubernetes

Hier sind zumindest acht Komponenten dargestellt, aber die meisten von ihnen werden wir ignorieren. Ich möchte sagen, dass das Minimale, was man mit gutem Grund Kubernetes nennen kann, aus drei Hauptkomponenten besteht:

  • kubelet
  • kube-apiserver (der von etcd — seiner Datenbank — abhängt)
  • Container-Laufzeitumgebung (in diesem Fall Docker)

Lassen Sie uns schauen, was in der Dokumentation zu jedem von ihnen gesagt wird (rus., eng). Zuerst kubelet:

Ein Agent, der auf jedem Knoten im Cluster läuft. Er sorgt dafür, dass die Container im Pod gestartet werden.

Das klingt ziemlich einfach. Was ist mit Container-Laufzeit (container runtime)?

Die Container-Laufzeit ist ein Programm, das dafür ausgelegt ist, Container auszuführen.

Sehr informativ. Aber wenn Sie mit Docker vertraut sind, sollten Sie ein allgemeines Verständnis dafür haben, was es tut. (Die Details der Aufgabenverteilung zwischen der Container-Laufzeit und kubelet sind tatsächlich ziemlich fein und ich werde hier nicht darauf eingehen.)

Und API-Server?

Der API-Server ist ein Bestandteil des Kubernetes-Dashboards, der die Kubernetes-API bereitstellt. Der API-Server ist der Client-Bereich des Kubernetes-Dashboards.

Jeder, der jemals mit Kubernetes gearbeitet hat, musste auf die API entweder direkt oder über kubectl zugreifen. 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 liegt auf der Hand, dass die API in unserer Minimal-Konfiguration vorhanden sein sollte.

Voraussetzungen

  • Eine Linux-VM oder ein physischer Computer mit Root-Zugriff (ich verwende Ubuntu 18.04 auf einer virtuellen Maschine).
  • Und das ist alles!

Langweilige Installation

Auf der Maschine, die wir nutzen werden, muss Docker installiert werden. (Ich werde nicht im Detail erklären, wie Docker und Container funktionieren; falls Sie interessiert sind, gibt es tolle Artikel). Lassen Sie uns einfach installieren mit apt:

$ sudo apt install docker.io
$ sudo systemctl start docker

Danach müssen wir die Kubernetes-Binärdateien erhalten. Tatsächlich benötigen wir für den ersten Start unseres „Clusters“ nur kubelet, da wir für den Start anderer Serverkomponenten kubeletverwenden können. Um mit unserem Cluster zu interagieren, nachdem er hochgefahren ist, werden wir ebenfalls 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 einfach starten? kubelet?

$ ./kubelet
F0609 04:03:29.105194 4583 server.go:254] mkdir /var/lib/kubelet: permission denied

kubelet muss als root laufen. Das ist nur logisch, da er den gesamten Knoten verwalten muss. Lass uns seine Optionen ansehen:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Wow, wie viele Optionen! Glücklicherweise benötigen wir nur ein paar davon. Hier ist eine der Optionen, die für uns von Interesse ist:

--pod-manifest-path string

Der Pfad zum Verzeichnis, das Dateien für statische Pods enthält, oder der Pfad zu einer Datei mit der Beschreibung statischer Pods. Dateien, die mit Punkt beginnen, werden ignoriert. (VERALTET: Dieser Parameter sollte in der Konfigurationsdatei gesetzt werden, die Kubelet über die Option —config übergeben wird. Für weitere Informationen siehe. kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Dieser Parameter ermöglicht es uns, statische Pods — Pods, die nicht über die Kubernetes API verwaltet werden. Statische Pods werden selten eingesetzt, sind aber sehr nützlich für den schnellen Aufbau eines Clusters, was genau das ist, was wir brauchen. Wir ignorieren diese laute Warnung (nochmals, führen Sie dies nicht in der Produktion aus!) und schauen, ob wir den Pod starten können.

Zuerst erstellen wir ein Verzeichnis für statische Pods und starten kubelet:

$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods

Dann in einem anderen Terminal/Fenster tmux/woanders erstellen wir das Pod-Manifest:

$ cat < pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - image: busybox
    name: hello
    command: ["echo", "hello world!"]
EOF

kubelet anfängt, einige Warnungen auszugeben, und es scheint, dass nichts passiert. Aber das ist nicht der Fall! Schauen wir uns Docker an:

$ sudo docker ps -a
CONTAINER ID        IMAGE                  COMMAND                 CREATED             STATUS                      PORTS               NAMES
8c8a35e26663        busybox                "echo 'hello world!'"   vor 36 Sekunden      Beendet (0) vor 36 Sekunden                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "pause"                vor 2 Minuten       Laufend seit 2 Minuten                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!

kubelet Ich habe das Manifest des Pods gelesen und Docker angewiesen, einige Container gemäß unserer Spezifikation zu starten. (Wenn Sie mehr über den Container „pause“ erfahren möchten, handelt es sich um einen Hack von Kubernetes — Details finden Sie in diesem Blog.) Kubelet wird unseren Container busybox mit dem angegebenen Befehl starten und ihn unendlich oft neu starten, solange der statische Pod nicht entfernt wird.

Herzlichen Glückwunsch! Wir haben gerade eine der verwirrendsten Methoden zum Ausgeben von Text im Terminal erfunden!

Starte etcd

Unser Endziel ist es, die Kubernetes API zu starten, aber dafür müssen wir zuerst die Unterstützung für die Version etcd2 eingestellt.. Lassen Sie uns einen minimalen etcd-Cluster starten und seine Konfigurationen im Verzeichnis Pods 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 ähnliche YAML-Dateien vertraut 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 (ansonsten wird der Status des Clusters bei jedem Neustart des Pods gelöscht, was selbst bei einer minimalen Kubernetes-Installation unangenehm ist).

Wir haben hostNetwork: true. Dieser Parameter konfiguriert, was nicht überraschend ist, etcd zur Nutzung des Hostnetzwerks anstelle des internen Podnetzwerks (was es dem API-Server erleichtert, den etcd-Cluster zu finden).

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

API-Server starten

Den Kubernetes API-Server zu starten ist noch einfacher. Der einzige Parameter, der übergeben werden muss, --etcd-servers, tut 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 pods, und der API-Server wird gestartet. Eine Überprüfung mit curl zeigt, dass die Kubernetes API den Port 8080 mit vollem Zugriff abhört – Authentifizierung ist nicht erforderlich!

$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "59"
  },
  "items": []
}

(Starten Sie das nicht in der Produktion! Ich war etwas überrascht, dass die Standardeinstellungen so unsicher sind. Aber ich nehme an, dass dies für die Einfachheit von Entwicklung und Tests so eingerichtet ist.)

Und als angenehme Überraschung funktioniert kubectl sofort ohne zusätzliche Konfiguration!

$ ./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
Keine Ressourcen im Standard-Namespace gefunden.

Das Problem

Aber wenn man etwas tiefer gräbt, scheint etwas nicht zu stimmen:

$ ./kubectl get pod -n kube-system
Keine Ressourcen im kube-system Namespace gefunden.

Die statischen Pods, die wir erstellt haben, sind verschwunden! Tatsächlich wird unser kubelet-Knoten überhaupt nicht erkannt:

$ ./kubectl get nodes
Keine Ressourcen im Standard-Namespace gefunden.

Was ist das Problem? Wenn Sie sich erinnern, haben wir vor einigen Absätzen kubelet mit einem sehr einfachen Satz von Befehlszeilenparametern gestartet, daher weiß kubelet nicht, wie es mit dem API-Server kommunizieren und ihn über seinen Status informieren soll. Durch das Studium der Dokumentation finden wir das entsprechende Flag:

--kubeconfig string

Der Pfad zur Datei kubeconfig, die angibt, wie man sich mit dem API-Server verbindet. Das Vorhandensein --kubeconfig aktiviert den API-Server-Modus, das Fehlen --kubeconfig aktiviert den Standalone-Modus.

Die ganze Zeit haben wir, ohne es zu wissen, kubelet im „Standalone-Modus“ betrieben. (Wenn wir pedantisch wären, könnte man den Standalone-Modus von kubelet als „minimal überlebensfähiges Kubernetes“ betrachten, aber das wäre sehr langweilig.) Um die „echte“ Konfiguration zum Laufen zu bringen, müssen wir die kubeconfig-Datei an kubelet übergeben, damit es weiß, wie man mit dem API-Server kommuniziert. Glücklicherweise ist das ziemlich einfach (da wir keine Probleme mit der Authentifizierung oder 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 kubelet und starten Sie mit den erforderlichen Parametern neu:

$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml

(Übrigens, wenn Sie versuchen, über curl auf die API zuzugreifen, während kubelet nicht läuft, werden Sie feststellen, dass es weiterhin aktiv ist! Kubelet ist nicht der „Elternteil“ seiner Pods, wie es bei Docker der Fall ist. Es ist eher wie ein „Verwaltungs-Daemon“. Container, die von kubelet verwaltet werden, laufen, bis kubelet sie stoppt.)

In ein paar Minuten kubectl sollte uns die Pods und Nodes anzeigen, wie wir erwarten:

$ ./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       21h   v1.18.5   10.70.10.228           Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

Lassen Sie uns diesmal richtig auf uns selbst anstoßen (ich weiß, dass ich das schon einmal getan habe) — wir haben einen funktionalen minimalen Kubernetes „Cluster“ mit einer voll funktionsfähigen API bekommen!

Pod starten

Jetzt schauen wir uns an, was die API kann. Lassen Sie uns mit dem nginx-Pod beginnen:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx

Hier werden wir auf einen ziemlich interessanten Fehler stoßen:

$ ./kubectl apply -f nginx.yaml
Fehler vom Server (Verboten): Fehler beim Erstellen von "nginx.yaml": Pods "nginx" sind
verboten: Fehler beim Nachschlagen des Dienstkontos default/default: Dienstkonto
"default" nicht gefunden
$ ./kubectl get serviceaccounts
Keine Ressourcen im Standard-Namensraum gefunden.

Hier sehen wir, wie erschreckend unvollständig unsere Kubernetes-Umgebung ist – wir haben keine Dienstkonten. Lassen Sie uns einen weiteren Versuch starten, indem wir ein Dienstkonto manuell erstellen und schauen, was passiert:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default erstellt
$ ./kubectl apply -f nginx.yaml
Fehler vom Server (ServerTimeout): Fehler beim Erstellen von "nginx.yaml": Kein API
token gefunden für das Dienstkonto "default", bitte erneut versuchen, nachdem das Token
automatisch erstellt und dem Dienstkonto hinzugefügt wurde.

Selbst nachdem wir das Dienstkonto manuell erstellt haben, wird das Authentifizierungs-Token nicht erstellt. Wenn wir weiterhin mit unserem minimalistischen "Cluster" experimentieren, werden wir herausfinden, dass die meisten nützlichen Dinge, die normalerweise automatisch geschehen, fehlen werden. Der Kubernetes API-Server ist ziemlich minimalistisch, der Großteil der schweren automatischen Konfigurationen erfolgt in verschiedenen Controllern und Hintergrundaufgaben, die noch nicht ausgeführt werden.

Wir können dieses Problem umgehen, indem wir die Option 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 konfiguriert
$ ./kubectl apply -f nginx.yaml
pod/nginx erstellt
$ ./kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          13m

Endlich ist der Pod erschienen! Aber tatsächlich wird er nicht starten, da wir keinen Scheduler haben — ein weiterer wichtiger Bestandteil von Kubernetes. Wieder sehen wir, dass die Kubernetes-API überraschend „dumm“ ist — wenn Sie einen Pod über die API erstellen, registriert sie ihn, versucht jedoch nicht herauszufinden, auf welchem Knoten er gestartet werden soll.

Tatsächlich ist für den Start eines Pods kein Scheduler erforderlich. Sie können den Knoten manuell im Manifest im Parameter nodeName:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: mink8s

(Ersetzen Sie mink8s durch den Namen des Knotens.) Nach dem Löschen und Anwenden sehen wir, dass nginx gestartet wurde und die interne IP-Adresse hört:

$ ./kubectl delete pod nginx
Pod "nginx" wurde gelöscht
$ ./kubectl apply -f nginx.yaml
Pod/nginx erstellt
$ ./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>Willkommen bei nginx!</title>

Um sicherzustellen, dass das Netzwerk zwischen den Pods richtig funktioniert, können wir curl von einem anderen Pod ausführen:

$ cat &lt;&lt;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 erstellt
$ ./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>Willkommen bei nginx!</title>

Es ist ziemlich interessant, in dieser Umgebung herumzuforschen und zu sehen, was funktioniert und was nicht. Ich habe festgestellt, dass ConfigMap und Secret wie erwartet funktionieren, während Service und Deployment das nicht tun.

Erfolg!

Dieser Beitrag wird umfangreich, daher möchte ich den Erfolg verkünden und sagen, dass dies eine funktionierende Konfiguration ist, die man 'Kubernetes' nennen kann. Zusammenfassend: vier Binärdateien, fünf Kommandozeilenparameter und 'nur' 45 Zeilen YAML (nicht viel nach Kubernetes-Standards) und wir haben viele Dinge am Laufen.

  • Pods werden über die standardmäßige Kubernetes-API (mit einigen Hacks) verwaltet.
  • Es können öffentliche Container-Images geladen und verwaltet werden.
  • Pods bleiben aktiv und werden automatisch neu gestartet.
  • Das Netzwerk zwischen Pods auf demselben Knoten funktioniert ziemlich gut.
  • ConfigMap, Secret und einfaches Storage-Mounting funktionieren wie vorgesehen.

Aber der Großteil dessen, was Kubernetes wirklich nützlich macht, fehlt noch, zum Beispiel:

  • Der Pod-Planer
  • Authentifizierung / Autorisierung
  • Mehrere Knoten
  • Service-Netzwerk
  • Cluster-internes DNS
  • Controller für Kontodienste, Bereitstellungen, Integration mit Cloud-Anbietern und die meisten anderen „Extras“, die Kubernetes mit sich bringt.

Was haben wir also tatsächlich bekommen? Die Kubernetes-API, die für sich allein arbeitet, ist tatsächlich nur eine Plattform für Containerautomatisierung.Sie macht nicht viel — das ist die Aufgabe verschiedener Controller und Operatoren, die die API verwenden, — aber sie sorgt für eine konsistente Umgebung zur Automatisierung.

Weitere Informationen über den Kurs im kostenlosen Webinar.

Weiterlesen:

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster