Minimaal haalbare Kubernetes

De vertaling van het artikel is gemaakt aan de vooravond van de start van de cursus "DevOps-praktijken en tools".

Minimaal haalbare Kubernetes

Als je dit leest, heb je waarschijnlijk wel eens iets over Kubernetes gehoord (en zo niet, hoe ben je hier terechtgekomen?) Maar wat is Kubernetes precies? Dit “Orchestratie van containers van industriële kwaliteit”? Of "Cloud-native besturingssysteem"? Wat betekent dit eigenlijk?

Eerlijk gezegd weet ik het niet 100% zeker. Maar ik denk dat het interessant is om in de interne aspecten te duiken en te zien wat er werkelijk aan de hand is in Kubernetes onder de vele lagen van abstracties. Laten we dus voor de lol eens kijken hoe een minimaal “Kubernetes-cluster” er eigenlijk uitziet. (Dit zal veel gemakkelijker zijn dan Kubernetes op de harde manier.)

Ik neem aan dat je basiskennis hebt van Kubernetes, Linux en containers. Alles waar we het hier over hebben is alleen bedoeld voor onderzoeks-/leerdoeleinden, breng er niets van in productie!

Recensie

Kubernetes bevat veel componenten. Volgens wikipedia, ziet de architectuur er als volgt uit:

Minimaal haalbare Kubernetes

Er worden hier minstens acht componenten getoond, maar we zullen de meeste ervan negeren. Ik wil stellen dat het minimale dat redelijkerwijs Kubernetes genoemd kan worden, uit drie hoofdcomponenten bestaat:

  • kubus
  • kube-apiserver (die afhankelijk is van etcd - zijn database)
  • containerruntime (Docker in dit geval)

Laten we eens kijken wat de documentatie over elk van hen zegt (Russisch., Engels.). Aanvankelijk kubus:

Een agent die op elk knooppunt in het cluster draait. Het zorgt ervoor dat containers in de pod draaien.

Klinkt eenvoudig genoeg. Hoe zit het met looptijden van containers (containerruntime)?

Een containerruntime is een programma dat is ontworpen om containers uit te voeren.

Erg informatief. Maar als u bekend bent met Docker, zou u een algemeen idee moeten hebben van wat het doet. (De details van de scheiding van verantwoordelijkheden tussen de containerruntime en de kubelet zijn eigenlijk behoorlijk subtiel en ik zal er hier niet op ingaan.)

И API-server?

API Server is het onderdeel van het Kubernetes-configuratiescherm dat de Kubernetes API beschikbaar stelt. De API-server is de clientzijde van het Kubernetes-configuratiescherm

Iedereen die ooit iets met Kubernetes heeft gedaan, heeft rechtstreeks of via kubectl met de API moeten communiceren. Dit is de kern van wat Kubernetes Kubernetes maakt - het brein dat de bergen van YAML die we allemaal kennen en waar we van houden (?) omzet in werkende infrastructuur. Het lijkt voor de hand te liggen dat de API aanwezig zou moeten zijn in onze minimale configuratie.

Randvoorwaarden

  • Virtuele of fysieke Linux-machine met root-toegang (ik gebruik Ubuntu 18.04 op een virtuele machine).
  • En het is alles!

Saaie installatie

We moeten Docker installeren op de machine die we gaan gebruiken. (Ik ga niet in detail treden over hoe Docker en containers werken; als je geïnteresseerd bent, dat is zo prachtige artikelen). Laten we het gewoon installeren met apt:

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

Daarna moeten we de binaire bestanden van Kubernetes ophalen. In feite hebben we alleen nodig voor de eerste lancering van ons “cluster”. kubelet, omdat we andere servercomponenten kunnen gebruiken om andere servercomponenten uit te voeren kubelet. Om met ons cluster te communiceren nadat het actief is, zullen we ook gebruiken 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

Wat gebeurt er als we gewoon wegrennen? kubelet?

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

kubelet moet als root draaien. Best logisch, aangezien hij het hele knooppunt moet beheren. Laten we eens kijken naar de parameters:

$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284

Wauw, zoveel opties! Gelukkig hebben we er maar een paar nodig. Hier is een van de parameters waarin we geïnteresseerd zijn:

--pod-manifest-path string

Pad naar de map met bestanden voor statische pods, of pad naar een bestand dat statische pods beschrijft. Bestanden die beginnen met punten worden genegeerd. (AFGESCHAFT: deze optie moet worden ingesteld in het configuratiebestand dat via de optie --config aan de Kubelet wordt doorgegeven. Zie voor meer informatie kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Met deze optie kunnen we rennen statische peulen — pods die niet worden beheerd via de Kubernetes API. Statische peulen worden zelden gebruikt, maar ze zijn erg handig om snel een cluster groot te brengen, en dit is precies wat we nodig hebben. We negeren deze grote waarschuwing (nogmaals, voer dit niet uit in productie!) en kijken of we de pod aan de praat kunnen krijgen.

Eerst zullen we een map voor statische pods maken en uitvoeren kubelet:

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

Vervolgens zullen we in een ander terminal/tmux-venster/wat dan ook een pod-manifest maken:

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

kubelet begint enkele waarschuwingen te schrijven en het lijkt alsof er niets gebeurt. Maar dat is niet waar! Laten we naar Docker kijken:

$ 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 Ik las het pod-manifest en gaf Docker de opdracht om een ​​paar containers te lanceren volgens onze specificaties. (Als je je afvraagt ​​wat de "pauze" -container is: het is een Kubernetes-hack - zie deze blog.) Kubelet zal onze container lanceren busybox met de opgegeven opdracht en zal het voor onbepaalde tijd opnieuw opstarten totdat de statische pod is verwijderd.

Feliciteer jezelf. We hebben zojuist een van de meest verwarrende manieren bedacht om tekst naar de terminal uit te voeren!

Lancering enz

Ons uiteindelijke doel is om de Kubernetes API uit te voeren, maar om dat te doen moeten we eerst draaien enz. Laten we een minimaal etcd-cluster starten door de instellingen ervan in de map pods te plaatsen (bijvoorbeeld 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

Als u ooit met Kubernetes heeft gewerkt, zullen deze YAML-bestanden u bekend voorkomen. Er zijn hier slechts twee punten die het vermelden waard zijn:

We hebben de hostmap aangekoppeld /var/lib/etcd in de pod zodat de etcd-gegevens behouden blijven na een herstart (als dit niet gebeurt, wordt de clusterstatus elke keer gewist wanneer de pod opnieuw wordt opgestart, wat zelfs voor een minimale Kubernetes-installatie niet goed zal zijn).

Wij hebben geïnstalleerd hostNetwork: true. Het is niet verrassend dat deze instelling etcd configureert om het hostnetwerk te gebruiken in plaats van het interne netwerk van de pod (dit maakt het gemakkelijker voor de API-server om het etcd-cluster te vinden).

Een eenvoudige controle laat zien dat etcd inderdaad op localhost draait en gegevens op schijf opslaat:

$ 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

Het starten van de API-server

Het runnen van een Kubernetes API-server is nog eenvoudiger. De enige parameter die moet worden doorgegeven is --etcd-servers, doet wat je verwacht:

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

Plaats dit YAML-bestand in de directory podsen de API-server zal starten. Controleren met curl laat zien dat de Kubernetes API luistert op poort 8080 met volledig open toegang - geen authenticatie vereist!

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

(Nogmaals, voer dit niet in productie uit! Ik was een beetje verrast dat de standaardinstelling zo onveilig is. Maar ik vermoed dat dit is om het ontwikkelen en testen eenvoudiger te maken.)

En, aangename verrassing, kubectl werkt direct uit de doos zonder extra instellingen!

$ ./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.

probleem

Maar als je wat dieper graaft, lijkt er iets mis te gaan:

$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.

De statische pods die we hebben gemaakt, zijn verdwenen! In feite wordt ons kubelet-knooppunt helemaal niet ontdekt:

$ ./kubectl get nodes
No resources found in default namespace.

Wat is er aan de hand? Als u zich een paar paragrafen geleden nog herinnert, zijn we de kubelet gestart met een extreem eenvoudige set opdrachtregelparameters, zodat de kubelet niet weet hoe hij contact moet maken met de API-server en hoe hij deze op de hoogte moet stellen van de status ervan. Na bestudering van de documentatie vinden we de bijbehorende vlag:

--kubeconfig string

Het pad naar het bestand kubeconfig, waarmee wordt aangegeven hoe verbinding moet worden gemaakt met de API-server. Beschikbaarheid --kubeconfig schakelt API-servermodus in, nee --kubeconfig schakelt de offlinemodus in.

Al die tijd draaiden we, zonder het te weten, de kubelet in ‘offline modus’. (Als we pedant zouden zijn, zouden we een op zichzelf staande kubelet kunnen beschouwen als "minimaal levensvatbare Kubernetes", maar dat zou erg saai zijn). Om de "echte" configuratie te laten werken, moeten we het kubeconfig-bestand doorgeven aan de kubelet, zodat deze weet hoe hij met de API-server moet praten. Gelukkig is het vrij eenvoudig (aangezien we geen authenticatie- of certificaatproblemen hebben):

apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://127.0.0.1:8080
  name: mink8s
contexts:
- context:
    cluster: mink8s
  name: mink8s
current-context: mink8s

Bewaar dit als kubeconfig.yaml, dood het proces kubelet en herstart met de nodige parameters:

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

(Trouwens, als je via curl toegang probeert te krijgen tot de API terwijl de kubelet niet actief is, zul je merken dat deze nog steeds actief is! Kubelet is geen “ouder” van zijn pods zoals Docker, het is meer een “controle” daemon." Containers die door een kubelet worden beheerd, blijven actief totdat de kubelet ze stopt.)

In een paar minuten kubectl zou ons de pods en knooppunten moeten laten zien zoals we verwachten:

$ ./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

Laten we onszelf deze keer echt feliciteren (ik weet dat ik onszelf al heb gefeliciteerd) - we hebben een minimaal Kubernetes "cluster" met een volledig functionele API!

We lanceren onder

Laten we nu eens kijken waartoe de API in staat is. Laten we beginnen met de nginx-pod:

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

Hier krijgen we een nogal interessante fout:

$ ./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 zien we hoe jammerlijk onvolledig onze Kubernetes-omgeving is: we hebben geen accounts voor services. Laten we het opnieuw proberen door handmatig een serviceaccount aan te maken en kijken wat er gebeurt:

$ 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

Zelfs als we het serviceaccount handmatig hebben aangemaakt, wordt het authenticatietoken niet gegenereerd. Terwijl we blijven experimenteren met ons minimalistische ‘cluster’, zullen we ontdekken dat de meeste nuttige dingen die gewoonlijk automatisch gebeuren, zullen ontbreken. De Kubernetes API-server is vrij minimalistisch, waarbij het grootste deel van het zware werk en de automatische configuratie plaatsvindt in verschillende controllers en achtergrondtaken die nog niet actief zijn.

We kunnen dit probleem omzeilen door de optie in te stellen automountServiceAccountToken voor het serviceaccount (aangezien we het toch niet hoeven te gebruiken):

$ 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

Eindelijk is de pod verschenen! Maar in feite zal het niet starten omdat we dat niet hebben planner (planner) is een ander belangrijk onderdeel van Kubernetes. Opnieuw zien we dat de Kubernetes API verrassend "dom" is: wanneer je een Pod in de API maakt, registreert deze deze, maar probeert hij niet uit te vinden op welk knooppunt deze moet worden uitgevoerd.

In feite hebt u geen planner nodig om een ​​pod uit te voeren. U kunt handmatig een knooppunt toevoegen aan het manifest in de parameter nodeName:

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

(Vervangen mink8s naar de naam van het knooppunt.) Na verwijderen en toepassen zien we dat nginx is gestart en luistert naar het interne IP-adres:

$ ./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>

Om er zeker van te zijn dat het netwerk tussen de pods correct werkt, kunnen we curl uitvoeren vanaf een andere pod:

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

Het is best interessant om in deze omgeving te duiken en te zien wat werkt en wat niet. Ik ontdekte dat ConfigMap en Secret werken zoals verwacht, maar Service en Deployment niet.

Succes!

Dit bericht wordt lang, dus ik ga de overwinning uitroepen en zeggen dat dit een haalbare configuratie is die 'Kubernetes' kan worden genoemd. Samenvattend: vier binaire bestanden, vijf opdrachtregelparameters en 'slechts' 45 regels YAML (niet zoveel volgens Kubernetes-standaarden) en er werken nogal wat dingen:

  • Pods worden beheerd met behulp van de reguliere Kubernetes API (met een paar hacks)
  • U kunt openbare containerimages uploaden en beheren
  • Pods blijven in leven en worden automatisch opnieuw opgestart
  • Netwerken tussen pods binnen hetzelfde knooppunt werkt redelijk goed
  • ConfigMap, geheime en eenvoudige opslagmontage werkt zoals verwacht

Maar veel van wat Kubernetes echt nuttig maakt, ontbreekt nog steeds, zoals:

  • Pod-planner
  • Authenticatie autorisatie
  • Meerdere knooppunten
  • Netwerk van diensten
  • Geclusterde interne DNS
  • Controllers voor serviceaccounts, implementaties, integratie met cloudproviders en de meeste andere goodies die Kubernetes met zich meebrengt

Dus wat hebben we eigenlijk gekregen? De Kubernetes API, die op zichzelf draait, is eigenlijk slechts een platform voor automatisering van containers. Het levert niet veel op (het is een taak voor verschillende controllers en operators die de API gebruiken), maar het biedt wel een consistente omgeving voor automatisering.

Lees meer over de cursus in het gratis webinar.

Lees verder:

Bron: www.habr.com

Voeg een reactie