Мінімально життєздатний Kubernetes

Переклад статті підготовлений напередодні старту курсу «DevOps практики та інструменти».

Мінімально життєздатний Kubernetes

Якщо ви це читаєте, ймовірно, ви щось чули про Kubernetes (а якщо ні, то як ви тут опинилися?) Але що ж насправді є Kubernetes? Це "Оркестрація контейнерів промислового рівня"? або "Cloud-Native Operating System"? Що це взагалі означає?

Щиро кажучи, я не впевнений на 100%. Але думаю цікаво покопатися у нутрощах і подивитися, що насправді відбувається в Kubernetes під його багатьма верствами абстракцій. Отже, заради інтересу, давайте подивимося, як насправді виглядає мінімальний “кластер Kubernetes”. (Це буде набагато простіше, ніж Kubernetes The Hard Way.)

Я вважаю, що у вас є базові знання Kubernetes, Linux та контейнерів. Все, про що ми тут говоритимемо, призначене тільки для дослідження/вивчення, не запускайте нічого з цього в продакшені!

Огляд

Kubernetes містить багато компонентів. Згідно вікіпедії, архітектура виглядає так:

Мінімально життєздатний Kubernetes

Тут показано принаймні вісім компонент, але більшість із них ми проігноруємо. Я хочу заявити, що мінімальна річ, яку можна обґрунтовано назвати Kubernetes, складається із трьох основних компонентів:

  • кубелет
  • kube-apiserver (залежний від etcd — його бази даних)
  • середовище виконання контейнера (у цьому випадку Docker)

Давайте подивимося, що про кожного з них йдеться у документації (рус., англ.). Спочатку кубелет:

Агент працює на кожному вузлі в кластері. Він слідкує за тим, щоб контейнери були запущені у поді.

Звучить досить легко. Що щодо середовища виконання контейнерів (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 <<EOF > 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, помістивши його налаштування в каталог 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    <none>   21h   v1.18.5   10.70.10.228   <none>        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
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.

Тут ми бачимо, наскільки жахливо неповне наше середовище Kubernetes — у нас немає облікових записів для служб. Давайте спробуємо ще раз, створивши обліковий запис служби вручну, і подивимося, що станеться:

$ 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

Навіть коли ми створили обліковий запис служби вручну, токен автентифікації не створюється. Продовжуючи експериментувати з нашим мінімалістичним «кластером», ми виявимо, що більшість корисних речей, які зазвичай відбуваються автоматично, не будуть. Сервер Kubernetes API досить мінімальний, більшість важких автоматичних налаштувань відбувається в різних контролерах і фонових завданнях, які ще не виконуються.

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

$ 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

Нарешті, з'явився! Але насправді він не запуститься, тому що у нас немає планувальника (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" 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>

Щоб переконатися, що мережа між подами працює коректно, ми можемо запустити curl з іншого пода:

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

Досить цікаво покопатися у цьому оточенні та подивитися, що працює, а що ні. Я виявив, що ConfigMap та Secret працюють так, як і очікується, а Service та Deployment немає.

Успіх!

Цей пост стає великим, тому я збираюся оголосити про перемогу і заявити, що це життєздатна конфігурація, яку можна назвати “Kubernetes”. Kubernetes) і у нас працює чимало речей:

  • Піди управляються за допомогою звичайного Kubernetes API (з кількома хаками)
  • Можна завантажувати публічні образи контейнерів та керувати ними
  • Піди залишаються живими та автоматично перезапускаються
  • Мережа між подами в рамках одного вузла працює досить добре
  • ConfigMap, Secret та найпростіше монтування сховищ працює як належить

Але більша частина того, що робить Kubernetes по-справжньому корисним, все ще відсутня, наприклад:

  • Планувальник подов
  • Аутентифікація / авторизація
  • Декілька вузлів
  • Мережа сервісів
  • Кластерний внутрішній DNS
  • Контролери для облікових записів служб, розгортань, інтеграції з хмарними провайдерами та більшість інших "плюшок", які приносить Kubernetes

То що ми насправді отримали? Kubernetes API, що працює сам по собі, насправді, є лише платформою для автоматизації контейнерів. Він не робить багато – це робота для різних контролерів та операторів, які використовують API, – але він забезпечує консистентне середовище для автоматизації.

Дізнатись докладніше про курс на безкоштовному вебінарі.

Читати ще:

Джерело: habr.com

Додати коментар або відгук