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

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

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

Ако четете това, вероятно сте чували нещо за Kubernetes (и ако не, как се озовахте тук?) Но какво точно е Kubernetes? Това „Оркестрация на индустриални контейнери“? Или „Облачна операционна система“? Какво изобщо означава това?

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

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

Преглед

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

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

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

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

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

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

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

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

Много информативен. Но ако сте запознати с 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 .)

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

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

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

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

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

Стартирайте и т.н

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

Инсталирахме hostNetwork: true. Тази настройка, не е изненадващо, конфигурира etcd да използва хост мрежата вместо вътрешната мрежа на pod (това ще улесни 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 сървъра

Изпълнението на Kubernetes API сървър е още по-лесно. Единственият параметър, който трябва да бъде предаден, е --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 показва, че API на Kubernetes слуша на порт 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, той е по-скоро като „контрол daemon.” Контейнерите, управлявани от 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 pod:

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

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

Можем да заобиколим този проблем, като зададем опцията 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

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

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

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

(Заменете mink8s към името на възела.) След изтриване и прилагане виждаме, че 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". За да обобщим: четири двоични файла, пет параметъра на командния ред и „само" 45 реда YAML (не толкова по стандартите Kubernetes) и имаме доста работещи неща:

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

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

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

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

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

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

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

Добавяне на нов коментар