Честно казано, не съм 100% сигурен. Но мисля, че е интересно да се поровим във вътрешността и да видим какво наистина се случва в Kubernetes под многобройните му слоеве от абстракции. Така че просто за забавление, нека да разгледаме как всъщност изглежда минималният „клъстер на Kubernetes“. (Това ще бъде много по-лесно от Kubernetes Трудният начин.)
Предполагам, че имате основни познания за Kubernetes, Linux и контейнери. Всичко, за което говорим тук, е само за изследователски/учебни цели, не поставяйте нищо от това в производство!
Преглед
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:
След това трябва да вземем двоичните файлове на Kubernetes. Всъщност за първоначалното стартиране на нашия „клъстер“ ни трябват само kubelet, тъй като за стартиране на други сървърни компоненти, които можем да използваме kubelet. За да взаимодействаме с нашия клъстер, след като работи, ние също ще използваме kubectl.
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:
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):
Ако някога сте работили с 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, прави това, което очаквате:
Поставете този YAML файл в директорията podsи API сървърът ще стартира. Проверка с curl показва, че API на Kubernetes слуша на порт 8080 с напълно отворен достъп - не се изисква удостоверяване!
(Отново, не стартирайте това в производствена среда! Бях малко изненадан, че настройката по подразбиране е толкова несигурна. Но предполагам, че това е, за да улесни разработката и тестването.)
И, приятна изненада, 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 сървъра. За щастие е доста просто (тъй като нямаме никакви проблеми с удостоверяването или сертификата):
(Между другото, ако се опитате да получите достъп до 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:
$ ./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:
(Заменете 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 - но осигурява последователна среда за автоматизация.