Как устроен Kubernetes
Написав статью о создании локального кластера, пришла мысль о том, что стоит описать как пользоваться k8s, как он работает, из чего состоит
Kubernetes (далее k8s) предназначен для контейнезированных приложений, так же предназначен и для:
- Автоматизации развертывания
- Автоматизации масштабирования
- Автоматизации управления
Но крутить k8s все же придется. Без основ знаний Docker-контейнеризации разбираться с k8s, смысла его изучать нет. Лучше перед тем как приступить к работе с k8s, хорошо-бы знать основы докера. Почему надо знать Docker? k8s - аркестрирует докер контейнеры, и сам докер является основной, на котором строится k8s
Основным объектом k8s является - Сluster (кластер), то есть когда говорят именно о k8s, всегда подразумевается кластер
Сам кластер состоит из Nodes (нод), вы всегда создаете Cluster состоящий из Nodes
Существует два типа нод
- Worker Node - это сервер на котором запускаются и работают ваши контейнеры
- Master Node - это сервер, который управляет Worker Nodes
Количество master и worker nodes может быть больше чем один. Зачастую кластер поднимают с одним мастером и множеством воркер нод, но для отказоустойчивости, k8s может реплицировать master node в большом количестве
Все основные команды, которые инициализируют создание ваших рабочих контейнеров, вы посылаете на master node
Master Node - требуется для контроля всего кластера, внутри этой ноды работает 3 главный процесса
- kube-apiserver - необходим для управления worker nodes
- kube-controller-manager - наблюдает за общим состоянием кластера
- kube-scheduler - отвечает за назначение подов узлам
Worker Node - на нем работает 2 главных процесса
- kublet - общается с kube-apiserver master node
- kube-proxy - сетевой “интерфейс” каждого worker node
Kubernetes cluster
Для работы кластера, требуется всегда минимум 1 единица каждого типа ноды, то есть, чтобы запустить кластер, нужен один Master Node, и один Worker Node
Вот пример схемы обычного кластера k8s, где имеются множество Worker Nodes, которые можно впоследствии работы добавлять в кластер, тем самым увеличивая мощность своего кластера, и как минимум одна Master Node
Так же появился новый объект “Docker Image Registry” - репозиторий, где вы храните свои контейнеры, это может быть свой личный репозиторий или например Docker Hub. Мастер нода собирает контейнеры и разливает их по воркерам
Главные объекты Kubernetes
Чтобы понимать работу k8s, нужно знать из чего он состоит
Container - Docker контейнер не состоит в кластере и не является объектом k8s, но контейнер создается. Так же необходимо учитывать принцип работы и создание контейнеров, хоть он и не является объектом. Вот такой интересный факт
Pod - самый простой объект в k8s, Pod состоит из одного или множества контейнеров. То есть, мы не запускаем контейнеры в кластере, а запускаем именно Pod
Deployments - Deployments состоит из одинаковых Pod, получается такая “матрешка”, одно состоит из другого
Service - дает доступ нам доступ к Pods, которые работают в Deployments
Nodes - ноды, это уже серверы, которые является объектами нашего кластера
Cluster - и сам кластер, совокупность наших серверов
Есть еще множество объектов k8s, которые думаю можно опустить, потому как без них k8s может существовать, но сейчас мы выделили самые главные
Рассмотрим объекты на схеме ниже
У нас есть k8s кластер, в котором есть Master, и например 2 Worker сервера
Самый простой и самый маленький объект, который мы можем запустить в кластере - это Pod. На схеме указан, как Web1 и MyApp, которые состоят их контейнеров, указан на схеме WebServer и TG Bot
Запуская Pod, мы указываем какой Image/Container будет использован в нем. В каждом Pod запускается неограниченное количество контейнеров, но хорошей практикой считается, что на один Pod запускается один контейнер. Плохой пример на схеме - это Pod MyApp в нем будет и Web-сервер, и например Telegram бот.
Почему, запускать в одном Pod более одного контейнера - плохо?
Представим, что у нас не тестовая, а уже боевая среда, и нам нужно сделать Autoscaling нашего приложения, например нагрузка поднялась.
Пришло много пользователей на ваш Web-сервер, который находится в Pod MyApp. Нам необходимо поднять еще один контейнер, для распределения нагрузки
Когда у нас несколько контейнеров в одном Pod, то при автомасштабировании (Autoscaling), поднимется еще один Pod, внутри которого будет еще 2 таких же одинаковых контейнера
Это приведет к лишнему потреблению ресурсов кластера, получается мы получаем один лишний контейнер TG Bot, который нам в случае нагрузки, не нужен
Автомасштабирование само по себе не работает, его необходимо определять заранее, и подготовить кластер для этого.
Чтобы автомасштабирование работало, для этого нам необходим Deployments, который по сути объединяет Pod’ы.
То есть Deployments создает копии Pod. Обычно отдельно Pod в одиночку не запускают, только для теста.
В основном запускают на кластере именно Deployments, указывают в нем, сколько копий Pod минимум будет работать, какие контейнеры/образы будут использоваться.
Так же Deployments, служит для обновления версий ваших контейнеров.
Deployments довольно умный и он видит, сколько у вас серверов в кластере и раскидывает новые Pod на самом лучшем сервере в текущий момент, и старается довольно грамонто распределить нагрузку на воркер-сервера.
Services предоставляет доступ к вашему приложению, если говорить с точки зрения объектов кластера, то Service подключается к нужному нам Deployments - конечным итогом может быть сайт, порт, API-сервер и т.д.
Ну и еще раз пройдемся по объектам, учитывая все выше сказанное
Pod - объект в котором находится контейнер
Deployments - шаблон одинаковых Pods, который занимается Autoscaling, обновлением без даун-тайма, держит минимальное количество рабочих Pods
Service - предоставляет доступ к Deployments через, Cluster IP, NodePort, LoadBalancer, ExternalName (DNS)
Nodes - сервера на которых крутятся контейнеры
Cluster - логическое объединение всех Nodes
Pods
Разберем команды для работы с Pods
kubectl get pods
- покажет какие pods сейчас запущены в кластере
kubectl run web-test --image=nginx:latest --port=80
- запустим один Pod в нашем кластере.
web-test - это имя нашего pod
–image=nginx:latest - Docker image container, который я взял с Docker Hub
–port - порт на котором будет работать наш pod
kubectl get pods
- запрашиваем повторно поды кластера и получаем вывод о статусе пода
NAME READY STATUS RESTARTS AGE
web-test 1/1 Running 0 3m11s
Чтобы увидеть более подробную информацию, где запущен Pod, выполним
kubectl describe pods web-test
Подобный вывод даст нам инфу, о том, где запущен Pod, когда он был запущен, внутренний IP кластера, и т.д.
Name: web-test
Namespace: default
Priority: 0
Service Account: default
Node: k8s-node-02/192.168.99.56
Start Time: Mon, 29 May 2023 10:54:22 +0000
Labels: run=web-test
Annotations: <none>
Status: Running
IP: 10.244.2.16
IPs:
IP: 10.244.2.16
Containers:
web-test:
Container ID: containerd://a9daa9c65ba0974c43e0a02c004b1dfa3db3ffc52e7ec981b40f0499164cfc9f
Image: nginx:latest
Image ID: docker.io/library/nginx@sha256:af296b188c7b7df99ba960ca614439c99cb7cf252ed7bbc23e90cfda59092305
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Mon, 29 May 2023 10:57:26 +0000
Last State: Terminated
Reason: Completed
Exit Code: 0
Started: Mon, 29 May 2023 10:55:51 +0000
Finished: Mon, 29 May 2023 10:56:54 +0000
Ready: True
Restart Count: 3
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-hzrhd (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-hzrhd:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 4m40s default-scheduler Successfully assigned default/web-test to k8s-node-02
Normal Pulled 4m38s kubelet Successfully pulled image "nginx:latest" in 1.225488709s (1.225540089s including waiting)
Normal Pulled 4m35s kubelet Successfully pulled image "nginx:latest" in 1.203974653s (1.203980823s including waiting)
Normal Created 3m12s (x3 over 4m38s) kubelet Created container web-test
Normal Pulled 3m12s kubelet Successfully pulled image "nginx:latest" in 1.235603358s (1.235662318s including waiting)
Normal Started 3m11s (x3 over 4m38s) kubelet Started container web-test
Normal Killing 2m8s (x3 over 4m38s) kubelet Stopping container web-test
Normal SandboxChanged 2m7s (x3 over 4m37s) kubelet Pod sandbox changed, it will be killed and re-created.
Warning BackOff 111s (x5 over 3m26s) kubelet Back-off restarting failed container web-test in pod web-test_default(139f68e9-4105-4ad3-9d7e-4a36dc31cb99)
Normal Pulling 98s (x4 over 4m40s) kubelet Pulling image "nginx:latest"
Normal Pulled 97s kubelet Successfully pulled image "nginx:latest" in 1.248218462s (1.248230332s including waiting)
Если требуется выполнить команду внутри контейнера, то можно ее вызвать вот так
kubectl exec web-test date
Так же можно попасть в сам контейнер, с помощью интерактивного режима
kubectl exec -ti web-test sh
Логи Pod можно смотреть вот так
kubectl logs web-test
И логи будут довольно маленькие, это нормально
Чтобы получить доступ к Pod локально, можно пробросить порт
kubectl port-forward web-test 7788:80
И удалить Pod можно вот этой командой
kubectl delete pods web-test
Ручками запускать Pod мы научились. А что если его надо запускать иногда, но команды писать, не хочется, да и запоминать надо. Тут придут на помощь манифест файлы. Такие файлы пишутся в yaml-формате. И опишем, просто запуск nginx в нем. Имя файла я дам web-test.yaml
apiVersion : v1
kind: Pod # что мы создаем, это Pod
metadata:
name: web-test # имя Pod
spec: # от сюда уже описывается контейнер, который мы хотим запустить в Pod
containers:
- name : container-nginx # имя контейнера
image: nginx:latest # image, который мы будем использовать
ports: # порты которые нужно запустить
- containerPort: 80
Сохранили файл, комментарии можно не сохранять, я их описал для понимания того, что мы делаем
Запускаем kubectl apply -f ./web-test.yaml
. Это все тоже что мы и делали руками, но только в файле
Удалить, то, что мы запускали из этого файла можно вот так kubectl delete -f ./web-test.yaml
. Если у вас в кластере, таких манифест файлов много, то данная команда распарсит файл и удалит именно те Pod, которые описаны в этом файле, не больше и не меньше
Приведу еще один пример манифест файла, в котором есть сразу 2 контейнера, на одном Pod. Мы помним, что это плохая практика, но случаи бывают разные, так же разберем, еще новые параметры
apiVersion : v1
kind: Pod
metadata:
name: MyApp
labels:
env: prod
app: main
tier: frontend
owner: IvanIvanov
spec:
containers:
- name : nginx-web
image: nginx:latest
ports:
- containerPort: 80
- name : tg-send
image: tomcat:8.5
ports:
- containerPort: 4444
И таким образом можно добавлять большое количество контейнеров в Pod, ну и labels - нужны, чтобы передавать ENV-переменные, некоторое описание, для чего данный Pod запускается и прочее. Еще labels всегда рекомендую заполнять, потому как k8s внутри себя обрабатывает эти данные и связывает объекты с labes. Это очень помогает понять в работе, какой Deployments или Service, для чего запущен, чтобы не перепутать и случайно не удалить ту часть кластера, что трогать было не нужно.
Ну и еще с помощью манифест файлов можно обновлять контейнеры внутри Pod, tg-send контейнер, например обновился с 8.5 до 8.5.89. В манифесте, заменяем версию и еще раз выполняем kubectl apply -f ./web-test.yaml
, ну и откатываться можно по этой же логике. Самое главное, что Pod при обновлении контейнеров не будет уничтожен, он будет работать. nginx-web не заденет, а вот tg-send стянется с репозитория новая версия и пересоздаст контейнер с ней
tg-send и tomcat в данном примере были взяты только как пример, это не боевой манифест
kubectl describe pods web-test
- и если мы еще раз выполним эту команду, то увидим, что появились наши labels
Попробуйте поднять 2 Pod, с разным количеством контейнеров и получить к ним доступ через port-forward
Если уронить один из Node кластера, на котором запущены Pods, поднимутся ли Pods на втором Node? Нет, потому что нет Autoscaling, для этого и был создан Deployments
Deployments
Перед началом работы, стоит удалить все Pods, которые мы создавали выше. И начнем с создания нового Deploymet из терминала
kubectl create deployment web-test --image=nginx:latest --port=80
Получаем статус по Deployments в нашем кластере
kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
web-test 1/1 1 1 91s
Посмотрим Pods kubectl get pods
NAME READY STATUS RESTARTS AGE
web-test-869f499b74-rbxjp 1/1 Running 1 (25s ago) 95s
Давайте посмотрим, что нам покажет более подробная инфа о созданном Pod
kubectl describe pods web-test-869f499b74-rbxjp
Name: web-test-869f499b74-rbxjp
Namespace: default
Priority: 0
Service Account: default
Node: k8s-node-02/192.168.99.56
Start Time: Mon, 29 May 2023 14:00:32 +0000
Labels: app=web-test
pod-template-hash=869f499b74
Annotations: <none>
Status: Running
IP: 10.244.2.26
IPs:
IP: 10.244.2.26
Controlled By: ReplicaSet/web-test-869f499b74
Containers:
nginx:
Container ID: containerd://5bea31593912c0d7466fb43479b3242c94a44ec148e356167cbc31ed02fe0314
Image: nginx:latest
Image ID: docker.io/library/nginx@sha256:af296b188c7b7df99ba960ca614439c99cb7cf252ed7bbc23e90cfda59092305
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Mon, 29 May 2023 14:03:11 +0000
Last State: Terminated
Reason: Completed
Exit Code: 0
Started: Mon, 29 May 2023 14:01:45 +0000
Finished: Mon, 29 May 2023 14:02:55 +0000
Ready: True
Restart Count: 2
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bj7jq (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-bj7jq:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 4m11s default-scheduler Successfully assigned default/web-test-869f499b74-rbxjp to k8s-node-02
Normal Pulled 4m9s kubelet Successfully pulled image "nginx:latest" in 1.204357661s (1.204405051s including waiting)
Normal Pulled 2m58s kubelet Successfully pulled image "nginx:latest" in 1.213935935s (1.213944135s including waiting)
Normal Killing 108s (x2 over 3m1s) kubelet Stopping container nginx
Normal SandboxChanged 107s (x2 over 3m) kubelet Pod sandbox changed, it will be killed and re-created.
Warning BackOff 106s (x2 over 107s) kubelet Back-off restarting failed container nginx in pod web-test-869f499b74-rbxjp_default(f1cf8095-2a76-424f-a672-56a627406afe)
Normal Pulling 94s (x3 over 4m10s) kubelet Pulling image "nginx:latest"
Normal Created 93s (x3 over 4m9s) kubelet Created container nginx
Normal Pulled 93s kubelet Successfully pulled image "nginx:latest" in 1.236149903s (1.236157173s including waiting)
Normal Started 92s (x3 over 4m9s) kubelet Started container nginx
Много чего не поменялось, а если подробно глянем на сам Deploy
kubectl describe deployments web-test
Name: web-test
Namespace: default
CreationTimestamp: Mon, 29 May 2023 14:00:32 +0000
Labels: app=web-test
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=web-test
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=web-test
Containers:
nginx:
Image: nginx:latest
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Progressing True NewReplicaSetAvailable
Available True MinimumReplicasAvailable
OldReplicaSets: <none>
NewReplicaSet: web-test-869f499b74 (1/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 6m49s deployment-controller Scaled up replica set web-test-869f499b74 to 1
Тут можно увидеть, что Deploy уже указал свои labels, количество реплик, стратегию обновления, указывает количество контейнеров и т.д. Сейчас создадим Scaling нашего Deployment, то есть мы будем запускать несколько Pods
kubectl scale deployment web-test --replicas 2
Теперь если запросить количество Pods kubectl get pods
NAME READY STATUS RESTARTS AGE
web-test-869f499b74-hhlw5 1/1 Running 2 (98s ago) 3m5s
web-test-869f499b74-rbxjp 1/1 Running 5 (2m27s ago) 13m
kubectl get deploy
- смотрим на наш Deploy
NAME READY UP-TO-DATE AVAILABLE AGE
web-test 1/2 2 1 14m
Все, репликация работает. И у нас появился новый объект ReplicaSet, после того, как мы объявили новый Deployment в кластере
kubectl get rs
NAME DESIRED CURRENT READY AGE
web-test-869f499b74 2 2 2 16m
Теперь мы можем удалить один Pod и он автоматически поднимется
kubectl delete pods web-test-869f499b74-hhlw5
Затем снова смотрим на Pods kubectl get pods
NAME READY STATUS RESTARTS AGE
web-test-869f499b74-dgnqn 1/1 Running 0 6s
web-test-869f499b74-rbxjp 1/1 Running 6 (4m52s ago) 19m
Как можно заметить, Pod поднялся автоматически, и мгновенно, если бы у нас было больше серверов в кластере, то можно полностью убить сервер, Deployments автоматом тут же поднимет еще Pods на других доступных Nodes
Хорошо, мы научились делать Scaling, всегда будет работать 2 Pods. Научимся делать AutoScale
kubectl autoscale deployment web-test --min=1 --max=2 --cpu-percent=80
- и создается еще один объект HPA (Horizontal Pod Autoscaling), минимальное и максимальное количество Pods думаю понятно, а вот –cpu-percent, определяется в процентах. Когда процессор будет утилизироваться, на более чем 80%, то HPA будет поднимать еще Pods на других Nodes, чтобы распределить нагрузку.
kubectl get hpa
- получим статут нашего HPA
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
web-test Deployment/web-test 2%/80% 1 2 2 14m
Перед тем, как обновлять можем посмотреть историю обновлений нашего Deployment
kubectl rollout history deployment/web-test
deployment.apps/web-test
REVISION CHANGE-CAUSE
1 <none>
Тут будут храниться команды, которыми вызываются обновления контейнеров. Сейчас попробуем обновить и все станет понятно.
kubectl rollout status deployment/web-test
- а вот тут можно смотреть за статусом обновлений
Перед обновлением, нам нужно взять точное название контейнера, из Deployment
kubectl descriube deployment web-test
Name: web-test
Namespace: default
CreationTimestamp: Mon, 29 May 2023 14:00:32 +0000
Labels: app=web-test
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=web-test
Replicas: 2 desired | 2 updated | 2 total | 1 available | 1 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=web-test
Containers:
- nginx:
Image: nginx:latest
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Progressing True NewReplicaSetAvailable
Available False MinimumReplicasUnavailable
OldReplicaSets: <none>
NewReplicaSet: web-test-869f499b74 (2/2 replicas created)
Events: <none>
Красным выделено то, что нам нужно из этого вывода. Это именно имя контейнера в нашем Deployment
kubectl set image deployment/web-test nginx=nginx:latest
мы запускаем обновление контейнера
kubectl rollout status deployment/web-test
ждем ответ
deployment "web-test" successfully rolled out
как только мы получаем эту строку, завершается обновление контейнеров в Pods
Теперь обратимся к истории обновлений kubectl rollout history deployment/web-test
, тут будут сохраняться истории команды обновления
REVISION CHANGE-CAUSE
1 kubectl set image deployment/web-test nginx=nginx:latest
Предположим, что у вас обновление завалилось и нужно откатиться
kubectl rollout undo deployment/web-test
- эта команда повернет на предыдущую версию
kubectl rollout undo deployment/web-test --to-revision=2
- эта команда повернет на ту версию, которая была записана в истории
Если в продакшене вы всегда будете указывать вместо версии, latest, и хотите обновляться каждый раз на latest версию, то можно использовать такую команду
kubectl rollout restart deployment/web-test
Теперь поучимся писать манифест файлы. Создадим файл deployment-web-test.yaml
apiVersion : apps/v1
kind: Deployment # инициализируем Deployment
metadata:
name: web-test-deployment-autoscaling # навзвание нашего деплоймента
labels:
app: web-test-deployment-autoscaling
env: prod
owner: IvanIvanov
spec:
selector:
matchLabels: # описываем с какими Pods, нащ Deployment будет работать
project: web-site
template: # далее описываем наши Pods для Deployment
metadata:
labels: # данные labels, описываются для подов
project: web-site
spec: # описываем контейнеры
containers:
- name: web-test
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler # инициализируем HPA
metadata:
name: web-test-autoscaling
spec:
scaleTargetRef: # описываем для какого Deployment описывается HPA
apiVersion: apps/v1
kind: Deployment
name: web-test-deployment-autoscaling
minReplicas: 1 # минимально количество Pods
maxReplicas: 2 # максимальное количество Pods
metrics: # по каким параметрам будет выполняется HPA
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Ну и получить информацию по запущенным Pods можно с помощью тех команд, которые мы уже изучили. Изменяя версии контейнеров в файлах, мы можем их обновлять
Services
Переходим к Services, тут будет все проще, потому как мы будем получать доступ к нашим контейнерам. На данный момент существует 4 вида сервисов
- ClusterIP - создается всегда, и если параметр доступа не обозначен, то ClusterIP будет использоваться по-умолчанию. Работает только внутри кластера
- NodePort - определенный порт, на всех k8s Worker Nodes будет данный порт открыт для доступа к приложению
- ExternalName - DNS CNAME запись, обычно используется в облаках (AWS, Azure и т.д.)
- LoadBalancer - Доступен только в облачных кластерах (AWS, Azure и т.д.)
Перед началом работы требуется создать Deployment
kubectl create deployment web-test --image nginx:latest
Создадим реплики
kubectl scale deployment web-test --replicas 2
И посмотрим, запустилось ли
kubectl get pods
Контейнеры запущены, теперь попробуем получить доступ к ним через ClusterIP
kubectl expose deployment web-test --type=ClusterIP --port 80
kubectl get services
или kubectl get svc
- получим статус по Services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d
web-test ClusterIP 10.102.199.240 <none> 80/TCP 15s
kubernetes - работает по-умолчанию
web-test - и тот, что мы создали
Внутри кластера (Master или Worker Nodes) мы можем выполнить команду
curl http://10.102.199.240
и получим дефолтную страницу Nginx, при повторном обращении на этот же IP, k8s может забрасывать вас на разные Worker Nodes, потому как автоматически включается балансировка нагрузки
Теперь удалим наш созданный Service, но Deployment при этом не затрагивается
kubectl delete service web-test
Создадим теперь NodePort
kubectl expose deployment web-test --type=NodePort --port 80
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d
web-test NodePort 10.104.85.34 <none> 80:31405/TCP 10s
Как видно из вывода, у нас есть и ClusterIP, потому как он работает всегда и появился порт 80:31405/TCP. На всех Nodes кластера был создан порт 31405, к которому можно обратиться и получить доступ к нашему приложению. Даже можно получить доступ через ноды, на которых не крутится Pods, можно даже обратиться через Master
kubectl describe nodes | grep "IP"
- можно будет отфильтровать вывод, чтобы получить InternalIP и ExternalIP, для доступа. ExternalIP может и не появится, все зависит от того, облачный k8s или нет
ExternalName - создавать можно тока через создание CNAME записи для домена, в нашем случае это не будет работать, потому как у нас была локальная установка
LoadBalancer - так же как и ExternalName не подходит для нашего теста
Поучимся создавать манифест файл, с созданием Pods, Deployments, Services и т.д. Файл назовем web-test.yaml
apiVersion : apps/v1
kind: Deployment
metadata:
name: web-test-deployment-autoscaling
labels:
app: web-test-deployment
env: prod
owner: IvanIvanov
spec:
selector:
matchLabels:
project: MyApp
template:
metadata:
labels:
project: MyApp # Сервис будет искать эти метки, они важны
spec:
containers:
- name: web-test
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-test-autoscaling
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-test-deployment-autoscaling
minReplicas: 1
maxReplicas: 2
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
---
apiVersion: v1
kind: Service # инициализируем Service
metadata:
name: my-autoscaling-pod-service # даем ему название
labels: # задаем ему labels
env: prod
owner: IvanIvanov
spec:
selector:
project: MyApp # выбираем с какими Pods будет работать наш Service
ports: # указываем порты
- name: app-listener
protocol: TCP
port: 80
targetPort: 80
type: NodePort # указываем тип Service
Ingress
До этого момента мы создавали кластер без Ingress контроллера. Он служит для того, чтобы создавать один Service, который будет принимать на себя трафик и уже с помощью LoadBalancer отправлять конечного пользователя на нужный внутренний Service на ClusterIP, и уже в конечном итоге трафик пользователя дойдет до нужного Pod
Данный тип контроллера предназначен для экономии средств. Создав одну точку входа в виде одного контроллера для вашего продакшена. Пример построения такой схемы можно посмотреть ниже
Для Service ingress можно подключить множество доменов/поддоменов, которые будут привязаны к вашему Services ClusterIP и даже можно привязывать к отдельным страницам вашего домена, что значительно упрощает жизнь. Ingress Controller проверяет свои правила, и направляет пользователя на нужное приложение, эти правила мы как раз и создаем Ingress Rules. В целом это вся задача Ingress, которую он должен выполнять.
Ingress контроллеров довольно много, и выбрать нужный потребуется от задач, которые стоят перед вами. Есть хорошая большая табличка, которая описывает большинство Ingress контроллеров.
Попробуем для примера установить Ingress Contour. Установка очень простая и одной командой
Переходим на сайт Contour и находим команду запуска
kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
Можно проверить, завершилась ли установка
kubectl get pods -n projectcontour -o wide
Попробуем задеплоить пустой Nginx, и подключим его в нашему Ingress для примера
kubectl create deployment main --image=nginx:latest --port=80
kubectl create deployment web-test --image=tomcat:latest --port=8080
Создаем scaling
kubectl scale deployment main --replicas 2
kubectl scale deployment web-test --replicas 2
Дождемся окончания создания подов
kubectl get pods
Теперь нам нужно создать Services с ClusterIP
kubectl expose deployment main --port=80
kubectl expose deployment web-test --port=8080
kubectl get services -o wide
И все готово для подключения к Ingress. Создавать подобные правила лучше всего через манифест-файл, дадим ему имя ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-paths
spec:
rules:
- host: domain.ru
http:
paths:
- path: "/page1"
backend:
serviceName: main
servicePort: 80
- path: "/page2"
backend:
serviceName: web-test
servicePort: 80
- host: www.domain.ru
http:
paths:
- backend:
serviceName: main
servicePort: 80
kubectl apply -f ingress.yaml
- применяем правила для Ingress
Состояние Ingress контроллера можно смотреть вот этими командами
kubectl get ingress && kubectl describe ingress
Ну и чтобы удалить полностью Ingress Controller Contour
kubectl delete ns projectcontour
Helm
Представим наш k8s кластер и мы хотим задеплоить приложение которое описано в манифест файлах например
~/k8s/AppMainProd/service.yaml
~/k8s/AppMainProd/deployment.yaml
На деле одно приложение может содержать не два файла, то есть не два объекта k8s, а значительно больше
Деплоим мы его вот такими командами
kubectl apply -f ~/k8s/AppMainProd/deployment.yaml
kubectl apply -f ~/k8s/AppMainProd/service.yaml
Предположим, что это приложение нужно поднять еще раз, только уже с другими параметрами, например создать Dev-облако, для разработчиков. Мы копируем папку ~/k8s/AppMainProd
и называем ее ~/k8s/AppMainDev
, а содержимое переписываем под новые условия использования например так
kubectl apply -f ~/k8s/AppMainDev/deployment.yaml
kubectl apply -f ~/k8s/AppMainDev/service.yaml
В голове откладываем, что таких файлов можно быть не два, а значительно больше. Появляется еще одна ситуация, когда необходимо еще раз зедеплоить это же приложение. И снова мы занимаемся копированием файликов. Уже складывается какая-то не хорошая тенденция, мы плодим сущности, в больших количествах, приходит еще одно изменение, которое необходимо будет редактировать во всех файлах этого приложения.
Чтобы не допускать такой ситуации и создали Helm. Наше приложение можно упаковать в Helm Chart
~/k8s/AppMain-HelmChart/templates/service.yaml
~/k8s/AppMain-HelmChart/templates/deployment.yaml
~/k8s/AppMain-HelmChart/Chart.yaml
~/k8s/AppMain-HelmChart/values.yaml
Как можно увидеть наши файлы service.yaml
и deployment.yaml
помещаются в папку templates - то есть в нее мы складываем все наши объекты кластера k8s
Файл values.yaml
в этом файле указываем для значения по умолчанию на разные переменные, при создании кластера, через Helm, templates/service.yaml
и остальные файлы будут обращаться к values.yaml
Файл Chart.yaml
“технический” файл в котором мы будем указывать инфу о данном чарте
Вот и все, сейчас ниже будут содержимое каждого файла для Helm Chart
Файл AppMain-HelmChart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-service
labels:
env : prod
owner: IvanIvanov
spec:
selector:
project: {{ .Release.Name }} # Selecting PODS with those Labels
ports:
- name : {{ .Release.Name }}-listener
protocol : TCP
port : 80 # Port on Load Balancer
targetPort: 80 # Port on POD
type: LoadBalancer
Файл AppMain-HelmChart/templates/deployment.yaml
apiVersion : apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-deployment
labels:
app : {{ .Release.Name }}-deployment
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
project: {{ .Release.Name }}
template:
metadata:
labels:
project: {{ .Release.Name }} # Service will look for those PODS Labels!!!
spec:
containers:
- name : {{ .Release.Name }}-web
image: {{ .Values.container.image }}
ports:
- containerPort: 80
Файл AppMain-HelmChart/Chart.yaml
apiVersion: v2
name : App-HelmChart
description: My Helm chart for Kubernetes
type : application
version : 0.1.0 # This is the Helm Chart version
appVersion : "1.2.3" # This is the version of the application being deployed
keywords:
- apache
- http
- https
maintainers:
- name : Ivan Ivanov
email: [email protected]
url : ivaaan.ru
Файл AppMain-HelmChart/values.yaml
# Default Values for my Helm Chart
container:
image: nginx:latest
replicaCount: 2
Разберем переменные файла AppMain-HelmChart/templates/deployment.yaml
...
metadata:
name: {{ .Release.Name }}-deployment
labels:
app : {{ .Release.Name }}-deployment
...
.Release.Name - в Helm есть переменная Release.Name, которую Helm создаст автоматически, чтобы при повторном деплое у вас не рушилась первая версия приложения
...
spec:
replicas: {{ .Values.replicaCount }}
selector:
...
.Values.replicaCount - данная переменная берется из файла AppMain-HelmChart/values.yaml
, значение которого будет равно 2 (обратите внимание на файл values.yaml и все станет понятно)
...
image: {{ .Values.container.image }}
...
.Values.container.image - Docker Image, который будет использоваться во время деплоя, так же будет взят из файла AppMain-HelmChart/values.yaml
Деплоится вся эта конструкция вот такой командой
helm install App AppMain-HelmChart/
- helm install [название чарта] [папка]
helm install App AppMain-HelmChart/ -f prod_values.yaml
- -f prod_values.yaml
- содержит переменные для Prod
helm install App AppMain-HelmChart/ --set container.image=apache:latest
- сменить переменную в файле values.yaml
не редактируя его и таких переменных может быть больше
Переходим на сайт Helm и находим команду установки. На деле это один батник. Helm можно установить вам на ПК, с которого вы управляете кластером
Можно вызнать команду helm create Project
- создаст пустой Chart, где можно будет посмотреть дефолтную структуру файлов и много полезного)
Посмотреть, что было задеплоино через Helm можно вот так
helm list
А так же для текущего Deployment увеличить например количество реплик с 2 до 4, для примера будем использовать наш Helm Chart
helm upgrade App AppMain-HelmChart/ --set replicaCount=4
Так же еще текущий Chart можно запаковать в архив вот такой командой
helm package AppMain-HelmChart/
и хранить его, например как версию приложения в своих репозиториях. Helm сохранит данный файл и к нему можно будет обращаться для деплоя не распаковывая
helm install App123 App-HelmChart-0.1.0.tgz
В Helm можно подключать публичные репозитории, где лежит куча чартов например для деплоя Zabbix
helm search hub zabbix
Заключение
На этом основы k8s считаю завершенным. Статья получилась большая, и если обнаружили ошибки, то смело пишите. Исправим, была мысль разделить данную статью на бОльшое количество статей, чем одна. Надеюсь все было понятно.
Анонсы и еще больше информации в Telegram-канале