Minimale levensvatbare Kubernetes

De vertaling van het artikel is voorbereid ter voorbereiding op de start van de cursus «DevOps-praktijken en -tools».

Minimale levensvatbare Kubernetes

Als je dit leest, heb je waarschijnlijk iets over Kubernetes gehoord (en als dat niet zo is, hoe ben je hier dan gekomen?) Maar wat is Kubernetes eigenlijk? Dit is “Container Orkestratie op industriële schaal”? Или «Cloud-Native Besturingssysteem»? Что вообще это значит?

Eerlijk gezegd ben ik niet 100% zeker. Maar ik denk dat het interessant is om eens in de binnenkant te kijken en te zien wat er echt gebeurt in Kubernetes onder zijn vele lagen van abstractie. Dus laten we uit nieuwsgierigheid eens kijken hoe een minimale “Kubernetes-cluster” er in werkelijkheid uitziet. (Dit zal veel eenvoudiger zijn dan Kubernetes The Hard Way.)

Ik neem aan dat je basiskennis hebt van Kubernetes, Linux en containers. Alles wat we hier zullen bespreken, is bedoeld voor onderzoek/educatie, draai niets hiervan in een productieomgeving!

Overzicht

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

Minimale levensvatbare Kubernetes

Hier worden minstens acht componenten getoond, maar de meeste hiervan zullen we negeren. Ik wil stellen dat het minimale dat met recht Kubernetes genoemd kan worden, uit drie belangrijke componenten bestaat:

  • kubelet
  • kube-apiserver (dat afhankelijk is van etcd — zijn database)
  • container-runtime (in dit geval Docker)

Laten we kijken wat er in de documentatie over elk van hen wordt gezegd (nl., en). Eerst kubelet:

Een agent die op elke node in de cluster draait. Hij houdt toezicht op het draaien van containers in de pod.

Ziet er redelijk eenvoudig uit. Hoe zit het met de container-runtime? Container-runtime is een programma dat is ontworpen om containers uit te voeren.

Zeer informatief. Maar als je bekend bent met Docker, zou je een algemeen idee moeten hebben van wat het doet. (De details van de verantwoordelijkheidsverdeling tussen de container-runtime en kubelet zijn eigenlijk vrij subtiel, en daar ga ik hier niet op in.)

API-server

En De API-server is een component van de Kubernetes-dashboard die de Kubernetes-API vertegenwoordigt. De API-server is het cliëntgedeelte van het Kubernetes-dashboard.?

Iedereen die ooit iets met Kubernetes heeft gedaan, heeft interactie moeten hebben met de API, hetzij direct, hetzij via kubectl. Dit is het hart van wat Kubernetes Kubernetes maakt — de hersenen die de bergen van YAML, die we allemaal kennen en waarderen (?), omzetten in werkende infrastructuur. Het lijkt voor de hand liggend dat de API aanwezig moet zijn in onze minimale configuratie.

Iedereen die ooit iets met Kubernetes heeft gedaan, heeft interactie gehad met de API, hetzij rechtstreeks, hetzij via kubectl. Dit is het hart van wat Kubernetes Kubernetes maakt — de geest die bergen YAML omzet in de functionerende infrastructuur die we allemaal kennen en waarderen (?). Het lijkt vanzelfsprekend dat de API in onze minimale configuratie aanwezig moet zijn.

Voorwaarden

  • Een virtuele of fysieke Linux-machine met roottoegang (ik gebruik Ubuntu 18.04 op een virtuele machine).
  • En dat is alles!

Saaie installatie

Op de machine die we gaan gebruiken, moet Docker worden geïnstalleerd. (Ik ga niet in detail uitleggen hoe Docker en containers werken; als je geïnteresseerd bent, zijn er fantastische 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 verkrijgen. Voor de opstart van onze ‘cluster’ hebben we in feite alleen kubelet, want voor het opstarten van andere servercomponenten kunnen we gebruikmaken van kubelet. Voor interactie met onze cluster, zodra deze actief is, zullen we ook gebruikmaken van 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 uitvoeren kubelet?

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

kubelet moet als root draaien. Best logisch, aangezien het alles op de node moet beheren. Laten we naar zijn parameters kijken:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Wauw, wat veel opties! Gelukkig hebben we er maar een paar nodig. Dit is er een die ons interesseert:

--pod-manifest-path string

Pad naar de directory met bestanden voor statische pods of het pad naar een bestand met de beschrijving van statische pods. Bestanden die met een punt beginnen, worden genegeerd. (DEPRECATED: deze parameter moet worden ingesteld in het configuratiebestand dat aan Kubelet wordt doorgegeven via de optie —config. Voor meer informatie zie kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Deze parameter stelt ons in staat om statische pods te draaien — pods die niet worden beheerd via de Kubernetes API. Statische pods worden zelden gebruikt, maar zijn erg handig voor een snelle opstart van de cluster, en dat is precies wat we nodig hebben. We negeren deze luidruchtige waarschuwing (nogmaals, ga dit niet in productie draaien!) en kijken of we een pod kunnen starten.

Eerst maken we een directory voor statische pods en starten we kubelet:

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

Vervolgens zullen we in een andere terminal/venster tmux/ergens anders het manifest van de pod aanmaken:

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

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

$ sudo docker ps -a
CONTAINER ID        IMAGE                  COMMAND                 CREATED             STATUS                      PORTS               NAMES
8c8a35e26663        busybox                "echo 'hello world!'"   36 seconden geleden      Gestopt (0) 36 seconden geleden                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "pause"                2 minuten geleden       Actief 2 minuten                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!

kubelet heeft het manifest van de pod gelezen en Docker de opdracht gegeven om een paar containers te starten volgens onze specificatie. (Als je geïnteresseerd bent in de container “pause”, is dat een hack van Kubernetes — zie details in deze blog.) Kubelet zal onze container starten busybox met de gespecificeerde opdracht en zal deze continu herstarten totdat de statische pod is verwijderd.

Gefeliciteerd! We hebben net een van de meest ingewikkelde manieren bedacht om tekst naar de terminal te schrijven!

We starten etcd

Ons uiteindelijke doel is om de Kubernetes API te starten, maar daarvoor moeten we eerst etcd. Laten we een minimale etcd-cluster starten door de configuratie 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 je ooit met Kubernetes hebt gewerkt, zijn dit soort YAML-bestanden je waarschijnlijk bekend. Hier zijn slechts twee punten om op te merken:

We hebben de hostmap /var/lib/etcd in de pod gemonteerd, zodat de gegevens van etcd behouden blijven na een herstart (als we dat niet doen, zal de status van de cluster worden gewist bij elke herstart van de pod, wat niet goed is, zelfs niet voor een minimale installatie van Kubernetes).

We hebben ingesteld hostNetwork: true. Deze optie configureert etcd, zoals je zou verwachten, om het hostnetwerk in plaats van het interne netwerk van de pod te gebruiken (dit vergemakkelijkt het de API-server om de etcd-cluster te vinden).

Een eenvoudige controle toont aan dat etcd daadwerkelijk draait op localhost en gegevens op de 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

De API-server starten

Een Kubernetes API-server starten is nog eenvoudiger. De enige parameter die je moet doorgeven, --etcd-servers, doet precies 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 folder pods, en de API-server wordt gestart. Controle op curl toont aan dat de Kubernetes API poort 8080 beluistert met volledige toegang — authenticatie is niet vereist!

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

(Weer, doe dit niet in productie! Ik was een beetje verrast dat de standaardinstelling zo onveilig is. Maar ik veronderstel dat het is gedaan om ontwikkeling en tests te vergemakkelijken.)

En, een aangename verrassing, kubectl werkt out-of-the-box zonder enige extra configuratie!

$ ./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 we iets dieper graven, lijkt het erop dat er iets misgaat:

$ ./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 onze kubelet-node helemaal niet gedetecteerd:

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

Wat is er aan de hand? Als je je herinnert, hebben we enkele alinea's geleden kubelet gestart met een uiterst eenvoudige set commandoregelparameters, dus kubelet weet niet hoe het contact moet opnemen met de API-server en deze op de hoogte moet stellen van zijn status. Door de documentatie te bestuderen, komen we de bijbehorende vlag tegen:

--kubeconfig string

Pad naar het bestand kubeconfig, waarin staat hoe verbinding te maken met de API-server. Het hebben van --kubeconfig activeert de API-servermodus, terwijl het ontbreken van --kubeconfig de standalone-modus activeert.

Gedurende al deze tijd hebben we, onbewust, kubelet in de "stand-alone modus" uitgevoerd. (Als we pedant waren, zouden we de stand-alone modus van kubelet kunnen beschouwen als "minimaal levensvatbaar Kubernetes", maar dat zou erg saai zijn). Voor de "echte" configuratie moeten we het kubeconfig-bestand aan kubelet doorgeven, zodat het weet hoe het met de API-server moet communiceren. Gelukkig is dat vrij eenvoudig (aangezien we geen problemen met authenticatie of certificaten 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

Sla dit op als kubeconfig.yaml, beëindig het proces kubelet en start opnieuw met de benodigde parameters:

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

(Trouwens, als je probeert de API via curl aan te roepen wanneer kubelet niet draait, ontdek je dat het nog steeds actief is! Kubelet is geen "ouder" van zijn pods, zoals Docker, het lijkt meer op een "beheerdaemoon". De containers die door kubelet worden beheerd, blijven draaien totdat kubelet ze stopt.)

Over een paar minuten kubectl zou ons de pods en nodes moeten tonen, 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       21h   v1.18.5   10.70.10.228           Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

Laten we ons deze keer oprecht feliciteren (ik weet dat ik al gefeliciteerd heb) — we hebben een minimale "cluster" van Kubernetes die werkt met een volledig functionele API!

Start de pod

Laten we nu kijken wat de API kan doen. We beginnen met de nginx-pod:

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

Hier krijgen we een vrij interessante foutmelding:

$ ./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 vreselijk incompleet onze Kubernetes-omgeving is — we hebben geen service-accounts. Laten we het nog een keer proberen door handmatig een service-account 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 wanneer we het service account handmatig hebben aangemaakt, wordt er geen authenticatietoken aangemaakt. Terwijl we blijven experimenteren met onze minimalistische ‘cluster’, zullen we ontdekken dat de meeste nuttige dingen die normaal automatisch gebeuren, ontbreken. De Kubernetes API-server is behoorlijk minimalistisch, het meeste zware automatische werk gebeurt in verschillende controllers en achtergrondtaken die nog niet draaien.

We kunnen dit probleem omzeilen door de optie automountServiceAccountToken voor het service account in te stellen (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, de pod is verschenen! Maar hij zal eigenlijk niet starten, omdat we geen scheduler (scheduler) hebben — weer een belangrijk onderdeel van Kubernetes. Opnieuw zien we dat de Kubernetes API verrassend ‘dom’ is — als je een pod in de API aanmaakt, registreert deze het, maar probeert niet uit te vinden op welke node deze moet draaien.

In feite is een scheduler niet nodig om een pod te draaien. Je kunt de node handmatig aan de manifest toevoegen in de parameter nodeName:

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

(Vervang mink8s door de naam van de node.) Na delete en apply zien we dat nginx is gestart en naar het interne IP-adres luistert:

$ ./kubectl delete pod nginx
pod "nginx" verwijderd
$ ./kubectl apply -f nginx.yaml
pod/nginx aangemaakt
$ ./kubectl get pods -owide
NAAM    KLAAR   STATUS    HERSTARTEN   LEEFTIJD   IP           KNOOP     GEBRUIKTE KNOOP   LEESBAARHEID GATES
nginx   1/1     Draaien   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welkom bij nginx!</title>

Om ervoor te zorgen dat het netwerk tussen de pods goed werkt, kunnen we curl vanuit een andere pod uitvoeren:

$ kat &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 aangemaakt
$ .\/kubectl logs curl | head -6
  % Totaal    % Ontvangen % Xferd  Gemiddelde Snelheid   Tijd    Tijd     Tijd  Huidig
                                 Dload  Upload   Totaal   Spent    Left  Snelheid
<!DOCTYPE html>
<html>
<head>
<title>Welkom bij nginx!</title>

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

Succes!

Deze post wordt lang, dus ik ga de overwinning bekendmaken en zeggen dat dit een levensvatbare configuratie is die we ‘Kubernetes’ kunnen noemen. Samenvattend: vier binaire bestanden, vijf commandoregelparameters en ‘slechts’ 45 regels YAML (niet zo veel volgens Kubernetes-normen) en we hebben een hoop dingen werkend:

  • Pods worden beheerd met behulp van de reguliere Kubernetes API (met enkele hacks)
  • Publieke containerafbeeldingen kunnen worden geüpload en beheerd
  • Pods blijven actief en worden automatisch opnieuw opgestart
  • Het netwerk tussen pods binnen één knooppunt werkt vrij goed
  • ConfigMap, Secret en de eenvoudigste opslagmontages werken zoals het hoort

Maar het grootste deel van wat Kubernetes echt nuttig maakt, ontbreekt nog steeds, bijvoorbeeld:

  • De podscheduler
  • Authenticatie / autorisatie
  • Meerdere knooppunten
  • Services netwerk
  • Cluster interne DNS
  • Controllers voor serviceaccounts, deployments, integratie met cloudproviders en de meeste andere 'snufjes' die Kubernetes met zich meebrengt

Wat hebben we eigenlijk gekregen? De Kubernetes API, die op zichzelf staat, is eigenlijk gewoon een platform voor automatisering van containers. Het doet niet veel — dat is werk voor verschillende controllers en operators die de API gebruiken — maar het biedt een consistente omgeving voor automatisering.

Leer meer over de cursus tijdens het gratis webinar.

Lees verder:

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster