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

Ако четете това, вероятно сте чували за Kubernetes (а ако не, как попаднахте тук?). Но какво всъщност представлява Kubernetes? Това е ? Или ? Что вообще это значит?
Честно казано, не съм напълно сигурен. Но мисля, че е интересно да се разгледа какво всъщност се случва в Kubernetes под многобройните му слоеве на абстракция. Така че за интерес, нека да видим как изглежда минималният “кластер Kubernetes”. (Това ще бъде много по-просто от .)
Предполагам, че имате основни знания по Kubernetes, Linux и контейнери. Всичко, за което ще говорим тук, е предназначено само за проучване/изучаване, не стартирайте нищо от това в продукция!
Преглед
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 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, като поставим настройките му в директория 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 от друг под:
$ котка <<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
