Minimalno održiv Kubernetes

Prevod članka pripremljen je uoči početka kursa «DevOps prakse in orodja».

Minimalno održiv Kubernetes

Če to berete, verjetno ste že slišali za Kubernetes (če pa niste, kako ste prišli sem?). Kaj pa v resnici je Kubernetes? To je »Industrijska orkestracija kontejnerjev«? Или «Operacijski sistem v oblaku»? Что вообще это значит?

Iskreno povedano, nisem povsem prepričan. Ampak mislim, da je zanimivo raziskati in videti, kaj dejansko poteka v Kubernetesu pod mnogimi plastmi abstrakcij. Zato si poglejmo, kakšna je v resnici minimalna "Kubernetes klaster". (To bo veliko lažje kot Kubernetes The Hard Way.)

Predvidevam, da imate osnovno znanje o Kubernetesu, Linuxu in kontejnerjih. Vse, o čemer bomo tukaj govorili, je namenjeno le raziskovanju/študiju, ne zagnati tega v produkciji!

Pregled

Kubernetes vsebuje številne komponente. Po wikipedia, arhitektura izgleda takole:

Minimalno održiv Kubernetes

Tukaj je prikazanih vsaj osem komponent, vendar se večine njih bomo ignorirali. Želim povedati, da minimalna stvar, ki jo lahko smiselno imenujemo Kubernetes, vsebuje tri glavne komponente:

  • kubelet
  • kube-apiserver (ki je odvisen od etcd — svoje baze podatkov)
  • izvajalna okolja kontejnerjev (v tem primeru Docker)

Poglejmo, kaj o vsakem od njih pravi dokumentacija (rus., angl.). Najprej kubelet:

Agent, ki dela na vsakem vozlišču v klasterju. Skrbi, da so kontejnerji zagnani v podu.

Zveni dovolj preprosto. Kaj pa izvajalna okolja kontejnerjev (container runtime)?

Izvajalno okolje kontejnerjev je program, namenjen za izvajanje kontejnerjev.

Zelo informativno. Ampak če ste seznanjeni z Dockerjem, bi morali imeti splošno predstavo o tem, kaj počne. (Podrobnosti o razdelitvi odgovornosti med okoljem za izvajanje kontejnerjev in kubeletom so v resnici precej tanke in tukaj se ne nameravam poglabljati v to.)

In API-server?

API-server je komponenta nadzorne plošče Kubernetes, ki predstavlja API Kubernetes. API-server je odjemalski del nadzorne plošče Kubernetes.

Vsakdo, ki je že kdaj delal s Kubernetesom, je moral sodelovati z API-jem bodisi neposredno bodisi preko kubectl. To je srce tistega, kar Kubernetes dela zadovoljivo — možgani, ki spreminjajo gore YAML, ki jih vsi poznamo in ljubimo (?), v delujočo infrastrukturo. Očitno je, da mora biti API prisoten v naši minimalni konfiguraciji.

Predpogoji

  • Virtualni ali fizični stroj Linux s pravicami root (jaz uporabljam Ubuntu 18.04 na virtualnem stroju).
  • In to je to!

Dolgočasna namestitev

Na stroj, ki ga bomo uporabili, je treba namestiti Docker. (Ne bom podrobno opisoval, kako Docker in kontejnerji delujejo; če vas zanima, obstajajo odlični članki). Preprosto ga namestimo s pomočjo apt:

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

Po tem moramo pridobiti binarne datoteke Kubernetes. Za začetni zagon našega 'klasterja' potrebujemo le kubelet, saj bomo za zagon drugih strežniških komponent lahko uporabili kubelet. Za interakcijo z našim klasterjem, ko bo ta zaživel, pa bomo uporabili 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

Kaj se zgodi, če preprosto zaženemo kubelet?

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

kubelet mora delovati kot root. Kar je logično, saj mora upravljati celoten vozlišče. Poglejmo njegove možnosti:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Wow, koliko možnosti! Na srečo, potrebujemo le nekaj od njih. Tukaj je ena izmed možnosti, ki nas zanima:

--pod-manifest-path string

Pot do imenika, ki vsebuje datoteke za statične pode, ali pot do datoteke z opisom statičnih podov. Datoteke, ki se začnejo s piko, se prezrejo. (ZASTARELO: ta parameter je treba nastaviti v konfiguracijski datoteki, ki se predaja Kubelet-u preko možnosti —config. Za dodatne informacije glej. kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Ta parameter nam omogoča zagon statičnih podov — podi, ki niso upravljani prek Kubernetes API. Statični podi se redko uporabljajo, a so zelo priročni za hitro postavitev klasterja, kar je točno to, kar potrebujemo. Ignorirali bomo to glasno opozorilo (spet, tega ne запускайте v produkciji!) in pogledali, ali lahko zagnamo pod.

Najprej bomo ustvarili imenik za statične pode in jih zagnali kubelet:

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

Nato v drugem terminalu/oknu tmux/nekje drugje, bomo ustvarili manifest poda:

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

kubelet začne pisati neka opozorila in zdi se, da se nič ne dogaja. A 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 je prebral manifest poda in dal Dockerju ukaz, da zažene nekaj vsebnikov v skladu z našo specifikacijo. (Če vas zanima vsebnik “pause”, je to hakerski trik Kubernetes — podrobnosti so v na ovom blogu.) Kubelet bo zagnal naš vsebnik busybox z navedeno ukazom in ga neprekinjeno ponovno zagnal, dokler statični pod ne bo odstranjen.

Čestitajte si. Ravno smo našli enega izmed najbolj zapletenih načinov za izpis besedila v terminal!

Zagon etcd

Naš končni cilj je zagon Kubernetes API, a za to moramo najprej zagnati etcd. Ustvarimo minimalen etcd klaster, tako da njegove nastavitve postavimo v imenik pods (na primer, 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

Če ste že kdaj delali z Kubernetes, so vam podobni YAML datoteki verjetno znani. Tukaj velja omeniti samo dve stvari:

Zmontirali smo mapo gostitelja /var/lib/etcd v podu, aby se data etcd uchovávala po restartu (pokud to neuděláte, stav clusteru se při každém restartu podu vymaže, což není dobré ani pro minimální instalaci Kubernetes).

Nainstalovali jsme hostNetwork: true. Tento parametr, jak se dalo očekávat, nastavuje etcd k použití hostitelské sítě místo interní sítě podu (to usnadní API serveru hledání etcd clusteru).

Jednoduchá kontrola ukazuje, že etcd skutečně běží na localhost a ukládá data 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

Spuštění API serveru

Spustit API server Kubernetes je ještě jednodušší. Jediný parametr, který musíte předat, --etcd-servers, dělá to, co očekáváte:

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

Umístěte tento YAML soubor do adresáře pods, a API server se spustí. Kontrola pomocí curl ukazuje, že Kubernetes API naslouchá na portu 8080 s plným otevřeným přístupem — autentizace není potřeba!

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

(Opět, nespouštějte to v produkci! Byl jsem trochu překvapen, že výchozí nastavení je tak nezabezpečené. Ale předpokládám, že to bylo uděláno pro usnadnění vývoje a testování.)

A, příjemné překvapení, kubectl funguje hned bez jakýchkoli dalších nastavení!

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

Težava

Ale pokud se ponoříte o něco hlouběji, vypadá to, že něco není v pořádku:

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

Statické pody, které jsme vytvořili, zmizely! Ve skutečnosti, náš kubelet uzel není vůbec detekován:

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

Kaj je narobe? Če se spomnite, smo nekaj odstavkov nazaj zagnali kubelet z zelo preprostim nizom ukaznih parametrov, zato kubelet ne ve, kako se povezati s API strežnikom in ga obvestiti o svojem stanju. Ko preučimo dokumentacijo, najdemo ustrezni zastavico:

--kubeconfig niz

Pot do datoteke kubeconfig, v kateri je navedeno, kako se povezati s strežnikom API. Ima --kubeconfig omogoča način API strežnika, odsotnost --kubeconfig omogoča samostojen način.

Vse to čas, ne da bi to vedeli, smo kubelet zagnali v 'samostojnem načinu'. (Če bi bili pedantni, bi lahko samostojni način kubeleta šteli za 'minimalko delujoči Kubernetes', vendar bi bilo to zelo dolgočasno). Da bi delovala 'prava' konfiguracija, moramo predati datoteko kubeconfig kubeletu, da ve, kako kommunicirati s API strežnikom. Na srečo je to precej preprosto (ker nimamo težav z avtentikacijo ali certifikati):

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

Shrani to kot kubeconfig.yaml, ustavi proces kubelet in znova zaženi z potrebnimi parametri:

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

(Mimogrede, če poskusite dostopati do API-ja preko curl, ko kubelet ne deluje, boste ugotovili, da še vedno deluje! Kubelet ni 'starš' svojih podov, kot Docker, je bolj podoben 'nadzorni demonu'. Kontejnerji, ki jih upravlja kubelet, bodo delovali, dokler jih kubelet ne ustavi.)

Čez nekaj minut kubectl bi nam moral pokazati pode in vozlišča, kot smo pričakovali:

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

Tokrat se resnično čestitajmo (vem, da sem že čestital) — imamo minimalni 'klaster' Kubernetes, ki deluje s polno funkcionalnim API-jem!

Zaganjamo pod

Zdaj poglejmo, kaj vse zmore API. Začnimo s podom nginx:

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

Tukaj bomo prejeli precej zanimivo napako:

$ .\/kubectl apply -f nginx.yaml
Napaka s strežnikom (Forbidden): napaka pri ustvarjanju "nginx.yaml": pods "nginx" je
prepovedano: napaka pri iskanju računa storitve default\/default: račun
"default" ni najden
$ .\/kubectl get serviceaccounts
Noben vir ni najden v privzetem imenskem prostoru.

Tukaj vidimo, kako grozno nepopolno je naše okolje Kubernetes - nimamo računov storitev. Poskusimo še enkrat in ročno ustvarimo račun storitve ter pogledamo, kaj se bo zgodilo:

$ cat <<EOS | .\/kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount\/default ustvarjen
$ .\/kubectl apply -f nginx.yaml
Napaka s strežnikom (ServerTimeout): napaka pri ustvarjanju "nginx.yaml": Ni API
tokena za račun storitve "default", poskusite znova, ko bo tokene
samodejno ustvarjen in dodan računu storitve

Tudi ko smo ročno ustvarili račun storitve, se avtentikacijski token ne ustvari. Ko nadaljujemo s poskusi z našim minimalističnim «klastrom», ugotovimo, da večina koristnih stvari, ki običajno potekajo samodejno, manjka. Kubernetes API strežnik je precej minimalističen, večina težkih samodejnih nastavitev poteka v različnih kontrolerjih in ozadnih opravilih, ki še niso izvedena.

To težavo lahko obidemo tako, da nastavimo možnost automountServiceAccountToken za račun storitve (saj ga še vedno ne bomo uporabljali):

$ cat <<EOS | .\/kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount\/default konfiguriran
$ .\/kubectl apply -f nginx.yaml
pod\/nginx ustvarjen
$ .\/kubectl get pods
IME    PRIPRAVLJEN   STANJE    PONOVITVE   STAROST
nginx   0\/1     Čaka   0          13m

Nazadnje se je pod pojavil! Vendar se dejansko ne bo zagnal, saj nimamo razporejevalnika (scheduler) — še enega pomembnega komponente Kubernetes. Spet vidimo, da je API Kubernetes presenetljivo 'neumen' — ko ustvarite pod v API, ga registrira, vendar ne poskuša ugotoviti, na katerem vozlišču naj ga zažene.

Dejansko za zagon poda razporejevalnik ni potreben. Lahko ročno dodate vozlišče v manifest v parametru nodeName:

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

(Zamenjajte mink8s z imenom vozlišča.) Po brisanju in uporabi vidimo, da se nginx zažene in posluša notranji IP naslov:

$ .\/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>Dobrodošli na nginx!</title>

Da bi preverili, ali omrežje med podi deluje pravilno, lahko zaženemo curl iz drugega poda:

$ 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>Dobrodošli na nginx!</title>

Zelo zanimivo je raziskovati to okolje in videti, kaj deluje in kaj ne. Ugotovil sem, da ConfigMap in Secret delujeta kot pričakovano, storitev in nameščanje pa ne.

Uspeh!

Ta objava postaja obsežna, zato nameravam razglasiti zmagovalca in trditi, da je to izvedljiva konfiguracija, ki jo lahko imenujemo "Kubernetes". Na kratko: štiri binarne datoteke, pet parametrov ukazne vrstice in "le" 45 vrstic YAML (ni tako veliko po standardih Kubernetes) in že imamo delujočih kar nekaj stvari:

  • Podji se upravljajo z uporabo običajnega Kubernetes API (s številnimi hacki)
  • Lahko naložite javne slike kontejnerjev in njimi upravljate
  • Podji ostajajo živi in se samodejno ponovno zagnajo
  • Omrežje med podji v okviru enega gostitelja deluje precej dobro
  • ConfigMap, Secret in najprej preprosto montažo shranjevanja delujejo, kot je treba

Toda večina tistega, kar Kubernetes dejansko dela resnično uporabno, še vedno manjka, na primer:

  • Načrtovalnik podov
  • Avtentikacija / avtoritacija
  • Več gostiteljev
  • Omrežje storitev
  • Notranji DNS grozda
  • Kontrolerji za račune storitev, uvedbe, integracijo s ponudniki oblakov in večino drugih "dobrot", ki jih prinaša Kubernetes

Kaj torej dejansko imamo? Kubernetes API, ki deluje samostojno, je dejansko le platforma za avtomatizacijo kontejnerjev. Ne počne veliko – to je naloga različnih kontrolerjev in operaterjev, ki uporabljajo API – toda zagotavlja dosledno okolje za avtomatizacijo.

Izvedite več o tečaju na brezplačnem spletnem seminarju.

Preberite več:

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster