Če sem iskren, nisem 100% prepričan. Ampak mislim, da se je zanimivo poglobiti v notranjost in videti, kaj se v resnici dogaja v Kubernetesu pod njegovimi številnimi plastmi abstrakcij. Torej samo za zabavo si poglejmo, kako dejansko izgleda minimalna »gruča Kubernetes«. (To bo veliko lažje kot Kubernetes Težka pot.)
Predvidevam, da imate osnovno znanje o Kubernetesu, Linuxu in vsebnikih. Vse, o čemer govorimo tukaj, je samo za raziskovalne/učne namene, ničesar od tega ne dajajte v proizvodnjo!
Pregled
Kubernetes vsebuje veliko komponent. Po navedbah Wikipedia, je arhitektura videti takole:
Tukaj je prikazanih najmanj osem komponent, vendar jih večino ne bomo upoštevali. Želim povedati, da je najmanjša stvar, ki jo lahko razumno imenujemo Kubernetes, sestavljena iz treh glavnih komponent:
kocka
kube-apiserver (ki je odvisen od etcd - njegove baze podatkov)
izvajalno okolje vsebnika (v tem primeru Docker)
Poglejmo, kaj piše v dokumentaciji o vsakem od njih (rus., angleščina.). Najprej kocka:
Agent, ki se izvaja na vsakem vozlišču v gruči. Zagotavlja, da posode tečejo v stroku.
Sliši se dovolj preprosto. Kaj pa o čas delovanja vsebnika (čas izvajanja vsebnika)?
Izvajalno okolje vsebnika je program, zasnovan za izvajanje vsebnikov.
Zelo informativno. Toda če poznate Docker, bi morali imeti splošno predstavo o tem, kaj počne. (Podrobnosti o ločevanju odgovornosti med izvajalnim okoljem vsebnika in kubelet so pravzaprav precej subtilne in tukaj se ne bom spuščal vanje.)
И API strežnik?
API Server je komponenta nadzorne plošče Kubernetes, ki razkriva API Kubernetes. Strežnik API je odjemalska stran nadzorne plošče Kubernetes
Vsakdo, ki je kdaj naredil karkoli s Kubernetesom, je moral komunicirati z API-jem neposredno ali prek kubectl. To je srce tistega, zaradi česar je Kubernetes Kubernetes – možgani, ki pretvarjajo gore YAML, ki jih vsi poznamo in ljubimo (?), v delujočo infrastrukturo. Zdi se očitno, da bi moral biti API prisoten v naši minimalni konfiguraciji.
Predpogoji
Virtualni ali fizični stroj Linux s korenskim dostopom (na virtualnem računalniku uporabljam Ubuntu 18.04).
In to je vse!
Dolgočasna namestitev
Docker moramo namestiti na stroj, ki ga bomo uporabljali. (Ne bom se spuščal v podrobnosti o delovanju Dockerja in vsebnikov; če vas zanima, obstaja čudoviti članki). Samo namestimo ga z apt:
Po tem moramo pridobiti binarne datoteke Kubernetes. Pravzaprav za začetni zagon našega »grozda« potrebujemo samo kubelet, saj lahko uporabimo za zagon drugih komponent strežnika kubelet. Za interakcijo z našo gručo po tem, ko se izvaja, bomo uporabili tudi kubectl.
kubelet mora delovati kot root. Povsem logično, saj mora upravljati celotno vozlišče. Oglejmo si njegove parametre:
$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284
Vau, toliko možnosti! Na srečo jih potrebujemo le nekaj. Tukaj je eden od parametrov, ki nas zanima:
--pod-manifest-path string
Pot do imenika, ki vsebuje datoteke za statične pode, ali pot do datoteke, ki opisuje statične pode. Datoteke, ki se začnejo s pikami, so prezrte. (ZASTARELO: to možnost je treba nastaviti v konfiguracijski datoteki, posredovani v Kubelet prek možnosti --config. Za več informacij glejte kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)
Ta možnost nam omogoča, da tečemo statični stroki — podov, ki se ne upravljajo prek API-ja Kubernetes. Statični stroki se redko uporabljajo, vendar so zelo priročni za hitro vzgojo grozda in to je točno tisto, kar potrebujemo. Prezrli bomo to veliko opozorilo (še enkrat, ne izvajajte tega v produkciji!) in preverili, ali lahko zaženemo sklop.
Najprej bomo ustvarili imenik za statične pode in ga zagnali kubelet:
kubelet začne pisati neka opozorila in zdi se, da se nič ne dogaja. Ampak to ni res! Poglejmo 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 Prebral sem manifest stroka in Dockerju dal ukaz za zagon nekaj vsebnikov v skladu z našimi specifikacijami. (Če se sprašujete o vsebniku "pause", je to kramp Kubernetes - glejte ta blog.) Kubelet bo lansiral naš vsebnik busybox z navedenim ukazom in ga bo znova zagnal za nedoločen čas, dokler statična enota ne bo izbrisana.
Čestitajte si. Pravkar smo iznašli enega najbolj zmedenih načinov za izpis besedila na terminal!
Zagon itd
Naš končni cilj je zagnati Kubernetes API, a za to moramo najprej zagnati itdd. Zaženimo minimalno gručo etcd tako, da postavimo njene nastavitve v imenik pods (na primer, pods/etcd.yaml):
Če ste kdaj delali s Kubernetesom, bi vam te datoteke YAML morale biti znane. Omeniti velja le dve točki:
Namestili smo gostiteljsko mapo /var/lib/etcd v pod, tako da se podatki etcd ohranijo po ponovnem zagonu (če tega ne storite, bo stanje gruče izbrisano ob vsakem ponovnem zagonu poda, kar ne bo dobro niti za minimalno namestitev Kubernetes).
Namestili smo hostNetwork: true. Ta nastavitev, kar ni presenetljivo, konfigurira etcd za uporabo gostiteljskega omrežja namesto notranjega omrežja poda (to bo strežniku API olajšalo iskanje gruče etcd).
Preprosto preverjanje pokaže, da etcd res deluje na lokalnem gostitelju in shranjuje 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
Zagon strežnika API
Zagon strežnika Kubernetes API je še lažji. Edini parameter, ki ga je treba posredovati, je --etcd-servers, naredi tisto, kar pričakujete:
Postavite to datoteko YAML v imenik podsin strežnik API se bo zagnal. Preverjanje z curl kaže, da Kubernetes API posluša na vratih 8080 s popolnoma odprtim dostopom – preverjanje pristnosti ni potrebno!
(Še enkrat, ne zaženite tega v produkciji! Bil sem nekoliko presenečen, da je privzeta nastavitev tako nezanesljiva. Vendar predvidevam, da je to zato, da olajšata razvoj in testiranje.)
In, prijetno presenečenje, kubectl deluje takoj, brez dodatnih nastavitev!
$ ./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
Toda če poglobite malo globlje, se zdi, da gre nekaj narobe:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.
Statičnih sklopov, ki smo jih ustvarili, ni več! Pravzaprav naše vozlišče kubelet sploh ni odkrito:
$ ./kubectl get nodes
No resources found in default namespace.
Kaj je narobe? Če se spomnite pred nekaj odstavki, smo kubelet zagnali z izjemno preprostim nizom parametrov ukazne vrstice, tako da kubelet ne ve, kako stopiti v stik s strežnikom API in ga obvestiti o svojem stanju. Po preučitvi dokumentacije najdemo ustrezno zastavo:
--kubeconfig string
Pot do datoteke kubeconfig, ki določa, kako se povezati s strežnikom API. Razpoložljivost --kubeconfig omogoča način strežnika API, št --kubeconfig omogoča način brez povezave.
Ves ta čas smo, ne da bi vedeli, izvajali kubelet v »načinu brez povezave«. (Če bi bili pedantni, bi lahko o samostojnem kubeletu razmišljali kot o "minimalno sposobnih Kubernetesih", vendar bi bilo to zelo dolgočasno). Da bo »prava« konfiguracija delovala, moramo kubeletu posredovati datoteko kubeconfig, da ve, kako komunicirati s strežnikom API. Na srečo je precej preprosto (ker nimamo nobenih težav z avtentikacijo ali certifikati):
(Mimogrede, če poskusite dostopati do API-ja prek curl, ko se kubelet ne izvaja, boste ugotovili, da se še vedno izvaja! Kubelet ni »starš« svojih podov, kot je Docker, je bolj kot »nadzor daemon." Vsebniki, ki jih upravlja kubelet, bodo še naprej delovali, dokler jih kubelet ne ustavi.)
V nekaj minutah kubectl nam mora pokazati pode in vozlišča, kot pričakujemo:
$ ./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
Tokrat si res čestitajmo (vem, da sem si že čestital) – imamo minimalno »gručo« Kubernetes, ki teče s popolnoma delujočim API-jem!
Zaženemo pod
Zdaj pa poglejmo, česa je sposoben API. Začnimo s podom nginx:
$ ./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.
Tukaj vidimo, kako obupno nepopolno je naše okolje Kubernetes - nimamo računov za storitve. Poskusimo znova z ročnim ustvarjanjem storitvenega računa in poglejmo, kaj se zgodi:
$ 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
Tudi ko smo storitveni račun ustvarili ročno, se žeton za preverjanje pristnosti ne ustvari. Ko bomo še naprej eksperimentirali z našo minimalistično "gručo", bomo ugotovili, da manjka večina uporabnih stvari, ki se običajno zgodijo samodejno. Strežnik Kubernetes API je precej minimalističen, saj se večina težkega dela in samodejne konfiguracije dogaja v različnih krmilnikih in opravilih v ozadju, ki se še ne izvajajo.
To težavo lahko rešimo tako, da nastavimo možnost automountServiceAccountToken za servisni račun (ker nam ga vseeno ne bo treba uporabljati):
$ 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
Končno se je pojavil pod! Toda v resnici se ne začne, ker nimamo načrtovalec (razporejevalnik) je še ena pomembna komponenta Kubernetesa. Spet vidimo, da je API Kubernetes presenetljivo "neumen" - ko ustvarite Pod v API-ju, ga registrira, vendar ne poskuša ugotoviti, na katerem vozlišču naj se izvaja.
Pravzaprav ne potrebujete razporejevalnika za zagon stroka. V parametru lahko manifestu ročno dodate vozlišče nodeName:
(Zamenjati mink8s na ime vozlišča.) Po brisanju in uporabi vidimo, da se je nginx zagnal in posluša notranji naslov IP:
$ ./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>
Da se prepričamo, da omrežje med podoma deluje pravilno, lahko zaženemo curl iz drugega poda:
$ 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>
Prav zanimivo je kopati v to okolje in videti, kaj deluje in kaj ne. Ugotovil sem, da ConfigMap in Secret delujeta po pričakovanjih, vendar Service in Deployment ne.
Uspeh!
Ta objava postaja dolga, zato bom razglasil zmago in povedal, da je to izvedljiva konfiguracija, ki jo lahko imenujemo "Kubernetes". Če povzamem: štiri binarne datoteke, pet parametrov ukazne vrstice in "le" 45 vrstic YAML (ne toliko po standardih Kubernetes) in imamo kar nekaj delujočih stvari:
Podi se upravljajo z običajnim API-jem Kubernetes (z nekaj vdori)
Lahko naložite in upravljate slike javnih vsebnikov
Stroki ostanejo živi in se samodejno znova zaženejo
Mreženje med podi znotraj istega vozlišča deluje zelo dobro
ConfigMap, Secret in preprosta namestitev pomnilnika delujejo po pričakovanjih
Toda še vedno manjka veliko tega, zaradi česar je Kubernetes resnično uporaben, na primer:
Pod razporejevalnik
Avtentikacija/avtorizacija
Več vozlišč
Mreža storitev
Notranji DNS v gruči
Krmilniki za storitvene račune, uvedbe, integracijo s ponudniki oblaka in večino drugih dobrot, ki jih prinaša Kubernetes
Kaj smo torej pravzaprav dobili? Kubernetes API, ki deluje sam, je v resnici samo platforma za avtomatizacija kontejnerjev. Ne naredi veliko - to je delo za različne krmilnike in operaterje, ki uporabljajo API -, vendar zagotavlja dosledno okolje za avtomatizacijo.