Minimal funktionsfähiger Kubernetes

Die Übersetzung des Artikels wurde im Vorfeld des Starts des Kurses vorbereitet „DevOps-Praktiken und -Werkzeuge“.

Minimal funktionsfähiger Kubernetes

Wenn Sie das lesen, haben Sie wahrscheinlich schon etwas über Kubernetes gehört (und wenn nicht, wie sind Sie hier gelandet?). Was genau ist also Kubernetes? Es ist „Container-Orchestrierung auf Unternehmensebene“? Или „Cloud-Native-Betriebssystem“? Что вообще это значит?

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

Ich nehme an, dass Sie Grundkenntnisse in Kubernetes, Linux und Containern haben. Alles, was wir hier besprechen werden, dient nur zu Forschungs-/Lernzwecken, führen Sie nichts davon in der Produktion aus!

Überblick

Kubernetes besteht aus vielen Komponenten. Laut Wikipedia, sieht die Architektur folgendermaßen aus:

Minimal funktionsfähiger Kubernetes

Hier sind mindestens acht Komponenten dargestellt, aber die meisten davon werden wir ignorieren. Ich möchte behaupten, dass das Minimalste, was man mit Recht 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 sehen, was in der Dokumentation über jede dieser beschrieben wird (rus., eng.). Zuerst Kubelet:

Der Agent, der auf jedem Knoten im Cluster läuft. Er überwacht, ob die Container im Pod gestartet werden.

Klingt ziemlich einfach. Was ist mit der Container-Laufzeitumgebung? (container runtime)?

Die Container-Laufzeitumgebung ist ein Programm, das für das Ausführen von Containern vorgesehen ist.

Sehr informativ. Aber wenn Sie mit Docker vertraut sind, sollten Sie eine allgemeine Vorstellung davon haben, was es tut. (Die Details der Verantwortlichkeiten zwischen der Container-Laufzeitumgebung und kubelet sind tatsächlich ziemlich subtil, und hier werde ich nicht darauf eingehen.)

Und API-Server?

Der API-Server ist eine Komponente des Kubernetes-Dashboards, die die API von Kubernetes bereitstellt. Der API-Server ist der Client-Teil des Kubernetes-Dashboards.

Jeder, der jemals etwas mit Kubernetes gemacht hat, musste entweder direkt oder über kubectl mit der API interagieren. Es ist das Herz dessen, was Kubernetes zu Kubernetes macht — das Gehirn, das die Berge von YAML, die wir alle kennen und lieben (?), in eine funktionierende Infrastruktur umwandelt. Es scheint offensichtlich, dass die API in unserer minimalen Konfiguration vorhanden sein sollte.

Vorbedingungen

  • Eine virtuelle oder physische Linux-Maschine mit Root-Zugriff (ich verwende Ubuntu 18.04 auf einer virtuellen Maschine).
  • Und das war's!

Langweilige Installation

Auf die Maschine, die wir verwenden werden, muss Docker installiert werden. (Ich werde nicht im Detail erklären, wie Docker und Container funktionieren; wenn es Sie interessiert, gibt es großartige Artikel). Lassen Sie uns einfach installieren mit apt:

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

Danach müssen wir die Binaries von Kubernetes erhalten. Tatsächlich benötigen wir für den ersten Start unseres „Clusters“ nur Kubelet, da wir für die Ausführung anderer Serverkomponenten Kubeletverwenden können. Um mit unserem Cluster zu interagieren, nachdem es funktioniert, 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 einfach ausführen Kubelet?

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

Kubelet muss als root ausgeführt werden. Ziemlich logisch, da es das gesamte Knotenmanagement übernehmen muss. Schauen wir uns seine Parameter an:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Wow, wie viele Optionen! Glücklicherweise benötigen wir nur ein paar davon. Hier ist einer der Parameter, der für uns interessant 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 von statischen Pods. Mit Punkten beginnende Dateien werden ignoriert. (VERALTET: Dieser Parameter sollte in der Konfigurationsdatei eingestellt werden, die über die Option —config an Kubelet übergeben wird. Weitere Informationen finden Sie unter kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Dieser Parameter ermöglicht es uns, statische Pods zu starten – Pods, die nicht über die Kubernetes-API verwaltet werden. Statische Pods werden selten verwendet, sind jedoch sehr nützlich, um schnell einen Cluster hochzufahren, und genau das brauchen wir. Wir werden diese laute Warnung ignorieren (nochmals, führen Sie dies nicht in der Produktion aus!) und sehen, ob wir einen Pod starten können.

Zunächst erstellen wir ein Verzeichnis für statische Pods und starten Kubelet:

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

Dann erstellen wir in einem anderen Terminal/Fenster tmux/woanders das Manifest des Pods:

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

Kubelet Es beginnt, einige Warnmeldungen auszugeben, und es scheint, als würde nichts passieren. Aber das ist nicht korrekt! Lassen Sie uns einen Blick auf Docker werfen:

$ 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       Läuft 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 den Befehl gegeben, eine Reihe von Containern gemäß unserer Spezifikation zu starten. (Falls Sie mehr über den Container „pause“ erfahren möchten, das ist ein Kubernetes-Hack - Details finden Sie in diesem Blog.) Der Kubelet wird unseren Container busybox mit dem angegebenen Befehl starten und ihn ununterbrochen neustarten, bis der statische Pod gelöscht wird.

Gratulieren Sie sich. Wir haben gerade eine der kompliziertesten Methoden erfunden, um Text im Terminal auszugeben!

Wir starten etcd

Unser Endziel ist es, die Kubernetes-API zu starten, aber dafür müssen wir zunächst im entsprechenden Docker-Imagestarten. Lassen Sie uns ein minimales etcd-Cluster starten, indem wir die Konfigurationen im Ordner 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 solche YAML-Dateien vertraut sein. Hier sind nur zwei Punkte zu beachten:

Wir haben das Host-Verzeichnis /var/lib/etcd in den Pod eingehängt, damit die etcd-Daten nach einem Neustart erhalten bleiben (wenn dies nicht getan wird, wird der Status des Clusters bei jedem Neustart des Pods gelöscht, was selbst für eine minimale Kubernetes-Installation nicht gut ist).

Wir haben festgelegt hostNetwork: true. Diese Einstellung konfiguriert etcd, um das Netzwerk des Hosts anstelle des internen Pod-Netzwerks zu nutzen (das erleichtert es dem API-Server, das 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

Start des API-Servers

Es ist noch einfacher, den Kubernetes API-Server zu starten. Der einzige Parameter, den Sie übergeben müssen, --etcd-servers, tut, 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. Ein Test mit curl zeigt, dass die Kubernetes API Port 8080 mit vollem Zugriff überwacht — eine 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 Standardkonfiguration so unsicher ist. Aber ich vermute, dass dies gemacht wurde, um die Entwicklung und das Testen zu erleichtern.)

Und, eine angenehme Überraschung, kubectl funktioniert sofort ohne weitere 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.

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 ist unser kubelet-Knoten überhaupt nicht auffindbar:

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

Was ist da los? Wenn Sie sich erinnern, hatten wir einige Absätze zuvor kubelet mit einem äußerst einfachen Satz von Kommandozeilenparametern gestartet, weshalb kubelet nicht weiß, wie es sich mit dem API-Server verbinden und ihn über seinen Status informieren kann. Beim Durchlesen der Dokumentation finden wir das passende 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 schaltet den Standalone-Modus ein.

Während all dieser Zeit haben wir, ohne es zu wissen, den kubelet im „autonomen Modus“ in Betrieb genommen. (Wenn wir genau wären, könnte man den autonomen Modus des kubelet als „minimal funktionsfähiges Kubernetes“ betrachten, aber das wäre sehr langweilig.) Damit die „echte“ Konfiguration funktioniert, müssen wir die kubeconfig-Datei an kubelet übergeben, damit es weiß, wie es mit dem API-Server kommunizieren kann. Zum Glück ist das ziemlich einfach (weil wir keine Probleme mit 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, töten 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 immer noch funktioniert! Kubelet ist nicht „Elternteil“ seiner Pods, ähnlich wie Docker, vielmehr ähnelt es einem „verwaltenden Dämon“. Die von kubelet verwalteten Container werden weiterlaufen, bis kubelet sie stoppt.)

Nach ein paar Minuten kubectl sollte es uns die Pods und Nodes zeigen, wie wir es 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   Bereit      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 wirklich gratulieren (ich weiß, ich habe es schon getan) – wir haben einen minimal funktionierenden Kubernetes-„Cluster“ mit einer voll funktionsfähigen API!

Starten wir einen Pod

Schauen wir uns nun an, was die API kann. Beginnen wir mit dem nginx-Pod:

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

Hier werden wir einen ziemlich interessanten Fehler bekommen:

$ ./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-Namespace 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 manuell ein Dienstkonto erstellen, und schauen wir mal, 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 erzeugt. Wenn wir weiterhin mit unserem minimalistischen "Cluster" experimentieren, werden wir feststellen, 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 Einstellungen geschieht 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 setzen (da wir es ohnehin 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 der Pod aufgetaucht! Aber tatsächlich wird er nicht gestartet, da wir keinen Scheduler haben – einem weiteren wichtigen Bestandteil von Kubernetes. Wieder einmal 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 ihn starten soll.

Tatsächlich ist kein Scheduler erforderlich, um einen Pod zu starten. Man kann manuell den Knoten 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ösch- und Anwendungsbefehl sehen wir, dass nginx gestartet ist und die interne IP-Adresse abhört:

$ .\/kubectl delete pod nginx
Pod "nginx" gelöscht
$ .\/kubectl apply -f nginx.yaml
Pod/nginx erstellt
$ .\/kubectl get pods -owide
NAME    BEREIT   STATUS    NEUSTARTS   ALTER   IP           KNOTEN     BENENNTEN KNOTEN   BEREITNESS-TOR
nginx   1\/1     Aktiv     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 korrekt funktioniert, können wir curl aus 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  Durchschnittsgeschwindigkeit   Zeit    Zeit     Zeit  Aktuell
                                 Dload  Upload   Gesamt   Verbraucht    Übrig  Geschwindigkeit
<!DOCTYPE html>
<html>
<head>
<title>Willkommen bei nginx!</title>

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

Erfolg!

Dieser Beitrag wird umfangreich, daher werde ich meinen Sieg verkünden und feststellen, dass dies eine praktikable Konfiguration ist, die wir "Kubernetes" nennen können. Zusammenfassend: vier Binärdateien, fünf Befehlszeilenparameter und "nur" 45 Zeilen YAML (nicht so viel nach den Standards von Kubernetes) und wir haben eine Menge Dinge zum Laufen gebracht:

  • Pods werden über die reguläre Kubernetes-API (mit einigen Hacks) verwaltet.
  • Öffentliche Container-Images können geladen und verwaltet werden.
  • Pods bleiben aktiv und werden automatisch neu gestartet.
  • Das Netzwerk zwischen Pods auf einem Knoten funktioniert ziemlich gut.
  • ConfigMap, Secret und einfaches Speichermounting funktionieren wie vorgesehen.

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

  • Der Scheduler für Pods.
  • Authentifizierung / Autorisierung.
  • Mehrere Knoten.
  • Dienstnetzwerk.
  • Cluster-interner DNS.
  • Controller für Dienstkonten, Deployments, Integration mit Cloud-Anbietern und die meisten anderen „Extras“, die Kubernetes mit sich bringt.

Was haben wir also tatsächlich erhalten? Die Kubernetes-API, die für sich allein funktioniert, ist tatsächlich nur eine Plattform für Containermanagement-Automatisierung.. Sie erledigt nicht viel – das liegt in der Verantwortung verschiedener Controller und Operatoren, die die API nutzen – aber sie bietet eine konsistente Umgebung zur Automatisierung.

Erfahren Sie mehr über den Kurs in unserem kostenlosen Webinar.

Weiterlesen:

Quelle: habr.com

60GB SSD 8Gb DDR4