Articolul este tradus în cadrul pregătirii pentru lansarea cursului .

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 ? Или ? Что вообще это значит?
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 .)
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 , arhitectura arată astfel:

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 (., .). 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ă ). 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
284Wow, 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 stringCalea 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. .)
Această opțiune ne permite să rulăm — 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=podsApoi, î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 .) 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 . 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-dataDacă 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.walPornirea 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.6Să 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: nginxAici 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 serviciuChiar ș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 (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 <<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.
Citește mai departe:
Sursa: habr.com
