Minimum lebensfähige Kubernetes

Die Übersetzung des Artikels wurde im Vorfeld des Kurses "DevOps Practices and Tools" vorbereitet .










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.






Weiterlesen:






All Articles