Da budem iskren, nisam 100% siguran. Ali mislim da je zanimljivo kopati po unutrašnjosti i vidjeti što se stvarno događa u Kubernetesu ispod njegovih brojnih slojeva apstrakcija. Dakle, samo zabave radi, pogledajmo kako zapravo izgleda minimalni "Kubernetes klaster". (Ovo će biti puno lakše nego Kubernetes na težak način.)
Pretpostavljam da imate osnovno znanje o Kubernetesu, Linuxu i kontejnerima. Sve o čemu ovdje govorimo samo je u svrhu istraživanja/učenja, nemojte ništa od toga stavljati u proizvodnju!
Pregled
Kubernetes sadrži mnoge komponente. Prema Wikipedia, arhitektura izgleda ovako:
Ovdje je prikazano najmanje osam komponenti, ali zanemarit ćemo većinu njih. Želim reći da se minimalna stvar koja se razumno može nazvati Kubernetes sastoji od tri glavne komponente:
kubelet
kube-apiserver (što ovisi o etcd - njegovoj bazi podataka)
runtime spremnika (Docker u ovom slučaju)
Pogledajmo što dokumentacija kaže o svakom od njih (rus., Engleski.). Isprva kubelet:
Agent koji radi na svakom čvoru u klasteru. Osigurava da spremnici rade u kapsuli.
Zvuči dovoljno jednostavno. O čemu? vrijeme izvođenja spremnika (vrijeme izvođenja spremnika)?
Vrijeme izvođenja spremnika je program dizajniran za pokretanje spremnika.
Vrlo informativno. Ali ako ste upoznati s Dockerom, trebali biste imati opću predodžbu o tome što radi. (Detalji o razdvajanju odgovornosti između runtimea spremnika i kubeleta zapravo su prilično suptilni i neću ulaziti u njih ovdje.)
И API poslužitelj?
API poslužitelj je komponenta Kubernetes upravljačke ploče koja izlaže Kubernetes API. API poslužitelj je klijentska strana Kubernetes upravljačke ploče
Svatko tko je ikada radio bilo što s Kubernetesom morao je komunicirati s API-jem izravno ili putem kubectla. Ovo je srce onoga što čini Kubernetes Kubernetes - mozak koji pretvara planine YAML-a koje svi poznajemo i volimo (?) u funkcionalnu infrastrukturu. Čini se očiglednim da bi API trebao biti prisutan u našoj minimalnoj konfiguraciji.
Preduvjeti
Linux virtualni ili fizički stroj s root pristupom (koristim Ubuntu 18.04 na virtualnom stroju).
I sve je to!
Dosadna instalacija
Moramo instalirati Docker na stroj koji ćemo koristiti. (Neću ići u detalje o tome kako rade Docker i kontejneri; ako ste zainteresirani, postoji divni članci). Samo ga instalirajmo s apt:
Nakon toga moramo dobiti Kubernetes binarne datoteke. Zapravo, za početno pokretanje našeg "klastera" trebamo samo kubelet, budući da možemo koristiti druge komponente poslužitelja kubelet. Za interakciju s našim klasterom nakon što se pokrene, također ćemo koristiti kubectl.
kubelet mora se izvoditi kao root. Sasvim logično, jer treba upravljati cijelim čvorom. Pogledajmo njegove parametre:
$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284
Vau, toliko opcija! Srećom, trebamo ih samo par. Evo jednog od parametara koji nas zanima:
--pod-manifest-path string
Put do direktorija koji sadrži datoteke za statične podove ili put do datoteke koja opisuje statičke podove. Datoteke koje počinju točkama se zanemaruju. (ZASTARJELO: Ova se opcija mora postaviti u konfiguracijskoj datoteci proslijeđenoj Kubelet-u putem opcije --config. Za više informacija pogledajte kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)
Ova nam opcija omogućuje trčanje statične mahune — podovi kojima se ne upravlja putem Kubernetes API-ja. Statične mahune se rijetko koriste, ali su vrlo zgodne za brzo podizanje grozda, a to je upravo ono što nam treba. Ignorirat ćemo ovo veliko upozorenje (opet, nemojte ovo pokretati u produkciji!) i vidjeti možemo li pokrenuti pod.
Najprije ćemo stvoriti direktorij za statične mahune i pokrenuti kubelet:
kubelet počne pisati neka upozorenja i čini se kao da se ništa ne događa. Ali to nije istina! Pogledajmo 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 Pročitao sam manifest kapsule i dao Dockeru naredbu da pokrene nekoliko spremnika prema našim specifikacijama. (Ako se pitate o spremniku "pause", to je Kubernetes hack - pogledajte ovaj blog.) Kubelet će pokrenuti naš kontejner busybox s navedenom naredbom i ponovno će ga pokrenuti na neodređeno vrijeme dok se statički modul ne izbriše.
Čestitajte sebi. Upravo smo smislili jedan od najzbunjujućih načina za ispis teksta na terminal!
Pokreni itd
Naš krajnji cilj je pokrenuti Kubernetes API, ali da bismo to učinili prvo se moramo pokrenuti itd. Pokrenimo minimalni etcd klaster postavljanjem njegovih postavki u pods direktorij (na primjer, pods/etcd.yaml):
Ako ste ikada radili s Kubernetesom, ove YAML datoteke bi vam trebale biti poznate. Ovdje su vrijedne pažnje samo dvije točke:
Postavili smo host mapu /var/lib/etcd u pod tako da se etcd podaci sačuvaju nakon ponovnog pokretanja (ako se to ne učini, stanje klastera će se izbrisati svaki put kada se pod ponovno pokrene, što neće biti dobro čak ni za minimalnu instalaciju Kubernetesa).
Instalirali smo hostNetwork: true. Ova postavka, nimalo iznenađujuće, konfigurira etcd da koristi mrežu domaćina umjesto interne mreže modula (ovo će olakšati API poslužitelju da pronađe etcd klaster).
Jednostavna provjera pokazuje da etcd doista radi na lokalnom hostu i sprema podatke na disk:
$ 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
Pokretanje API poslužitelja
Pokretanje Kubernetes API poslužitelja još je lakše. Jedini parametar koji treba proslijediti je --etcd-servers, radi ono što očekujete:
Postavite ovu YAML datoteku u direktorij pods, i API poslužitelj će se pokrenuti. Provjera sa curl pokazuje da Kubernetes API sluša port 8080 s potpuno otvorenim pristupom - nije potrebna provjera autentičnosti!
(Opet, nemojte ovo pokretati u produkciji! Malo sam se iznenadio da je zadana postavka tako nesigurna. Ali pretpostavljam da je ovo zbog lakšeg razvoja i testiranja.)
I, ugodno iznenađenje, kubectl radi izvan kutije bez dodatnih postavki!
$ ./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
Ali ako kopate malo dublje, čini se da nešto nije u redu:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.
Nestale su statične mahune koje smo stvorili! Zapravo, naš kubelet čvor uopće nije otkriven:
$ ./kubectl get nodes
No resources found in default namespace.
Što je bilo? Ako se sjećate, prije nekoliko odlomaka pokrenuli smo kubelet s iznimno jednostavnim skupom parametara naredbenog retka, tako da kubelet ne zna kako kontaktirati API poslužitelj i obavijestiti ga o svom stanju. Nakon proučavanja dokumentacije nalazimo odgovarajuću zastavu:
--kubeconfig string
Put do datoteke kubeconfig, koji navodi kako se povezati s API poslužiteljem. Dostupnost --kubeconfig omogućuje način API poslužitelja, br --kubeconfig omogućuje izvanmrežni način rada.
Sve to vrijeme, a da toga nismo znali, izvodili smo kubelet u "izvanmrežnom načinu". (Da smo pedantni, mogli bismo zamisliti samostalni kubelet kao "minimalno održivi Kubernetes", ali to bi bilo vrlo dosadno). Da bi "prava" konfiguracija radila, moramo proslijediti datoteku kubeconfig kubeletu kako bi znao kako komunicirati s API poslužiteljem. Srećom, prilično je jednostavno (budući da nemamo problema s autentifikacijom ili certifikatom):
(Usput, ako pokušate pristupiti API-ju putem curla kada kubelet nije pokrenut, vidjet ćete da još uvijek radi! Kubelet nije "roditelj" svojih mahuna kao Docker, više je poput "kontrole" daemon.” Spremnici kojima upravlja kubelet nastavit će raditi dok ih kubelet ne zaustavi.)
Za par minuta kubectl trebali bi nam pokazati mahune i čvorove kako očekujemo:
$ ./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
Čestitajmo si ovaj put (znam da sam si već čestitao) - imamo minimalni Kubernetes "klaster" koji radi s potpuno funkcionalnim API-jem!
Pokrećemo pod
Sada da vidimo za što je API sposoban. Počnimo s nginx podom:
$ ./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.
Ovdje vidimo koliko je naše Kubernetes okruženje užasno nepotpuno - nemamo računa za usluge. Pokušajmo ponovno ručnim stvaranjem računa usluge i vidimo što će se dogoditi:
$ 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
Čak i kada smo račun usluge kreirali ručno, token za provjeru autentičnosti nije generiran. Dok nastavljamo eksperimentirati s našim minimalističkim "klasterom", otkrit ćemo da će nedostajati većina korisnih stvari koje se obično događaju automatski. Kubernetes API poslužitelj prilično je minimalistički, s većinom teškog podizanja i automatske konfiguracije koja se događa u raznim kontrolerima i pozadinskim poslovima koji još nisu pokrenuti.
Ovaj problem možemo riješiti postavljanjem opcije automountServiceAccountToken za račun usluge (budući da ga ionako nećemo morati koristiti):
$ 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
Napokon se pojavila mahuna! Ali zapravo neće krenuti jer nemamo planer (planer) još je jedna važna komponenta Kubernetesa. Opet, vidimo da je Kubernetes API iznenađujuće "glup" - kada kreirate Pod u API-ju, on ga registrira, ali ne pokušava otkriti na kojem čvoru ga pokrenuti.
Zapravo, ne trebate planer da biste pokrenuli pod. Možete ručno dodati čvor manifestu u parametru nodeName:
(Zamijeniti mink8s na naziv čvora.) Nakon brisanja i primjene, vidimo da je nginx pokrenut i sluša internu IP adresu:
$ ./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>
Kako bismo bili sigurni da mreža između mahuna radi ispravno, možemo pokrenuti curl iz druge mahune:
$ 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>
Vrlo je zanimljivo kopati po ovom okruženju i vidjeti što funkcionira, a što ne. Otkrio sam da ConfigMap i Secret rade prema očekivanjima, ali Service i Deployment ne.
Uspjeh!
Ovaj post postaje dug, pa ću proglasiti pobjedu i reći da je ovo održiva konfiguracija koja se može nazvati "Kubernetes". Da sažmemo: četiri binarne datoteke, pet parametara naredbenog retka i "samo" 45 redaka YAML-a (ne toliko prema standardima Kubernetes) i imamo dosta stvari koje rade:
Podovima se upravlja pomoću uobičajenog Kubernetes API-ja (uz nekoliko hakova)
Možete prenijeti javne slike spremnika i upravljati njima
Podovi ostaju živi i automatski se ponovno pokreću
Umrežavanje između mahuna unutar istog čvora funkcionira prilično dobro
ConfigMap, Secret i jednostavno montiranje pohrane rade prema očekivanjima
Ali mnogo toga što Kubernetes čini uistinu korisnim još uvijek nedostaje, poput:
Pod Planer
Autentifikacija/autorizacija
Višestruki čvorovi
Mreža usluga
Grupirani interni DNS
Kontrolori za račune usluga, implementacije, integraciju s pružateljima usluga u oblaku i većinu drugih pogodnosti koje donosi Kubernetes
Dakle, što smo zapravo dobili? Kubernetes API, koji radi samostalno, zapravo je samo platforma za automatizacija kontejnera. Ne čini puno - to je posao za razne kontrolere i operatere koji koriste API - ali pruža dosljedno okruženje za automatizaciju.