Kubernetes viabil minim

Articolul este tradus în cadrul pregătirii pentru lansarea cursului „Practicile și instrumentele DevOps”.

Kubernetes viabil minim

Dacă citiți asta, este probabil că ați auzit despre Kubernetes (și dacă nu, atunci cum ați ajuns aici?) Dar ce este cu adevărat Kubernetes? Este „Orchestrarea containerelor la nivel industrial”? Или „Sistem de operare cloud-native”? Что вообще это значит?

Sincer să fiu, nu sunt 100% sigur. Dar cred că ar fi interesant să ne uităm în interior și să vedem ce se întâmplă de fapt în Kubernetes sub cele multe straturi de abstracție. Așa că, din curiozitate, haideți să vedem cum arată de fapt un „cluster Kubernetes” minim. (Va fi mult mai simplu decât Kubernetes The Hard Way.)

Presupun că aveți cunoștințe de bază despre Kubernetes, Linux și containere. Tot ceea ce vom discuta aici este destinat exclusiv pentru cercetare/studiu, nu rulați nimic din acestea în producție!

Prezentare generală

Kubernetes conține multe componente. Conform wikipedia, arhitectura arată astfel:

Kubernetes viabil minim

Aici sunt prezentate cel puțin opt componente, dar majoritatea le vom ignora. Vreau să afirm că minimum pe care putem să-l numim Kubernetes constă în trei componente principale:

  • kubelet
  • kube-apiserver (care depinde de etcd — baza sa de date)
  • mediu de execuție a containerului (în acest caz Docker)

Să vedem ce se spune despre fiecare dintre ele în documentație (ro., en.). Mai întâi kubelet:

Agentul care funcționează pe fiecare nod din cluster. Acesta monitorizează dacă containerele sunt pornite în pod.

Sună destul de simplu. Ce ziceți de mediu de execuție a containerelor (container runtime)?

Mediul de execuție a containerului este un program destinat pentru a rula containere.

Foarte informativ. Dar dacă sunteți familiarizat cu Docker, ar trebui să aveți o idee generală despre ce face. (Detalii despre separarea responsabilităților între mediul de execuție a containerelor și kubelet sunt de fapt destul de subtile și nu voi deluca aici.)

Și API-server?

Serverul API este componenta de control al Kubernetes care reprezintă API-ul Kubernetes. Serverul API este partea client a panoului de control Kubernetes.

Oricine a lucrat vreodată cu Kubernetes a interacționat cu API-ul fie direct, fie prin kubectl. Acesta este inima a ceea ce face Kubernetes Kubernetes — creierul care transformă munții de YAML, pe care îi cunoaștem și îi iubim, într-o infrastructură funcțională. Se pare că este evident că API-ul trebuie să fie prezent în configurația noastră minimă.

Cerinte preliminare

  • O mașină virtuală sau fizică Linux cu acces root (folosesc Ubuntu 18.04 pe o mașină virtuală).
  • Și asta e tot!

Instalare plictisitoare

Pe mașina pe care o vom folosi trebuie instalat Docker. (Nu o să explic în detaliu cum funcționează Docker și containerele; dacă ești interesat, există articole excelente). Să-l instalăm doar cu ajutorul apt:

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

După aceasta, trebuie să obținem binarele Kubernetes. De fapt, pentru a porni inițial „clusterul” nostru, avem nevoie doar de kubelet, deoarece pentru a rula celelalte componente server, vom putea folosi kubelet. Pentru a interacționa cu clusterul nostru după ce va deveni funcțional, vom folosi, de asemenea, 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

Ce se va întâmpla dacă lansăm pur și simplu kubelet?

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

kubelet trebuie să ruleze sub root. E destul de logic, având în vedere că trebuie să gestioneze întregul nod. Să ne uităm la opțiunile sale:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Wow, câte opțiuni! Din fericire, vom avea nevoie doar de câteva dintre ele. Iată una dintre opțiunile care ne interesează:

--pod-manifest-path string

Calea către directorul care conține fișiere pentru poduri statice sau calea către un fișier cu descrierea podurilor statice. Fișierele care încep cu puncte sunt ignorate. (DEPRECATED: această opțiune ar trebui setată în fișierul de configurație, transmis Kubelet prin opțiunea —config. Pentru informații suplimentare, vezi. kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Această opțiune ne permite să rulăm poduri statice — poduri care nu sunt gestionate prin API-ul Kubernetes. Podurile statice sunt folosite rar, dar sunt foarte utile pentru ridicarea rapidă a unui cluster, iar asta este exact ceea ce ne trebuie. Vom ignora acest avertisment sonor (din nou, nu rulați asta în producție!) și vom vedea dacă putem porni un pod.

Mai întâi, vom crea un director pentru podurile statice și vom porni kubelet:

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

Apoi, în alt terminal/fereastră tmux/undeva, vom crea un manifest pentru pod:

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

kubelet începe să scrie câteva avertismente și pare că nu se întâmplă nimic. Dar nu este așa! Să ne uităm la 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!

kubelet a citit manifestul podului și a dat comanda Docker-ului să pornească câteva containere conform specificației noastre. (Dacă ești curios să afli despre containerul "pause", asta este o ingeniozitate Kubernetes — detalii în acest blog.) Kubelet va porni containerul nostru busybox cu comanda specificată și îl va reporni continuu, până când podul static va fi șters.

Felicitări! Tocmai am inventat unul dintre cele mai complicate moduri de a afișa text în terminal!

Să pornim etcd

Scopul nostru final este să pornim API-ul Kubernetes, dar pentru asta trebuie mai întâi să pornim etcd. Să pornim un cluster etcd minim, punând configurațiile sale în directorul pods (de exemplu, 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

Dacă ai lucrat vreodată cu Kubernetes, aceste fișiere YAML ar trebui să-ți fie familiare. Aici se cuvine să menționăm doar două aspecte:

Am montat folderul gazdei /var/lib/etcd în pod, astfel încât datele etcd să fie păstrate după repornire (dacă nu se face acest lucru, starea cluster-ului va fi ștearsă la fiecare repornire a pod-ului, ceea ce nu este deloc bine, chiar și pentru o instalare minimă Kubernetes).

Am instalat hostNetwork: true. Acest parametru, ceea ce nu este surprinzător, configurează etcd pentru a folosi rețeaua gazdelor în loc de rețeaua internă a pod-ului (acest lucru va facilita serverului API să găsească cluster-ul etcd).

O verificare simplă arată că etcd este, într-adevăr, pornit pe localhost și salvează datele pe disc:

$ 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

Pornirea serverului API

Pornirea serverului API Kubernetes este și mai simplă. Singurul parametru pe care trebuie să-l treci, --etcd-servers, face exact ceea ce te aștepți:

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

Pune acest fișier YAML în directorul pods, iar serverul API se va lansa. Verificarea cu curl arată că API-ul Kubernetes ascultă pe portul 8080 cu acces complet — autentificarea nu este necesară!

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

(Din nou, nu rulați asta în producție! Am fost puțin surprins că setarea implicită este atât de nesigură. Dar bănuiesc că a fost făcută pentru a facilita dezvoltarea și testarea.)

Și, o surpriză plăcută, kubectl funcționează din cutie fără configurații suplimentare!

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

Problema

Dar dacă te uiți mai profund, pare că ceva nu este în regulă:

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

Pod-urile statice pe care le-am creat au dispărut! De fapt, nodul nostru kubelet nu este detectat deloc:

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

Ce este în neregulă? Dacă vă amintiți, acum câteva paragrafe în urmă, am lansat kubelet cu un set extrem de simplu de parametri de linie de comandă, așa că kubelet nu știe cum să se conecteze la serverul API și să-l anunțe despre starea sa. Studiind documentația, găsim steagul corespunzător:

--kubeconfig string

Calea către fișier kubeconfig, în care este especificat cum să se conecteze la serverul API. Prezența --kubeconfig activează modul API-server, absența --kubeconfig activează modul autonom.

Tot acest timp, fără să știm, am rulat kubelet în „mod autonom”. (Dacă am fi fetiști, am putea considera modul autonom kubelet ca „Kubernetes minim viabil”, dar ar fi fost foarte plictisitor). Pentru ca configurația „reală” să funcționeze, trebuie să transmitem fișierul kubeconfig către kubelet, astfel încât să știe cum să comunice cu serverul API. Din fericire, este destul de simplu (deoarece nu avem probleme cu autentificarea sau certificatele):

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

Salvați aceasta ca kubeconfig.yaml, opriți procesul kubelet și reporniți cu parametrii necesari:

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

(Apropo, dacă încercați să accesați API-ul prin curl, când kubelet nu funcționează, veți descoperi că acesta este încă activ! Kubelet nu este „părintele” podurilor sale, la fel ca Dockerul, el este mai degrabă un „demon de gestionare”. Containerele gestionate de kubelet vor funcționa până când kubelet le va opri.)

După câteva minute kubectl ar trebui să ne arate podurile și nodurile, așa cum ne așteptăm:

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

Să ne felicităm cu adevărat de data aceasta (știu că deja am făcut-o) — avem un „cluster” Kubernetes minim care funcționează cu un API complet funcțional!

Lansăm un pod

Acum să vedem ce poate face API-ul. Să începem cu un pod nginx:

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

Aici vom primi o eroare destul de interesantă:

$ ./kubectl apply -f nginx.yaml
Eroare de la server (Interzis): eroare la crearea "nginx.yaml": pods "nginx" este
interzis: eroare la găsirea contului de serviciu default/default: contul de serviciu
"default" nu a fost găsit
$ ./kubectl get serviceaccounts
Nicio resursă găsită în namespace-ul default.

Aici vedem cât de incompletă este mediul nostru Kubernetes – nu avem conturi pentru servicii. Să încercăm din nou, creând manual un cont de serviciu și să vedem ce se întâmplă:

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default creat
$ ./kubectl apply -f nginx.yaml
Eroare de la server (TimeoutServer): eroare la crearea "nginx.yaml": Niciun token API
e găsit pentru contul de serviciu "default", retry după ce tokenul este
creat automat și adăugat la contul de serviciu

Chiar și atunci când am creat manual un cont de serviciu, tokenul de autentificare nu este creat. Continuând să experimentăm cu „clusterul” nostru minimalist, vom descoperi că majoritatea lucrurilor utile care de obicei se întâmplă automat vor lipsi. Serverul API Kubernetes este destul de minimalist, cea mai mare parte a setărilor automate grele se desfășoară în diverse controlere și sarcini de fundal care încă nu rulează.

Putem ocoli această problemă stabilind opțiunea automountServiceAccountToken pentru contul de serviciu (deoarece nu o vom folosi):

$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configurat
$ ./kubectl apply -f nginx.yaml
pod/nginx creat
$ ./kubectl get pods
NUME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          13m

În sfârșit, pod-ul a apărut! Dar de fapt nu se va lansa, deoarece nu avem scheduler-ul (programator) — încă un component important al Kubernetes. Din nou, vedem că API-ul Kubernetes este surprinzător de „prost” – atunci când creezi un pod în API, el îl înregistrează, dar nu încearcă să afle pe ce nod să-l ruleze.

De fapt, pentru a lansa un pod nu este nevoie de un programator. Poate fi adăugat manual un nod în manifest la parametrul nodeName:

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

(Înlocuiți mink8s cu numele nodului.) După delete și apply vedem că nginx a fost lansat și ascultă pe adresa IP internă:

$ ./kubectl delete pod nginx
pod "nginx" șters
$ ./kubectl apply -f nginx.yaml
pod/nginx creat
$ ./kubectl get pods -owide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1/1     Rulând  0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Bine ai venit la nginx!</title>

Pentru a ne asigura că rețeaua dintre pod-uri funcționează corect, putem rula curl dintr-un alt pod:

$ 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 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>Bine ai venit la nginx!</title>

Este destul de interesant să ne băgăm nasul în acest mediu și să vedem ce funcționează și ce nu. Am descoperit că ConfigMap și Secret funcționează așa cum era de așteptat, dar Service și Deployment nu.

Succes!

Această postare devine mare, așa că voi anunța victoria și voi declara că aceasta este o configurație viabilă cunoscută sub numele de “Kubernetes”. În sinteză: patru fișiere binare, cinci parametri ai liniei de comandă și “doar” 45 de linii YAML (nu atât de multe după standardele Kubernetes) și avem multe lucruri care funcționează:

  • Podule sunt gestionate prin intermediul API-ului Kubernetes obișnuit (cu câteva hack-uri)
  • Se pot încărca imagini publice de containere și se pot gestiona
  • Podule rămân active și se repornească automat
  • Rețeaua dintre podule de pe un singur nod funcționează destul de bine
  • ConfigMap, Secret și montarea simplă a stocărilor funcționează așa cum trebuie

Dar cea mai mare parte din ceea ce face Kubernetes cu adevărat util lipsește în continuare, de exemplu:

  • Programatorul de poduri
  • Autentificare / autorizare
  • Mai multe noduri
  • Rețeaua de servicii
  • DNS intern al clusterului
  • Controlere pentru conturi de servicii, desfășurări, integrare cu furnizorii de servicii cloud și majoritatea altor “accesorii” pe care le aduce Kubernetes

Deci, ce am obținut de fapt? API-ul Kubernetes, care funcționează de sine stătător, este într-adevăr doar o platformă pentru automatizarea containerelor. Nu face multe — aceasta este sarcina diferitelor controllere și operatoare care utilizează API-ul — dar oferă un mediu consistent pentru automatizare.

Aflați mai multe despre curs în cadrul webinarului gratuit.

Citește mai departe:

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster