Minimální životaschopné Kubernetes

Překlad článku byl připraven v předvečer zahájení kurzu "Postupy a nástroje DevOps".

Minimální životaschopné Kubernetes

Pokud toto čtete, pravděpodobně jste o Kubernetes něco slyšeli (a pokud ne, jak jste se sem dostali?) Ale co to vlastně Kubernetes je? Tento "Orchestrování průmyslových kontejnerů"? Nebo "Cloud-nativní operační systém"? Co to vůbec znamená?

Abych byl upřímný, nejsem si 100% jistý. Ale myslím si, že je zajímavé ponořit se do vnitřností a podívat se, co se skutečně děje v Kubernetes pod mnoha vrstvami abstrakcí. Pojďme se tedy jen pro zajímavost podívat, jak vlastně vypadá minimální „klastr Kubernetes“. (Bude to mnohem jednodušší než Kubernetes The Hard Way.)

Předpokládám, že máte základní znalosti o Kubernetes, Linuxu a kontejnerech. Vše, o čem zde mluvíme, je pouze pro výzkumné/výukové účely, nic z toho nezadávejte do výroby!

Recenze

Kubernetes obsahuje mnoho komponent. Podle wikipedia, architektura vypadá takto:

Minimální životaschopné Kubernetes

Je zde zobrazeno nejméně osm komponent, ale většinu z nich budeme ignorovat. Chci říci, že minimální věc, kterou lze rozumně nazvat Kubernetes, se skládá ze tří hlavních složek:

  • kubelet
  • kube-apiserver (který závisí na etcd - jeho databázi)
  • runtime kontejneru (v tomto případě Docker)

Podívejme se, co o každém z nich říká dokumentace (Ruský., Angličtina.). Nejprve kubelet:

Agent běžící na každém uzlu v clusteru. Zajišťuje, že kontejnery běží v podu.

Zní to jednoduše. Co takhle doby běhu kontejneru (doba běhu kontejneru)?

Kontejnerový runtime je program určený ke spouštění kontejnerů.

Velmi informativní. Ale pokud znáte Docker, měli byste mít obecnou představu o tom, co dělá. (Podrobnosti o oddělení odpovědností mezi běhovým prostředím kontejneru a kubeletem jsou ve skutečnosti docela jemné a nebudu je zde rozebírat.)

И API server?

API Server je komponenta ovládacího panelu Kubernetes, která zpřístupňuje rozhraní Kubernetes API. Server API je klientská strana ovládacího panelu Kubernetes

Každý, kdo někdy něco dělal s Kubernetes, musel interagovat s API buď přímo, nebo přes kubectl. To je srdce toho, co dělá Kubernetes Kubernetes – mozek, který mění hory YAML, které všichni známe a milujeme (?), na fungující infrastrukturu. Zdá se zřejmé, že API by mělo být přítomno v naší minimální konfiguraci.

Předpoklady

  • Virtuální nebo fyzický počítač Linux s přístupem root (používám Ubuntu 18.04 na virtuálním počítači).
  • A je to všechno!

Nudná instalace

Na stroj, který budeme používat, musíme nainstalovat Docker. (Nebudu zabíhat do podrobností o tom, jak Docker a kontejnery fungují; pokud vás to zajímá, je úžasné články). Pojďme to nainstalovat pomocí apt:

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

Poté musíme získat binární soubory Kubernetes. Ve skutečnosti k prvnímu spuštění našeho „klastru“ potřebujeme pouze my kubelet, protože ke spuštění dalších serverových komponent můžeme použít kubelet. K interakci s naším clusterem po jeho spuštění také použijeme 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

Co se stane, když prostě utečeme kubelet?

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

kubelet musí běžet jako root. Docela logické, protože potřebuje spravovat celý uzel. Podívejme se na jeho parametry:

$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284

Páni, tolik možností! Naštěstí jich potřebujeme jen pár. Zde je jeden z parametrů, který nás zajímá:

--pod-manifest-path string

Cesta k adresáři obsahujícímu soubory pro statické pody nebo cesta k souboru popisujícím statické pody. Soubory začínající tečkami jsou ignorovány. (zastaralé: Tato volba musí být nastavena v konfiguračním souboru předaném Kubeletu přes volbu --config. Další informace viz kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Tato možnost nám umožňuje běh statické lusky - pody, které nejsou spravovány přes Kubernetes API. Statické lusky se používají zřídka, ale jsou velmi vhodné pro rychlé zvednutí shluku, a to je přesně to, co potřebujeme. Toto velké varování budeme ignorovat (opět to nespouštějte ve výrobě!) a uvidíme, jestli dokážeme modul spustit.

Nejprve vytvoříme adresář pro statické pody a spustíme kubelet:

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

Poté v jiném terminálu/okně tmux/cokoli vytvoříme manifest pod:

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

kubelet začne psát nějaká varování a zdá se, že se nic neděje. Ale to není pravda! Podívejme se na 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 Přečetl jsem manifest pod a dal Dockerovi příkaz ke spuštění několika kontejnerů podle našich specifikací. (Pokud vás zajímá kontejner „pause“, jedná se o hack Kubernetes – viz tento blog.) Kubelet spustí náš kontejner busybox zadaným příkazem a bude jej restartovat na dobu neurčitou, dokud nebude statický modul odstraněn.

Gratuluji si. Právě jsme přišli s jedním z nejvíce matoucích způsobů výstupu textu do terminálu!

Spustit atd

Naším konečným cílem je spouštět Kubernetes API, ale k tomu musíme nejprve spustit atd. Začněme minimální cluster etcd umístěním jeho nastavení do adresáře pods (např. 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

Pokud jste někdy pracovali s Kubernetes, tyto soubory YAML by vám měly být známé. Zde stojí za zmínku pouze dva body:

Připojili jsme hostitelskou složku /var/lib/etcd v podu, aby se data etcd po restartu zachovala (pokud to neuděláte, stav clusteru se vymaže při každém restartu podu, což nebude dobré ani pro minimální instalaci Kubernetes).

Nainstalovali jsme hostNetwork: true. Toto nastavení nepřekvapivě nakonfiguruje etcd tak, aby používal hostitelskou síť namísto vnitřní sítě podu (to usnadní serveru API najít cluster etcd).

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í serveru API

Spuštění serveru Kubernetes API je ještě jednodušší. Jediný parametr, který je třeba předat, je --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 soubor YAML do adresáře podsa spustí se server API. Kontrola s curl ukazuje, že Kubernetes API naslouchá na portu 8080 se zcela otevřeným přístupem – není potřeba žádné ověřování!

$ 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 to nespouštějte v produkci! Trochu mě překvapilo, že výchozí nastavení je tak nejisté. Ale hádám, že to má usnadnit vývoj a testování.)

A příjemné překvapení, kubectl funguje hned po vybalení bez dalšího nastavování!

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

problém

Ale když se ponoříte trochu hlouběji, zdá se, že něco není v pořádku:

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

Statické moduly, které jsme vytvořili, jsou pryč! Ve skutečnosti náš uzel kubelet nebyl vůbec objeven:

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

Co se děje? Pokud si pamatujete před několika odstavci, spustili jsme kubelet s extrémně jednoduchou sadou parametrů příkazového řádku, takže kubelet neví, jak kontaktovat API server a upozornit ho na jeho stav. Po prostudování dokumentace najdeme odpovídající příznak:

--kubeconfig string

Cesta k souboru kubeconfig, který určuje, jak se připojit k serveru API. Dostupnost --kubeconfig umožňuje režim serveru API, ne --kubeconfig umožňuje offline režim.

Celou tu dobu, aniž bychom o tom věděli, jsme provozovali kubelet v „režimu offline“. (Pokud bychom byli pedantští, mohli bychom samostatný kubelet považovat za „minimálně životaschopné Kubernetes“, ale to by byla velká nuda). Aby „skutečná“ konfigurace fungovala, musíme předat soubor kubeconfig kubelet, aby věděl, jak komunikovat s API serverem. Naštěstí je to docela jednoduché (protože nemáme žádné problémy s ověřováním nebo certifikátem):

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

Uložit toto jako kubeconfig.yaml, zabít proces kubelet a restartujte s potřebnými parametry:

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

(Mimochodem, pokud se pokusíte přistupovat k API přes curl, když kubelet neběží, zjistíte, že stále běží! Kubelet není „rodič“ svých modulů jako Docker, je to spíše „ovládací prvek“ démon.“ Kontejnery spravované kubeletem budou pokračovat v provozu, dokud je kubelet nezastaví.)

Za pár minut kubectl by nám měl ukázat pody a uzly, jak očekáváme:

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

Tentokrát si opravdu pogratulujeme (vím, že už jsem si gratuloval) – běží nám minimální „klastr“ Kubernetes s plně funkčním API!

Startujeme pod

Nyní se podívejme, co API umí. Začněme s nginx pod:

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

Zde dostáváme poměrně zajímavou chybu:

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

Zde vidíme, jak žalostně neúplné je naše prostředí Kubernetes – nemáme žádné účty pro služby. Zkusme to znovu ručním vytvořením servisního účtu a uvidíme, co se stane:

$ 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

I když jsme účet služby vytvořili ručně, ověřovací token se nevygeneruje. Jak budeme pokračovat v experimentování s naším minimalistickým „clusterem“, zjistíme, že většina užitečných věcí, které se obvykle dějí automaticky, bude chybět. Server Kubernetes API je zcela minimalistický, většina náročných a automatických konfigurací se děje v různých řadičích a úlohách na pozadí, které ještě neběží.

Tento problém můžeme obejít nastavením možnosti automountServiceAccountToken pro servisní účet (protože jej stejně nebudeme muset používat):

$ 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

Konečně se lusk objevil! Ale ve skutečnosti to nezačne, protože nemáme plánovač (plánovač) je další důležitou součástí Kubernetes. Opět vidíme, že Kubernetes API je překvapivě „blbé“ – když vytvoříte Pod v API, zaregistruje ho, ale nesnaží se zjistit, na jakém uzlu ho spustit.

Ve skutečnosti ke spuštění modulu nepotřebujete plánovač. Uzel můžete do manifestu přidat ručně v parametru nodeName:

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

(Nahradit mink8s na jméno uzlu.) Po odstranění a použití vidíme, že nginx se spustil a naslouchá interní IP adrese:

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

Abychom se ujistili, že síť mezi moduly funguje správně, můžeme spustit curl z jiného modulu:

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

Je docela zajímavé se v tomto prostředí ponořit a zjistit, co funguje a co ne. Zjistil jsem, že ConfigMap a Secret fungují podle očekávání, ale Service a Deployment ne.

Úspěch!

Tento příspěvek se prodlužuje, takže vyhlásím vítězství a řeknu, že se jedná o životaschopnou konfiguraci, kterou lze nazvat „Kubernetes". Abych to shrnul: čtyři binární soubory, pět parametrů příkazového řádku a „pouze" 45 řádků YAML (ne tolik podle standardů Kubernetes) a funguje nám docela dost věcí:

  • Pody jsou spravovány pomocí běžného Kubernetes API (s několika hacky)
  • Můžete nahrávat a spravovat obrázky veřejných kontejnerů
  • Lusky zůstávají naživu a automaticky se restartují
  • Síť mezi moduly v rámci stejného uzlu funguje docela dobře
  • ConfigMap, tajná a jednoduchá montáž úložiště fungují podle očekávání

Mnoho z toho, co dělá Kubernetes skutečně užitečným, však stále chybí, jako například:

  • Plánovač pod
  • Autentizace/autorizace
  • Více uzlů
  • Síť služeb
  • Klastrovaný interní DNS
  • Kontroléry pro servisní účty, nasazení, integraci s poskytovateli cloudu a většinu dalších vychytávek, které Kubernetes přináší

Co jsme tedy vlastně dostali? Kubernetes API, které běží samo o sobě, je opravdu jen platforma pro kontejnerová automatizace. Moc toho nedělá – je to práce pro různé řadiče a operátory využívající API – ale poskytuje konzistentní prostředí pro automatizaci.

Více o kurzu se dozvíte na bezplatném webináři.

Přečtěte si více:

Zdroj: www.habr.com

Přidat komentář