Przekład artykułu przygotowany przed rozpoczęciem kursu .

Jeśli to czytasz, prawdopodobnie słyszałeś coś o Kubernetes (a jeśli nie, to jak się tutaj znalazłeś?). Ale czym tak naprawdę jest Kubernetes? To ? Или ? Что вообще это значит?
Szczerze mówiąc, nie jestem tego całkowicie pewien. Ale myślę, że warto zbadać wnętrza i zobaczyć, co tak naprawdę dzieje się w Kubernetes pod jego wieloma warstwami abstrakcji. Z ciekawości, zobaczmy, jak wygląda minimalny „klaster Kubernetes”. (To będzie znacznie prostsze niż .)
Zakładam, że masz podstawową wiedzę o Kubernetes, Linuksie i kontenerach. Wszystko, o czym tutaj będziemy mówić, jest przeznaczone wyłącznie do badań/wniknięcia, nie uruchamiaj nic z tego w produkcji!
Przegląd
Kubernetes zawiera wiele komponentów. Zgodnie z , architektura wygląda następująco:

Tutaj pokazano przynajmniej osiem komponentów, ale większość z nich zignorujemy. Chcę stwierdzić, że minimalna rzecz, którą można uzasadnione nazywać Kubernetes, składa się z trzech głównych komponentów:
- kubelet
- kube-apiserver (który zależy od etcd — swojej bazy danych)
- środowisko uruchomieniowe kontenerów (w tym przypadku Docker)
Spójrzmy, co o każdym z nich mówi dokumentacja (., ). Najpierw kubelet:
Agent działający na każdym węźle w klastrze. Monitoruje, aby kontenery były uruchomione w podzie.
Brzmi dosyć prosto. A co z środowiskiem uruchomieniowym kontenerów (container runtime)?
Środowisko uruchomieniowe kontenerów to program przeznaczony do uruchamiania kontenerów.
Bardzo informacyjne. Ale jeśli znasz Dockera, to powinieneś mieć ogólne pojęcie o tym, co on robi. (Szczegóły podziału odpowiedzialności między środowiskiem uruchomieniowym a kubelet są w rzeczywistości dość subtelne i nie będę się w nie zagłębiać.)
I API-server?
Serwer API to komponent panelu zarządzania Kubernetes, który przedstawia API Kubernetes. API-server to część kliencka panelu zarządzania Kubernetes
Każdy, kto kiedykolwiek cokolwiek robił z Kubernetes, musiał wchodzić w interakcję z API albo bezpośrednio, albo przez kubectl. To serce tego, co sprawia, że Kubernetes jest Kubernetes — mózg, który przekształca góry YAML, które wszyscy znamy i kochamy (?), w działającą infrastrukturę. Wydaje się oczywiste, że API powinno być obecne w naszej minimalnej konfiguracji.
Wstępne warunki
- Wirtualna lub fizyczna maszyna Linux z dostępem root (używam Ubuntu 18.04 na maszynie wirtualnej).
- I to wszystko!
Nudna instalacja
Na maszynie, którą będziemy używać, należy zainstalować Docker. (Nie zamierzam szczegółowo opisywać, jak działa Docker i kontenery; jeśli chcesz, są ). Po prostu zainstalujmy go przy pomocy apt:
$ sudo apt install docker.io
$ sudo systemctl start docker Następnie musimy pobrać binaria Kubernetes. W rzeczywistości do początkowego uruchomienia naszego “klastra” potrzebujemy tylko kubelet, ponieważ do uruchomienia innych komponentów serwerowych możemy użyć kubelet. Aby komunikować się z naszym klastrem po jego uruchomieniu, również będziemy korzystać z 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 się stanie, jeśli po prostu uruchomimy kubelet?
$ ./kubelet
F0609 04:03:29.105194 4583 server.go:254] mkdir /var/lib/kubelet: permission denied kubelet musi działać z uprawnieniami root. Zasadniczo logiczne, ponieważ musi zarządzać całym węzłem. Zobaczmy jego opcje:
$ ./kubelet -h
$ ./kubelet -h | wc -l
284O wow, ile opcji! Na szczęście potrzebujemy tylko kilku z nich. Oto jeden z parametrów, który nas interesuje:
--pod-manifest-path stringŚcieżka do katalogu zawierającego pliki dla statycznych podów, lub ścieżka do pliku z opisem statycznych podów. Pliki zaczynające się od kropek są ignorowane. (ZDEPRECJONOWANE: ten parametr należy ustawić w pliku konfiguracyjnym przekazywanym do Kubelet przez opcję —config. Dodatkowe informacje można znaleźć na .)
Ten parametr pozwala nam uruchamiać — pode, które nie są zarządzane przez API Kubernetes. Statyczne pode są rzadko używane, ale są bardzo wygodne do szybkiego uruchomienia klastra, a to dokładnie to, czego potrzebujemy. Zignorujemy to głośne ostrzeżenie (jeszcze raz, nie uruchamiaj tego w produkcji!) i zobaczymy, czy zdołamy uruchomić pod.
Najpierw stworzymy katalog dla statycznych podów i uruchomimy kubelet:
$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=podsNastępnie w innym terminalu/oknie tmux/jakimś innym miejscu stworzymy 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 zaczyna wypisywać różne ostrzeżenia i wydaje się, że nic się nie dzieje. Ale tak nie jest! Przyjrzyjmy się Dockerowi:
$ sudo docker ps -a
IDENTYFIKATOR KONTENERA OBRAZ KOMENDA UTWORZONO STATUS PORTY NAZWY
8c8a35e26663 busybox "echo 'hello world!'" 36 sekund temu Zakończono (0) 36 sekund temu k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f k8s.gcr.io\/pause:3.2 "\/pause" 2 minuty temu Działa 2 minuty k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world! kubelet przeczytałem manifest poda i dałem Dockerowi polecenie uruchomienia dwóch kontenerów zgodnie z naszą specyfikacją. (Jeśli chcesz dowiedzieć się o kontenerze “pause”, to jest to gra w Kubernetes — szczegóły znajdziesz w .) Kubelet uruchomi nasz kontener busybox z podaną komendą i będzie go w nieskończoność restartować, aż statyczny pod zostanie usunięty.
Gratulacje! Właśnie wymyśliliśmy jeden z najciekawszych sposobów na wypisanie tekstu w terminalu!
Uruchamiamy etcd
Naszym ostatecznym celem jest uruchomienie API Kubernetes, ale najpierw musimy uruchomić . Stwórzmy minimalny klaster etcd, umieszczając jego ustawienia w katalogu pods (na przykład, 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-dataJeśli kiedykolwiek pracowałeś z Kubernetes, to podobne pliki YAML powinny być Ci znane. Należy zwrócić uwagę na dwa szczegóły:
Zamontowaliśmy folder hosta /var/lib/etcd w podzie, aby dane etcd były zachowywane po restarcie (jeśli tego nie zrobisz, stan klastra będzie zniknął przy każdym restarcie poda, co byłoby niekorzystne nawet dla minimalnej instalacji Kubernetes).
Ustawiliśmy hostNetwork: true. Ta opcja, jak można się domyślić, konfiguruje etcd do korzystania z sieci hosta zamiast wewnętrznej sieci poda (co ułatwi API serwerowi znalezienie klastra etcd).
Prosta kontrola pokazuje, że etcd jest rzeczywiście uruchomione na localhost i zapisuje dane na dysku:
$ curl localhost:2379\/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree \/var\/lib\/etcd\/\n\/var\/lib\/etcd\/\n└── member
├── snap
│ └── db
└── wal
├── 0.tmp
└── 0000000000000000-0000000000000000.walUruchomienie serwera API
Uruchomienie serwera API Kubernetes jest jeszcze prostsze. Jedyny parametr, który należy podać, --etcd-servers, robi dokładnie to, czego się spodziewasz:
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 Umieść ten plik YAML w katalogu pods, a serwer API uruchomi się. Sprawdzenie za pomocą curl pokazuje, że Kubernetes API nasłuchuje na porcie 8080 z całkowitym dostępem — uwierzytelnienie nie jest wymagane!
$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/pods",
"resourceVersion": "59"
},
"items": []
}(Znowu, nie uruchamiaj tego w środowisku produkcyjnym! Byłem trochę zdziwiony, że domyślna konfiguracja jest tak niebezpieczna. Ale przypuszczam, że zrobiono to dla ułatwienia rozwoju i testowania.)
I, miła niespodzianka, kubectl działa od razu bez żadnych dodatkowych ustawień!
$ ./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
Jednak gdy zagłębimy się nieco głębiej, wydaje się, że coś jest nie tak:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.Statyczne pody, które stworzyliśmy, zniknęły! W rzeczywistości nasz węzeł kubelet w ogóle nie jest widoczny:
$ ./kubectl get nodes
No resources found in default namespace.W czym więc rzecz? Jeśli pamiętasz, kilka akapitów wcześniej uruchamialiśmy kubelet z niezwykle prostym zestawem parametrów wiersza poleceń, więc kubelet nie wie, jak połączyć się z serwerem API i informować go o swoim stanie. Zgłębiając dokumentację, znajdujemy odpowiedni flag:
--kubeconfig string
Ścieżka do pliku kubeconfig, w którym określono, jak połączyć się z serwerem API. Obecność --kubeconfig włącza tryb API serwera, nieobecność --kubeconfig włącza tryb autonomiczny.
Przez cały ten czas, nie zdając sobie z tego sprawy, uruchamialiśmy kubelet w trybie „autonomicznym”. (Gdybyśmy byli pedantyczni, można by uznać autonomiczny tryb kubelet za „minimalnie działający Kubernetes”, ale to byłoby bardzo nudne). Aby uzyskać „prawdziwą” konfigurację, musimy przekazać plik kubeconfig do kubelet, aby wiedział, jak komunikować się z API-serverem. Na szczęście jest to dość proste (ponieważ nie mamy problemów z uwierzytelnieniem ani certyfikatami):
apiVersion: v1
kind: Config
clusters:
- cluster:
server: http://127.0.0.1:8080
name: mink8s
contexts:
- context:
cluster: mink8s
name: mink8s
current-context: mink8s Zapisz to jako kubeconfig.yaml, zabij proces kubelet i uruchom ponownie z wymaganymi parametrami:
$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml(A propos, jeśli spróbujesz uzyskać dostęp do API przez curl, gdy kubelet nie działa, odkryjesz, że nadal działa! Kubelet nie jest „rodzicem” swoich podów, podobnie jak Docker, bardziej przypomina „demon zarządzający”. Kontenery zarządzane przez kubelet będą działać, dopóki kubelet ich nie zatrzyma.)
Po kilku minutach kubectl powinien pokazać nam pody i węzły, tak jak się spodziewaliśmy:
$ ./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.6Za chwilę gratulujmy sobie naprawdę (wiem, że już gratulowałem) — mamy minimalny „klaster” Kubernetes, działający z pełnoprawnym API!
Uruchamiamy pod
Teraz przyjrzyjmy się, co potrafi API. Zaczniemy od poda nginx:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginxTutaj napotkamy dość interesujący błąd:
$ ./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.Tutaj widzimy, jak strasznie niekompletne jest nasze środowisko Kubernetes — nie mamy kont dla usług. Spróbujmy jeszcze raz, tworząc konto usługi ręcznie, i zobaczmy, co się stanie:
$ 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 accountNawet kiedy stworzyliśmy konto usługi ręcznie, token uwierzytelniający nie jest tworzony. Kontynuując eksperymenty z naszym minimalistycznym "klastrem", odkryjemy, że wiele użytecznych rzeczy, które zwykle odbywają się automatycznie, będzie brakować. Serwer API Kubernetes jest dość minimalistyczny, a większość ciężkich automatycznych ustawień odbywa się w różnych kontrolerach i zadaniach w tle, które jeszcze nie są wykonywane.
Możemy obejść ten problem, ustawiając opcję automountServiceAccountToken dla konta usługi (ponieważ i tak nie będziemy musieć go używać):
$ 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 13mW końcu pod się pojawił! Ale w rzeczywistości nie uruchomi się, ponieważ nie mamy (scheduler) – jeszcze jednego ważnego komponentu Kubernetes. Po raz kolejny widzimy, że API Kubernetes na zaskakująco jest „głupie” – kiedy tworzysz pod w API, rejestruje go, ale nie próbuje ustalić, na którym węźle go uruchomić.
Rzeczywiście, aby uruchomić pod, nie potrzebny jest harmonogram. Możesz ręcznie dodać węzeł do manifestu w parametrze nodeName:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
nodeName: mink8s
(Zastąp mink8s nazwa węzła.) Po delete i apply widzimy, że nginx uruchomił się i nasłuchuje na wewnętrzny adres IP:
$ ./kubectl delete pod nginx
pod "nginx" usunięty
$ ./kubectl apply -f nginx.yaml
pod/nginx utworzony
$ ./kubectl get pods -owide
NAZWA GOTOWE STATUS RESTARTY WIEK IP WĘZEŁ NOMINOWANY WĘZEŁ BRAMY GOTOWOŚCI
nginx 1/1 Działa 0 30s 172.17.0.2 mink8s <none> <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Witamy w nginx!</title>Aby upewnić się, że sieć między podami działa poprawnie, możemy uruchomić curl z innego 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>Witamy w nginx!</title>Dosyć ciekawie jest grzebać w tym środowisku i sprawdzić, co działa, a co nie. Odkryłem, że ConfigMap i Secret działają zgodnie z oczekiwaniami, a Service i Deployment nie.
Sukces!
Ten post staje się długi, więc zamierzam ogłosić zwycięstwo i powiedzieć, że to wykonalna konfiguracja, którą można nazwać „Kubernetes”. Podsumowując: cztery pliki binarne, pięć opcji wiersza poleceń i „zaledwie” 45 wierszy YAML (nie tak wiele według standardów Kubernetes) i działa nam sporo rzeczy:
- Pody są zarządzane za pomocą standardowego API Kubernetes (z kilkoma hackami)
- Można załadować publiczne obrazy kontenerów i nimi zarządzać
- Pody pozostają aktywne i są automatycznie restartowane
- Sieć między podami w ramach jednego węzła działa całkiem dobrze
- ConfigMap, Secret i najprostsze montowanie magazynów działają jak należy
Ale większość z tego, co czyni Kubernetes naprawdę użytecznym, nadal brakuje, na przykład:
- Planista podów
- Uwierzytelnianie / autoryzacja
- Wiele węzłów
- Sieć usług
- Klastra wewnętrzny DNS
- Kontrolery dla kont usług, wdrożeń, integracji z dostawcami chmurowymi oraz większość innych „fajerwerków”, które przynosi Kubernetes
Co więc faktycznie otrzymaliśmy? API Kubernetes, które działa samo w sobie, jest tak naprawdę tylko platformą do automatyzacji kontenerów. Nie robi zbyt wiele — to zadanie dla różnych kontrolerów i operatorów, którzy wykorzystują API — ale zapewnia spójną środowisko do automatyzacji.
Czytaj więcej:
Źródło: habr.com
