Minimal lebensfähige Kubernetes

Die Übersetzung des Artikels wurde am Vorabend des Kursbeginns erstellt „DevOps-Praktiken und -Tools“.

Minimal lebensfähige Kubernetes

Wenn Sie dies lesen, haben Sie wahrscheinlich schon etwas über Kubernetes gehört (und wenn nicht, wie sind Sie hier gelandet?). Aber was genau ist Kubernetes? Das „Orchestrierung industrietauglicher Behälter“? Oder „Cloud-natives Betriebssystem“? Was bedeutet das überhaupt?

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:

Minimal lebensfähige Kubernetes

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:

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

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.

$ 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 rennen? kubelet?

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

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:

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

Dann erstellen wir in einem anderen Terminal/tmux-Fenster/was auch immer 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

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):

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 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:

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

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!

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

(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):

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 unter kubeconfig.yaml, töte den Prozess kubelet und mit den notwendigen Parametern neu starten:

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

(Ü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:

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

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:

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

(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.

Erfahren Sie mehr über den Kurs im kostenlosen Webinar.

Weiterlesen:

Source: habr.com

Kommentar hinzufügen