Минимално жизнеспособен Kubernetes

Преводът на статията е подготвен в очакване на старта на курса „DevOps практики и инструменти“.

Минимално жизнеспособен Kubernetes

Ако четете това, вероятно сте чували за Kubernetes (а ако не, как попаднахте тук?). Но какво всъщност представлява Kubernetes? Това е „Оркестрация на контейнери на индустриално ниво“? Или „Cloud-Native Операционна Система“? Что вообще это значит?

Честно казано, не съм напълно сигурен. Но мисля, че е интересно да се разгледа какво всъщност се случва в Kubernetes под многобройните му слоеве на абстракция. Така че за интерес, нека да видим как изглежда минималният “кластер Kubernetes”. (Това ще бъде много по-просто от Kubernetes The Hard Way.)

Предполагам, че имате основни знания по Kubernetes, Linux и контейнери. Всичко, за което ще говорим тук, е предназначено само за проучване/изучаване, не стартирайте нищо от това в продукция!

Преглед

Kubernetes съдържа много компоненти. Според Wikipedia, архитектурата изглежда по следния начин:

Минимално жизнеспособен Kubernetes

Тук са показани поне осем компонента, но повечето от тях ще игнорираме. Искам да подчертая, че минималното нещо, което може разумно да се нарече Kubernetes, се състои от три основни компонента:

  • kubelet
  • kube-apiserver (който зависи от etcd — неговата база данни)
  • среда за изпълнение на контейнери (в този случай Docker)

Нека да видим какво се казва за всеки от тях в документацията (ру., англ.). Първо kubelet:

Агент, работещ на всеки възел в кластера. Той следи за това, че контейнерите са стартирани в пода.

Звучи достатъчно просто. Какво ще кажете за средата за изпълнение на контейнери? (container runtime)?

Средата за изпълнение на контейнери е програма, предназначена да изпълнява контейнери.

Много информативно. Но ако сте запознати с Docker, трябва да имате представа какво прави той. (Детайлите за разпределение на отговорностите между средата за изпълнение на контейнери и kubelet всъщност са доста тънки и тук няма да се задълбочавам в тях.)

И API-сервер?

Сървърът на API е компонент на управляващата панел на Kubernetes, който представлява API на Kubernetes. API-серверът е клиентската част на управителния панел на Kubernetes.

На всеки, който някога е работил с Kubernetes, му се е налагало да взаимодейства с API, или директно, или чрез kubectl. Това е сърцето на това, което прави Kubernetes Kubernetes — мозъкът, който преобразува планини от YAML, които всички знаем и обичаме (?), в работеща инфраструктура. Очевидно е, че API трябва да присъства в нашата минимална конфигурация.

Предварителни условия

  • Виртуална или физическа машина с Linux и root достъп (ползвам Ubuntu 18.04 на виртуална машина).
  • И това е всичко!

Скучно инсталиране

На машината, която ще използваме, трябва да инсталираме Docker. (Няма да обяснявам подробно как работи Docker и контейнерите; ако се интересувате, има прекрасни статии). Нека просто го инсталираме с помощта на apt:

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

След това трябва да получим бинарните файлове на Kubernetes. Всъщност, за инициализацията на нашия 'кластер' ни трябва само kubelet, тъй като за стартиране на другите сървърни компоненти можем да използваме kubelet. За взаимодействие с нашия кластер след като стартира, ще използваме също и 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

Какво ще се случи, ако просто стартираме kubelet?

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

kubelet трябва да работи от root. Доста логично, тъй като трябва да управлява целия възел. Нека да видим параметрите му:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Уау, колко много опции! За щастие, ще ни потрябват само няколко от тях. Ето един от параметрите, който ни интересува:

--pod-manifest-path string

Път до директорията, съдържаща файловете за статични подове, или път до файл с описание на статични подове. Файловете, започващи с точки, се игнорират. (УСТАРЯЛО: този параметър трябва да се задава в конфигурационния файл, предаван на Kubelet чрез опцията —config. За допълнителна информация вижте. kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Този параметър ни позволява да стартираме статични подове — поди, които не се управляват чрез Kubernetes API. Статичните поди се използват рядко, но са много удобни за бързо развиване на клъстера, а точно това е, от което имаме нужда. Ще игнорираме това силно предупреждение (отново, не го стартирайте в продукция!) и ще видим дали можем да стартираме пода.

Първо, ще създадем директория за статичните поди и ще стартираме kubelet:

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

След това в друг терминал/прозорец tmux/някъде другаде, ще създадем манифест на пода:

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

kubelet започва да пише някакви предупреждения и изглежда, че нищо не се случва. Но това не е така! Нека да погледнем 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 прочете манифеста на пода и даде на Docker команда да стартира няколко контейнера в съответствие с нашата спецификация. (Ако ви интересува какво е контейнерът “pause”, то това е хакерство на Kubernetes — подробности вижте в този блог.) Kubelet ще стартира нашия контейнер busybox с указаната команда и ще го перезапуска безкрайно, докато статичният под не бъде изтрит.

Поздравете се! Току-що измислихме един от най-заплетените начини за извеждане на текст в терминала!

Стартираме etcd

Нашата крайна цел е да стартираме Kubernetes API, но за това първо трябва да стартираме etcd. Нека стартираме минимален клъстер etcd, като поставим настройките му в директория pods (например, 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

Ако някога сте работили с Kubernetes, то подобни YAML файлове трябва да са ви известни. Тук трябва да отбележим само два момента:

Ние монтирахме папка на хоста /var/lib/etcd в пода, за да данните etcd да се съхраняват след рестартиране (ако не го направите, състоянието на клъстера ще бъде изтриваемо при всеки рестарт на пода, което не е добре дори за минимална инсталация на Kubernetes).

Инсталирахме hostNetwork: true. Този параметър, което не е изненада, конфигурира etcd да използва мрежата на хоста вместо вътрешната мрежа на пода (това ще улесни API сървъра да намери клъстера etcd).

Проста проверка показва, че etcd всъщност работи на localhost и съхранява данни на диска:

$ 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

Стартиране на API сървъра

Стартирането на API сървъра на Kubernetes е още по-лесно. Единственият параметър, който трябва да предадете, --etcd-servers, прави точно това, което очаквате:

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

Поставете този YAML файл в директорията pods, и API сървърът ще стартира. Проверка с помощта на curl показва, че Kubernetes API слуша на порт 8080 с напълно отворен достъп — аутентификация не е необходима!

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

(Отново, не стартирайте това в продукция! Бях малко изненадан, че конфигурацията по подразбиране е толкова несигурна. Но предполагам, че е направено, за да се улесни разработката и тестването.)

И, приятно изненадващо, kubectl работи направо от кутията без никакви допълнителни настройки!

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

Проблема

Но ако се копае малко по-дълбоко, изглежда, че нещо не е наред:

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

Статичните подове, които създадохме, изчезнаха! Всъщност нашият kubelet възел изобщо не се открива:

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

Каква е ситуацията? Ако помните, преди няколко абзаца стартирахме kubelet с изключително прост набор от командни параметри, така че kubelet не знае как да се свърже със сървъра на API и да го уведомява за своето състояние. Преглеждайки документацията, намираме съответния флаг:

--kubeconfig string

Път до файла kubeconfig, в който е указано как да се свързвате със сървъра на API. Наличието на --kubeconfig включва режима на API-сървър, неговата липса --kubeconfig включва автономен режим.

През цялото време, без да знаем, сме стартирали kubelet в "автономен режим". (Ако бяхме педантични, можехме да смятаме автономния режим на kubelet за "минимално жизнеспособен Kubernetes", но това щеше да бъде много скучно). За да заработи "истинската" конфигурация, трябва да предадем файла kubeconfig на kubelet, за да знае как да комуникира с API-сървъра. За щастие, това е доста просто (тъй като нямаме проблеми с удостоверяването или сертификатите):

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

Запазете това като kubeconfig.yaml, убийте процеса kubelet и рестартирайте с необходимите параметри:

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

(Между другото, ако се опитате да се свържете с API чрез curl, когато kubelet не работи, ще откриете, че все още работи! Kubelet не е "родител" на своите подове, подобно на Docker, а по-скоро играе ролята на "управляващ демон". Контейнерите, управлявани от kubelet, ще работят, докато kubelet не ги спре.)

След няколко минути kubectl трябва да ни покаже подовете и възлите, както очакваме:

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

Нека този път наистина да се поздравим (знам, че вече го направих) — успяхме да изградим минимален "кластер" Kubernetes, работещ с напълно функционален API!

Стартиране на под

Сега да видим какво може API-то. Нека започнем с пода nginx:

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

Тук ще получим доста интересна грешка:

$ .\/kubectl apply -f nginx.yaml
Грешка от сървъра (Забранено): грешка при създаването на "nginx.yaml": pods "nginx" е
забранен: грешка при търсенето на акаунт за услуга default\/default: serviceaccount
"default" не е намерен
$ .\/kubectl get serviceaccounts
Не са намерени ресурси в основното пространство.

Тук виждаме колко ужасно непълна е нашата среда Kubernetes — нямаме акаунти за услуги. Нека пробваме отново, като създадем акаунт за услуга ръчно, и да видим какво ще се случи:

$ cat <<EOS | .\/kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount\/default създаден
$ .\/kubectl apply -f nginx.yaml
Грешка от сървъра (ServerTimeout): грешка при създаването на "nginx.yaml": Няма API
token намерен за акаунт за услуга "default", опитайте отново след като токенът бъде
aвтоматично създаден и добавен към акаунта за услуга

Дори когато създадохме акаунт за услуга ръчно, токенът за аутентификация не се създава. Продължавайки да експериментираме с нашия минималистичен "кластер", ще открием, че повечето полезни неща, които обикновено се случват автоматично, ще липсват. Kubernetes API сървърът е доста минималистичен, повечето от тежките автоматични настройки се извършват от различни контролери и фонови задачи, които все още не се изпълняват.

Можем да заобиколим този проблем, като настроим опцията automountServiceAccountToken за акаунта за услуга (тъй като така или иначе не ни е нужно да го използваме):

$ cat <<EOS | .\/kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount\/default конфигуриран
$ .\/kubectl apply -f nginx.yaml
pod\/nginx създаден
$ .\/kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0\/1     Очаква   0          13m

Накрая, подът се появи! Но всъщност няма да се стартира, защото нямаме планировчика (scheduler) — още един важен компонент на Kubernetes. Отново виждаме, че API на Kubernetes е изненадващо "глупав" — когато създавате под в API, той го регистрира, но не се опитва да разбере на кой възел да го стартира.

Наистина, за да стартирате пода, планировчикът не е необходим. Можете ръчно да добавите възел в манифеста в параметъра nodeName:

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

(Заменете mink8s с името на възела.) След delete и apply виждаме, че nginx е стартирал и слуша на вътрешния IP адрес:

$ .\/kubectl delete pod nginx
pod "nginx" е изтрит
$ .\/kubectl apply -f nginx.yaml
pod/nginx е създаден
$ .\/kubectl get pods -owide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1\/1     Работи   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Добре дошли в nginx!</title>

За да се уверим, че мрежата между подовете работи правилно, можем да стартираме curl от друг под:

$ котка &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>Добре дошли в nginx!</title>

Доста интересно е да се ровим в тази среда и да видим какво работи и какво не. Открих, че ConfigMap и Secret работят така, както се очаква, а Service и Deployment не.

Успех!

Този пост става дълъг, така че ще обявя победител и ще заявя, че това е жизнеспособна конфигурация, която можем да наречем “Kubernetes”. В обобщение: четири бинарни файла, пет параметъра на командния ред и “само” 45 реда YAML (не толкова много по стандартите на Kubernetes) и имаме работещи множество неща:

  • Подовете се управляват чрез обикновен Kubernetes API (с някои хакове)
  • Може да се зареждат публични образи на контейнери и да се управляват
  • Подовете остават живи и автоматично се перезапускат
  • Мрежата между подовете в рамките на един възел работи доста добре
  • ConfigMap, Secret и най-простото монтиране на хранилища работят както трябва

Но голяма част от това, което прави Kubernetes наистина полезен, все още отсъства, например:

  • Планировчик на подове
  • Аутентикация / авторизация
  • Няколко възела
  • Мрежа на услугите
  • Клъстерен вътрешен DNS
  • Контролери за акаунти на услуги, разгръщания, интеграции с облачни доставчици и повечето други “удобства”, които носи Kubernetes

Така какво наистина получихме? Kubernetes API, който работи сам по себе си, всъщност е само платформа за автоматизация на контейнери. Той не прави много — това е работа за различни контролери и оператори, използващи API, — но предоставя последователна среда за автоматизация.

Научете повече за курса на безплатния уебинар.

Прочетете още:

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster