Szczerze mówiąc, nie jestem pewien na 100%. Myślę jednak, że ciekawie jest zajrzeć do wnętrza i zobaczyć, co naprawdę dzieje się w Kubernetesie pod wieloma warstwami abstrakcji. A więc dla zabawy przyjrzyjmy się, jak faktycznie wygląda minimalny „klaster Kubernetes”. (Będzie to znacznie łatwiejsze niż Kubernetes na własnej skórze.)
Zakładam, że masz podstawową wiedzę na temat Kubernetesa, Linuksa i kontenerów. Wszystko, o czym tutaj mówimy, służy wyłącznie celom badawczym/naukowym, nie wprowadzaj niczego do produkcji!
Przegląd
Kubernetes zawiera wiele komponentów. Według Wikipedia, architektura wygląda następująco:
Pokazano tutaj co najmniej osiem komponentów, ale większość z nich zignorujemy. Chcę stwierdzić, że minimum, które można rozsądnie nazwać Kubernetesem, składa się z trzech głównych elementów:
kubelet
kube-apiserver (który zależy od etcd - jego bazy danych)
środowisko wykonawcze kontenera (w tym przypadku Docker)
Zobaczmy, co dokumentacja mówi o każdym z nich (Rosyjski., Angielski.). Najpierw kubelet:
Agent działający w każdym węźle klastra. Zapewnia, że kontenery działają w zasobniku.
Brzmi dość prosto. Co powiesz na środowiska wykonawcze kontenerów (czas wykonania kontenera)?
Środowisko wykonawcze kontenera to program przeznaczony do uruchamiania kontenerów.
Bardzo informujące. Ale jeśli znasz Dockera, powinieneś mieć ogólne pojęcie o tym, co robi. (Szczegóły podziału odpowiedzialności pomiędzy środowiskiem wykonawczym kontenera a kubeletem są w rzeczywistości dość subtelne i nie będę się nimi tutaj zajmować.)
И Serwer API?
Serwer API to komponent panelu sterowania Kubernetes, który udostępnia interfejs API Kubernetes. Serwer API jest stroną klienta panelu sterowania Kubernetes
Każdy, kto kiedykolwiek robił cokolwiek z Kubernetesem, musiał wchodzić w interakcję z API bezpośrednio lub poprzez kubectl. To jest serce tego, co czyni Kubernetes Kubernetes — mózg, który zamienia 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.
Warunki wstępne
Wirtualna lub fizyczna maszyna z systemem Linux z dostępem roota (używam Ubuntu 18.04 na maszynie wirtualnej).
I to wszystko!
Nudna instalacja
Musimy zainstalować Dockera na maszynie, z której będziemy korzystać. (Nie będę wdawał się w szczegóły działania Dockera i kontenerów; jeśli jesteś zainteresowany, jest to możliwe wspaniałe artykuły). Po prostu zainstalujmy to za pomocą apt:
Następnie musimy zdobyć pliki binarne Kubernetes. Tak naprawdę do pierwszego uruchomienia naszego „klastra” potrzebujemy jedynie kubelet, ponieważ do uruchomienia innych komponentów serwera możemy użyć kubelet. Do interakcji z naszym klastrem po jego uruchomieniu będziemy również używać kubectl.
kubelet musi działać jako root. Całkiem logiczne, ponieważ musi zarządzać całym węzłem. Spójrzmy na jego parametry:
$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284
Wow, tak wiele 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 opisującego statyczne pody. Pliki zaczynające się od kropek są ignorowane. (WYCOFANO: tę opcję należy ustawić w pliku konfiguracyjnym przekazywanym do Kubelet za pomocą opcji --config. Aby uzyskać więcej informacji, zobacz kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)
Ta opcja pozwala nam uruchomić statyczne kapsuły — pody, które nie są zarządzane poprzez Kubernetes API. Pody statyczne są rzadko używane, ale są bardzo wygodne do szybkiego podnoszenia klastra, a właśnie tego potrzebujemy. Zignorujemy to ważne ostrzeżenie (ponownie: nie uruchamiaj tego w środowisku produkcyjnym!) i zobaczmy, czy uda nam się uruchomić pod.
Najpierw utworzymy katalog dla statycznych podów i uruchomimy kubelet:
kubelet zaczyna pisać ostrzeżenia i wygląda na to, że nic się nie dzieje. Ale to nieprawda! Spójrzmy na Dockera:
$ 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 Przeczytałem manifest poda i wydałem Dockerowi polecenie uruchomienia kilku kontenerów zgodnie z naszymi specyfikacjami. (Jeśli zastanawiasz się nad kontenerem „pauzy”, jest to hack Kubernetesa - patrz tego bloga.) Kubelet uruchomi nasz kontener busybox za pomocą określonego polecenia i uruchomi je ponownie na czas nieokreślony, dopóki statyczny pod nie zostanie usunięty.
Pogratuluj sobie. Właśnie wymyśliliśmy jeden z najbardziej zagmatwanych sposobów wysyłania tekstu do terminala!
Uruchom itp
Naszym ostatecznym celem jest uruchomienie interfejsu API Kubernetes, ale aby to zrobić, musimy najpierw uruchomić itd. Uruchommy minimalny klaster etcd, umieszczając jego ustawienia w katalogu pods (na przykład pods/etcd.yaml):
Jeśli kiedykolwiek pracowałeś z Kubernetesem, te pliki YAML powinny być Ci znane. Warto tutaj zwrócić uwagę tylko na dwie kwestie:
Zamontowaliśmy folder hosta /var/lib/etcd w zasobniku, aby dane etcd zostały zachowane po ponownym uruchomieniu (jeśli nie zostanie to zrobione, stan klastra zostanie usunięty przy każdym ponownym uruchomieniu zasobnika, co nie będzie dobre nawet w przypadku minimalnej instalacji Kubernetes).
Zainstalowaliśmy hostNetwork: true. To ustawienie, co nie jest zaskakujące, konfiguruje etcd tak, aby korzystał z sieci hosta zamiast z sieci wewnętrznej modułu (ułatwi to serwerowi API znalezienie klastra etcd).
Proste sprawdzenie pokazuje, że etcd rzeczywiście działa na localhost i zapisuje dane na dysku:
$ 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
Uruchomienie serwera API
Uruchamianie serwera Kubernetes API jest jeszcze łatwiejsze. Jedynym parametrem, który należy przekazać, jest --etcd-servers, robi to, czego oczekujesz:
Umieść ten plik YAML w katalogu pods, a serwer API zostanie uruchomiony. Sprawdzanie z curl pokazuje, że Kubernetes API nasłuchuje na porcie 8080 z całkowicie otwartym dostępem - nie jest wymagane żadne uwierzytelnianie!
(Ponownie nie uruchamiaj tego w środowisku produkcyjnym! Byłem trochę zaskoczony, że ustawienie domyślne jest tak niepewne. Ale domyślam się, że ma to na celu ułatwienie programowania 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
Ale jeśli kopiesz trochę głębiej, wydaje się, że coś idzie nie tak:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.
Statyczne kapsuły, które stworzyliśmy, zniknęły! Tak naprawdę nasz węzeł kubelet w ogóle nie został odkryty:
$ ./kubectl get nodes
No resources found in default namespace.
O co chodzi? Jeśli pamiętasz kilka akapitów temu, uruchomiliśmy kubelet z niezwykle prostym zestawem parametrów wiersza poleceń, więc kubelet nie wie, jak skontaktować się z serwerem API i powiadomić go o swoim stanie. Po przestudiowaniu dokumentacji znajdujemy odpowiednią flagę:
--kubeconfig string
Ścieżka do pliku kubeconfig, który określa sposób łączenia się z serwerem API. Dostępność --kubeconfig włącza tryb serwera API, nie --kubeconfig włącza tryb offline.
Przez cały ten czas nieświadomie uruchamialiśmy kubelet w „trybie offline”. (Gdybyśmy byli pedantyczni, moglibyśmy pomyśleć o samodzielnym kubelecie jako o „minimalnie wykonalnym Kubernetesie”, ale byłoby to bardzo nudne). Aby „prawdziwa” konfiguracja działała, musimy przekazać plik kubeconfig do kubelet, aby wiedział, jak rozmawiać z serwerem API. Na szczęście jest to dość proste (ponieważ nie mamy żadnych problemów z uwierzytelnianiem ani certyfikatami):
(Nawiasem mówiąc, jeśli spróbujesz uzyskać dostęp do API poprzez curl, gdy kubelet nie jest uruchomiony, przekonasz się, że nadal działa! Kubelet nie jest „rodzicem” swoich podów jak Docker, bardziej przypomina „kontrolę” demon.” Kontenery zarządzane przez kubelet będą nadal działać, dopóki kubelet ich nie zatrzyma.)
W ciągu kilku minut kubectl powinien pokazać nam strąki i węzły zgodnie z oczekiwaniami:
$ ./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
Tym razem naprawdę sobie pogratulujmy (wiem, że już sobie pogratulowałem) - mamy minimalny „klaster” Kubernetesa działający z w pełni funkcjonalnym API!
Startujemy pod
Zobaczmy teraz, do czego zdolne jest API. Zacznijmy od kapsuły 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.
Tutaj widzimy, jak żałośnie niekompletne jest nasze środowisko Kubernetes – nie mamy kont dla usług. Spróbujmy ponownie, ręcznie tworząc konto usługi 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 account
Nawet gdy konto usługi utworzyliśmy ręcznie, token uwierzytelniający nie jest generowany. Kontynuując eksperymenty z naszym minimalistycznym „klasterem”, odkryjemy, że będzie brakować większości przydatnych rzeczy, które zwykle dzieją się automatycznie. Serwer Kubernetes API jest dość minimalistyczny, a większość ciężkich prac i automatycznej konfiguracji odbywa się na różnych kontrolerach i zadaniach w tle, które jeszcze nie działają.
Możemy obejść ten problem, ustawiając opcję automountServiceAccountToken dla konta usługi (ponieważ i tak nie będziemy musieli z niego korzystać):
$ 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
W końcu pojawił się kapsuła! Ale tak naprawdę to się nie uruchomi, bo nie mamy planista (harmonogram) to kolejny ważny komponent Kubernetes. Ponownie widzimy, że Kubernetes API jest zaskakująco „głupi” — gdy tworzysz Pod w API, rejestruje go, ale nie próbuje dowiedzieć się, na którym węźle go uruchomić.
Tak naprawdę do uruchomienia poda nie jest potrzebny program planujący. Możesz ręcznie dodać węzeł do manifestu w parametrze nodeName:
(Zastępować mink8s do nazwy węzła.) Po usunięciu i zastosowaniu widzimy, że nginx został uruchomiony i nasłuchuje wewnętrznego adresu 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>
Aby mieć pewność, że sieć pomię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>Welcome to nginx!</title>
To całkiem interesujące zagłębić się w to środowisko i zobaczyć, co działa, a co nie. Odkryłem, że ConfigMap i Secret działają zgodnie z oczekiwaniami, ale usługi i wdrażanie nie.
Powodzenie!
Ten post robi się długi, więc ogłoszę zwycięstwo i powiem, że jest to wykonalna konfiguracja, którą można nazwać „Kubernetes”. Podsumowując: cztery pliki binarne, pięć parametrów wiersza poleceń i „tylko” 45 linii YAML (nie tyle według standardów Kubernetes) i mamy sporo rzeczy, które działają:
Podami zarządza się za pomocą zwykłego API Kubernetes (z kilkoma hackami)
Możesz przesyłać publiczne obrazy kontenerów i zarządzać nimi
Pody pozostają aktywne i automatycznie uruchamiają się ponownie
Sieć między kapsułami w tym samym węźle działa całkiem dobrze
ConfigMap, Secret i prosty montaż pamięci działają zgodnie z oczekiwaniami
Jednak nadal brakuje wielu elementów, które czynią Kubernetes naprawdę użytecznym, takich jak:
Harmonogram podów
Uwierzytelnianie/autoryzacja
Wiele węzłów
Sieć usług
Klastrowany wewnętrzny DNS
Kontrolery do kont usług, wdrożeń, integracji z dostawcami usług chmurowych i większości innych gadżetów, które przynosi Kubernetes
Co więc właściwie dostaliśmy? Działający samodzielnie interfejs API Kubernetes jest tak naprawdę tylko platformą dla automatyzacja kontenerów. Nie robi to wiele – jest to zadanie dla różnych kontrolerów i operatorów korzystających z API – ale zapewnia spójne środowisko dla automatyzacji.