Kuber for quality
Немного о Kubernetes и контейнерах.
Kubernetes - это оркестратор контейнеров. Это то определение которое написано в Википедии. И в нём есть два не очень применимых к IT слова - оркестратор и контейнеры. Давайте поподробнее их рассмотрим.
Контейнеры, с точки зрения IT в общем и Linux систем в частности, это некая изолированная область в операционной системе. У него обособлены от основной системы: файловая система, сетевой стек, дисковое пространство, оперативная память, процессор. Чем-то всё это похоже на виртуальные машины. Но есть отличие - в контейнерах для запуска программ (приложений) нет необходимости устанавливать операционную систему. Там располагаются только сами исполняемые файлы и всё, что необходимо для их запуска: бинарники, библиотеки, зависимые программы. Это позволяет сократить издержки ресурсов на поддержку работу ОС, уменьшает время загрузки приложения и увеличивает стабильность его работы.
Итак, резюмируя. Контейнеры - это выделенная область в ОС, в которой находится какое-то приложение и все необходимые для его запуска компоненты.
Использование контейнеров нашло широкое применение в последние несколько лет. Их облюбовали программисты, тестировщики, системные администраторы. Они упрощают и ускоряют их работу. Также с появлением контейнеров появилась и новая специальность - DevOps. Это специалист, который хорошо понимает как работают контейнеры, помогает программисту и системному администратору найти общий язык и часто совмещает эти функции. О специалисте DevOps есть даже прекрасная шутка:
"Что общего между девопсом и бомжом?"
"Тот и другой ковыряются в контейнерах."
Понятие оркестрации должно быть хорошо известно любителям музыки. В оркестре есть много разных инструментов и дирижёр, который ими управляет. Это позволяет разным инструментам слаженно играть красивую симфонию.
Kubernetes можно сравнить с дирижёром. Только он управляет не музыкальными инструментами, а контейнерами. Он знает какой контейнер где находится, ресурсы которые он потребляет, какое приложение на нём запущено, жив ли он. Также он позволяет переносить контейнеры с одной виртуальной машины на другую в случае поломки оной. Это гарантирует независимость работы приложения от работы операционной системы ВМ и самой ВМ. Также это гарантирует работу приложения от поломки физического сервера, на котором расположены ВМ.
В Kubernetes есть множество разных абстракций, управляющих работой приложения и контейнера. Разобраться в них не просто, но мы постараемся вкратце их описать и дать базовое представление о них. К базовым абстракциям Kubernetes относятся: Pod, Namespace, Deployment, DaemonSet, StatefulSet, Service, Ingress, Secret, ConfigMap, PV/PVC.
Про них мы поговорим далее.
Namespace.
Первая абстракция, о которой поговорим - это namespace. Её задача - это группировка ресурсов. В namespace находятся почти все ресурсы Kubernetes.
Для понимания его проще всего сравнить с каталогами, которые хранят всякие разные файлы. Только namespace будут содержать не файлы, а: Pod, Namespace, Deployment, DaemonSet, StatefulSet, Service, Ingress, Secret, ConfigMap, PVC.
Также, в отличие от каталогов, namespace не может содержать в себе другой namespace. То есть нет вложенности.
При создании кластера Kubernetes создаются несколько стандартных namespace: default, kube-system, kube-public. В namespace default предлагается складывать ресурсы пользователю. В namespace kube-system находятся системные ресурсы кластера. В namespace kube-public лежат ресурсы доступные всем пользователям кластера.
Также можно самим создавать свои namespace. Для этого можно воспользоваться утилитой kubectl. Она используется для взаимодействия с API кластера.
Команда для создания namespace.
kubectl create ns testing
Эта команда создаёт namespace с именем testing.
- Namespace используются для группировки других абстракций Kubernetes;
- Namespace используются для логического разделения ресурсов разных команд. Например, у команды mdb свой namespace, у команды тестировщикой свой, у фронтендеров свой. Свои namespace для окружения prod/preprod/testing;
- У namespace нет вложенности;
- Namespace можно ограничить по ресурсам. Например, сколько выделено ресурсов cpu/ram каждому namespace. Сколько можно создать подов/деплойментов/сервисов/ингрессов/etc;
- Имена ресурсов уникальны в пределах одного namespace. В разных namespace имена ресурсов могут совпадать;
- При создании кластера Kubernetes по-умолчанию создаются несколько namespace: default, kube-system, kube-public;
- Namespace можно создать/удалить.
Для удаления namespace можно воспользоваться командой.
kubectl delete ns testing
Pod.
Kubernetes не умеет работать с контейнерами напрямую. Минимальной, базовой, ресурсной единицей с которой он работает - это Pod. Контейнеры с полезной нагрузкой в виде приложений находятся в подах кластера.
Pod - это эфемерная сущность. Это запрос на создание контейнера с определённой конфигурацией, определёнными ресурсами и пробами.
Особенности подов.
1. Под - это минимальная объектная единица в кластере k8s. Kubernetes управляет не контейнерами напрямую, а подами.
2. Внутри одного пода могут располагаться несколько контейнеров с полезной нагрузкой. К примеру, nginx и postgresql. Но это абсолютно не продовое решение. Ведь если с подом что-то произойдёт, то погибнут сразу оба приложения. Также такое решение плохо масштабируется. Даже если сделать несколько одинаковых подов, то у каждого пода будет свой экземпляр базы данных и обратного прокси.
Поэтому такие сервисы лучше разносить по разным подам.
3. Под не может находиться вне какого-то пространства имён. Иными словами он всегда будет частью какого-то Namespace.
4. Под физически расположен на нодах кластера.
5. Поды, находящиеся на ноде кластера, совместно используют сетевой стек данной ноды.
6. У подов есть много разных состояний. Об этом поговорим ниже.
7. К подам могут монтироваться постоянные тома, для хранения данных, которые необходимо сохранить.
8. Кластер k8s управляет подами самостоятельно. Так он может перенести под с ноды на ноду без вмешательства со стороны человека.
9. Каждому поду присваивается свой уникальный ip адрес в сети кластера.
10. Поды предоставляют контейнерам доступ к сети и хранилищу.
11. Стоит разделять проблемы пода и проблемы с контейнером внутри данного пода.
12. Под - это не масштабируемый ресурс. Имеется ввиду, если мы создаём просто под, без управляющего им контроллера.
13. Поды зависят от ресурсов и лимитов в кластере. Если под будет запрашивать для своей работы ресурсов больше чем есть в кластере, то он будет ожидать когда эти ресурсы освободятся.
14. Для проверки того живы ли контейнеры внутри пода или нет используются специальные пробы (Liveness Probe и Readness Probe).
15. В случае пересоздания пода, вся информация в нём удаляется. Поэтому нет какого-то большого смысла подключаться к поду и что-то там устанавливать или править настройки.
16. Для управления настройками прилжений внутри контейнера в поде существует специальная абстракция - ConfigMap.
17. Если для работы пода нужна секретная информация, например, ключи доступа или пароли, то используется специальная сущность - Secret.
18. В современном мире поды вручную никто не создаёт. Для создания подов используются контроллеры. Например, Deployment, StatefulSet, DaemonSet.
19. У пода в обязательном порядке есть имя и спецификация контейнера с приложением внутри.
20. В манифесте любой сущности в кластере k8s обязательно есть версия его api (apiVersion), тип к которому относится сущность (kind), метаинформация о сущности (metadata) и сама спецификация (spec). Это обязательные параметры. При создании спецификации пода посмотрим на них внимательнее.
Из написанного выше становится ясно, что под - это базовая сущность, с которой работают множество других сущностей в кластере. Я здесь перечислил далеко не всё. Постепенно будем изучать их все.
Но что такое под в физическом смысле? Человек видит спецификацию на его создание, может посмотреть статус пода. Но что же такое сам под. В физическом смысле под - это запрос на создание и запуск контейнера на определённом узле (ноде) кластера k8s.
Под может находиться в одной из следующих фаз.
- Pending (ожидание): API Server создал ресурс пода и сохранил его в etcd, но под ещё не был запланирован, а образы его контейнеров — не получены из реестра;
- Running (функционирует): под был назначен узлу и все контейнеры созданы kubelet'ом;
- Succeeded (успешно завершён): функционирование всех контейнеров пода успешно завершено и они не будут перезапускаться;
- Failed (завершено с ошибкой): функционирование всех контейнеров пода прекращено и как минимум один из контейнеров завершился со сбоем;
- Unknown (неизвестно): API Server не смог опросить статус пода, обычно из-за ошибки во взаимодействии с kubelet.
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: cam-nginx namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:latest args: - sleep - "3600" EOF
При использовании yaml манифеста следует помнить, что отступы здесь имеют значение. Количество отступов для объектов одного уровня должно быть одинаковым.
В данной спецификации четыре верхнеуровненых элемента: apiVersion, kind, metadata, spec.
Посмотреть какие версии api поддерживает кластер можно с помощью команды:
kubectl api-resources
Посмотреть информацию о подах.
# Посмотреть информацию о подах в текущем namespace kubectl get po # Посмотреть информацию о подах во всех namespace kubectl get po -A # Посмотреть информацию о подах в namespace kube-system kubectl get po -n kube-system # Посмотреть информацию о подах в текущем namespace в расширенном формате kubectl get po -owide # Посмотреть информацию о подах в текущем namespace в формате yaml kubectl get po -oyaml # Посмотреть информацию о подах в текущем namespace в формате json kubectl get po -ojson # Посмотреть информацию о подах в namespace kube-system в расширенном формате yaml kubectl get po -n kube-system -owide -oyaml
kubectl logs cam-nginx
kubectl exec --tty --stdin cam-nginx -- /bin/bash
Посмотреть расширенную информацию о поде.
kubectl describe po cam-nginx # Также расширенную информацию покажут команды kubectl get po cam-nginx -oyaml kubectl get po cam-nginx -ojson
Под также можно создать через команду run.
# Пример пода, используемого для диагностики DNS. kubectl run jessie-dnsutils --image=k8s.gcr.io/jessie-dnsutils --restart=Never --command sleep infinity
# Пример ещё одного тестового пода. kubectl run -i -t --rm --image amouat/network-utils test bash
При создании пода можно прописать какие ресурсы ему выделяются при его создании и сколько ресурсов ноды он может отхапать при увеличении нагрузки.
Для их указания существует блок resources.
cpu: 300m memory: 300Mi limits: memory: 500Mi
resources: requests: cpu: 300m memory: 300Mi limits: memory: 500Mi
Использование cpu здесь указано в милиядрах. А memory в мебибайтах.
Указание лимитов на cpu является дурной практикой. Поэтому без неё обойдёмся.
Спецификация пода с реквестами и лимитами.
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: cam-nginx namespace: default labels: app: nginx spec: containers: - name: nginx image: nginx:latest resources: requests: cpu: 300m memory: 300Mi limits: memory: 500Mi args: - sleep - "3600" EOF
С реквестами и лимитами напрямую связаны QOS классы.
Поды не равны между собой по значимости. Одни поды более ценные, другие менее ценные. В тоже время ресурсы нод ограничены. Иными словами может сложиться ситуация когда кластеру придётся выбирать какой под запустить, а какой остановить из-за нехватки ресурсов. Или не выдать поду ресурсов. Чтобы управлять процессом значимости подов и придумали QOS классы.
Также QOS класс влияет на то, какие поды будут убиты OOM Killer, если памяти на нодах будет не хватать. Поведение подов при этом будет подчиняться тому что указано в поле restartPolicy.
У каждого пода есть свой класс QOS. Неважный, важный и самый важный.
1. BestEffort — самый низкоприоритетный класс. Он назначается объектам, у которых не указаны requests и limits. В самом негативном случае такие объекты могут вообще не получить ресурсов. Они же — первые кандидаты на перемещение на другой узел при перераспределении ресурсов. Это QOS класс по-умолчанию.
2. Burstable — почти как Guaranteed, только с заданными requests < limits. И достаточно только одного ресурса CPU or Memory.
3. Guaranteed — назначается объектам с заданными requests = limits. Нужно задать эти значения для каждого контейнера, при этом запрошенное и лимитируемое значение cpu и ram должно быть идентичным. У этого класса наивысший приоритет.
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: cam-nginx labels: app: nginx environment: preprod spec: containers: - image: nginx:latest name: cameda-nginx ports: - containerPort: 80 EOF
Кусочек дескрайба этого пода QoS Class: BestEffort Node-Selectors: <none>
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: cam-nginx spec: containers: - image: nginx:latest name: cam-nginx resources: requests: cpu: 200m memory: 100Mi limits: cpu: 300m memory: 150Mi EOF
Кусочек дескрайба этого пода QoS Class: Burstable Node-Selectors: <none>
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: cam-nginx spec: containers: - image: nginx:latest name: cam-nginx resources: requests: cpu: 300m memory: 150Mi limits: cpu: 300m memory: 150Mi EOF
Кусочек дескрайба этого пода QoS Class: Guaranteed Node-Selectors: <none>
Пробы - это хелсчеки состояния ресурсов. С их помощью можно проверить жив ли контейнер в поде или ресурс в контейнере.
Liveness Probe - контролирует состояние приложения во время его жизни. Работает постоянно.
Readness Probe - проверяет готово ли приложение принимать трафик. Если не готово, оно убирается из балансировки. Работает постоянно.
Events.
При просмотре дескрайба особое внимание необходимо уделить блоку events. Он будет находиться в самом его низу.
Выглядеть он будет примерно так:
Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Pulling 24m (x3 over 144m) kubelet Pulling image "nginx:latest" Normal Created 24m (x3 over 144m) kubelet Created container nginx Normal Started 24m (x3 over 144m) kubelet Started container nginx Normal Pulled 24m kubelet Successfully pulled image "nginx:latest" in 1.245822227s
Если с подом всё хорошо и он находится в состоянии RUNNING/COMPLETED, то в эвентах не будет ошибок. Но если под будет находиться в состоянии Error/Failed/CrashLoopBackOff/Init 0:1/Terminating, то там часто будет указана ошибка из-за которой под не работоспособен.
Поэтому, при траблшутинге, изучение эвентов - это то, с чего стоит начинать.
Node.
Поды шедулятся на воркер ноды кластера. Нода - это обычно ВМ, на которой расположена полезная нагрузка кластера в виде контейнеров. Здесь важно понимать, что физически на ноде будут запущены контейнеры. А в API k8s будут отображаться поды. Всё потому что k8s не умеет напрямую в контейнеры, а ноды не работают только с ними. Но для дальнейшего упрощения я буду говорить, что на нодах тоже поды :)
Ноды в кластере k8s бывают двух типов: воркер ноды, на которых расположена вся полезная нагрузка в виде подов с приложениями и мастер ноды, на которых расположены поды, управляющие работой кластера. О мастер нодах поговорим чуть позже.
Посмотреть информацию о том, сколько нод есть в кластере и их названия можно, используя команду:
kubectl get no
kubectl get no NAME STATUS ROLES AGE VERSION cl1k0hr81fu4s6fuqlue-ahoq Ready <none> 26d v1.24.8 cl1k0hr81fu4s6fuqlue-ejuj Ready <none> 26d v1.24.8 cl1k0hr81fu4s6fuqlue-etyf Ready <none> 26d v1.24.8 cl1k0hr81fu4s6fuqlue-iqep Ready <none> 26d v1.24.8 cl1k0hr81fu4s6fuqlue-ucaj Ready <none> 26d v1.24.8 cl1k0hr81fu4s6fuqlue-ugeb Ready <none> 26d v1.24.8 cl1k0hr81fu4s6fuqlue-uguf Ready <none> 26d v1.24.8 cl1k0hr81fu4s6fuqlue-yguq Ready <none> 26d v1.24.8 cl1k0hr81fu4s6fuqlue-ynip Ready <none> 26d v1.24.8
В расширенном варианте выводится также информация об её адресе, версии ОС и ядра:
kubectl get no -owide
kubectl get no -owide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME cl1k0hr81fu4s6fuqlue-ahoq Ready <none> 26d v1.24.8 10.130.0.15 51.250.35.160 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18 cl1k0hr81fu4s6fuqlue-ejuj Ready <none> 26d v1.24.8 10.128.0.54 158.160.107.155 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18 cl1k0hr81fu4s6fuqlue-etyf Ready <none> 26d v1.24.8 10.130.0.18 51.250.42.245 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18 cl1k0hr81fu4s6fuqlue-iqep Ready <none> 26d v1.24.8 10.128.0.16 158.160.107.128 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18 cl1k0hr81fu4s6fuqlue-ucaj Ready <none> 26d v1.24.8 10.129.0.15 51.250.25.154 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18 cl1k0hr81fu4s6fuqlue-ugeb Ready <none> 26d v1.24.8 10.129.0.31 158.160.18.145 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18 cl1k0hr81fu4s6fuqlue-uguf Ready <none> 26d v1.24.8 10.129.0.40 158.160.31.142 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18 cl1k0hr81fu4s6fuqlue-yguq Ready <none> 26d v1.24.8 10.128.0.46 158.160.96.199 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18 cl1k0hr81fu4s6fuqlue-ynip Ready <none> 26d v1.24.8 10.130.0.17 51.250.39.185 Ubuntu 20.04.5 LTS 5.4.0-139-generic containerd://1.6.18
Если сделать дескрайб ноды, то там можно увидеть какие поды на ней работают.
Non-terminated Pods: (12 in total) Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age --------- ---- ------------ ---------- --------------- ------------- --- default csi-s3-dhl2x 0 (0%) 0 (0%) 0 (0%) 0 (0%) 26d default filebeat-filebeat-42xz8 100m (5%) 0 (0%) 100Mi (3%) 0 (0%) 6d22h default ingress-nginx-controller-5cdbcc9966-859gx 100m (5%) 0 (0%) 90Mi (3%) 0 (0%) 11d default yc-alb-ingress-controller-hc-rkzjm 100m (5%) 0 (0%) 200Mi (7%) 200Mi (7%) 6d18h kube-system cilium-l2hv9 100m (5%) 0 (0%) 100Mi (3%) 0 (0%) 26d kube-system coredns-558bcd7559-65bpm 100m (5%) 100m (5%) 128Mi (4%) 128Mi (4%) 3d20h kube-system hubble-ui-86889fdc75-swwth 0 (0%) 0 (0%) 0 (0%) 0 (0%) 11d kube-system ip-masq-agent-kwzln 10m (0%) 0 (0%) 16Mi (0%) 0 (0%) 26d kube-system node-local-dns-5nww5 30m (1%) 0 (0%) 50Mi (1%) 0 (0%) 25d kube-system npd-v0.8.0-kgfgf 20m (1%) 200m (10%) 20Mi (0%) 100Mi (3%) 26d kube-system yc-disk-csi-node-v2-wrd5p 30m (1%) 600m (31%) 96Mi (3%) 600Mi (22%) 26d kubecost kubecost-prometheus-node-exporter-rvvsh 0 (0%) 0 (0%) 0 (0%) 0 (0%) 6d11h
Сколько ресурсов аллоцировано:
Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) Resource Requests Limits -------- -------- ------ cpu 590m (30%) 900m (46%) memory 800Mi (29%) 1028Mi (37%) ephemeral-storage 0 (0%) 0 (0%) hugepages-1Gi 0 (0%) 0 (0%) hugepages-2Mi 0 (0%) 0 (0%)
ConfigMap.
Данная абстракция Kubernetes предназначена для создания кастомных файлов конфигурации у приложения и передачи их в под. То есть для приложения в поде можно заранее подготовить нужную конфигурацию и при создании пода эту конфигурацию подложить.
Пример. Создадим кастомную конфигурацию для nginx сервера.
Файл /etc/nginx/site-available/default
server { listen 80 default_server; server_name cameda.ru; root /var/www/html/cameda.ru; access_log off; location / { index.html; } ########################################################################### location ~ /\.ht { deny all; } }
Создадим ConfigMap из этого файла.
kubectl create cm nginx-config --from-file /etc/nginx/site-available/default
Создадим под с прикрученным ConfigMap.
cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: cam-nginx namespace: default spec: containers: - name: cam-nginx image: nginx:latest resources: requests: cpu: 300m memory: 300Mi limits: memory: 500Mi volumeMounts: - name: nginx-configmap mountPath: /etc/nginx/sites-available readOnly: true volumes: - name: nginx-configmap configMap: name: nginx-config EOF
Посмотреть информацию по ConfigMap.
kubectl get cm kubectl describe cm nginx-config kubectl describe po cam-nginx kubectl exec --tty --stdin cam-nginx -- cat /etc/nginx/sites-available/default
Secret.
С помощью этой абстракции можно хранить чувствительные данные в защищенном хранилище. Например, сюда можно отнести пароли, OAUTH токены и тп.
Создать секрет можно с помощью команды.
kubectl create secret generic cam-secret --from-literal=cameda=goodPa$word
Создать секрет можно из файла.
echo -n 'cameda' > ./username.txt echo -n 'goodPa$word' > ./password.txt
kubectl create secret generic cam-secret2 --from-file=./username.txt --from-file=./password.txt
Пример с использованием ssh ключа.
kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/Users/cameda/.ssh/id_rsa --from-file=ssh-publickey=/Users/cameda/.ssh/id_rsa.pub
После того как прокинули в под ssh ключ, нужно будет поменять права на приватный ключ (600). После этого его можно будет использовать.
Создадим под с прикрученным Secret.
cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: cam-nginx namespace: default spec: containers: - name: cam-nginx image: nginx:latest resources: requests: cpu: 300m memory: 300Mi limits: memory: 500Mi volumeMounts: - name: nginx-secret mountPath: /etc/secret readOnly: true volumes: - name: nginx-secret secret: secretName: cam-secret EOF