K8s
YAML Синтаксис#
Основные сущности#
-
Скалярные значения
-
Списки (массивы)
или -
Словари
или -
Многострочные строки
|- сохраняет всё как есть, включая\n>- склеивает строки в одну с пробелами
Расширенные возможности#
-
Ссылки и якори ($, *)
-
Линтер
Разное#
-
Null
-
Boolean
чтобы явно задать строку, необходимо использовать кавычки
Разворот кластера#
Буду поднимать 1 мастер ноду, 2 воркер ноды, 1 вспомогательную (DNS, etc)
Поднимаем ВМ для кластера#
Я делаю всё от рута
apt update && apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst- устанавливаем гипервизор- Загружаем образ Ununtu
-
Создаём ВМ
-
virsh vncdisplay k8s-master- выводит VNC-дисплей, к которому привязан указанная гостевая ОС, если запущена
Пример вывода
Значит порт: 5900 + 0
remote-viewer vnc://localhost:5900- конфигурируем, устанвливаем ВМvirsh list --all- список запущенных ВМvirsh start k8s-master- запуск созданонй ВМvirsh domifaddr k8s-master- узнаём адрес ВМssh ilyamak04@192.168.122.157- ну и подключаемся по ssh
Аналогично поднимаем 2 воркер ноды, и 1 вспомогательную, не забываем менять выделяемые ресурсы для ВМ
Дополнительные команды для управления ВМ
virsh shutdown <vm-name>- штатное выключение ВМvirsh destroy <vm-name>- жёсткое выключение, например, если ВМ зависла, НЕ УДАЛЯЕТ ВМvirsh list --all- показать список всех виртуальных машин (включая выключенные)virsh start <vm-name>- запустить виртуальную машинуvirsh undefine <vm-name>- удалить ВМ из libvirt (не удаляет диск в /var/lib/libvirt/images/)virsh undefine <vm_name> --remove-all-storage- удалить вм со всеми дискамиvirsh domifaddr <vm-name>- показать IP-адрес ВМ (если доступен)virsh dumpxml <vm-name>- вывести XML-конфигурацию ВМvirsh console <vm-name>- подключиться к консоли ВМ (если настроен serial-порт)virsh domstate <vm-name>- показать текущее состояние ВМvirsh autostart <vm-name>- включить автозапуск ВМ при старте хостаvirsh autostart --disable <vm-name>- отключить автозапуск ВМvirsh net-list- список виртуальных сетей libvirtvirsh net-dumpxml default- показать XML-конфигурацию сети defaultvirsh dumpxml <vm-name>- посмотреть XML-конфиг ВМvirsh net-edit default- отредактировать настройки сети (например, static DHCP)- Клонировать ВМ
Подготовка ВМ#
- Откючаем
swap, k8s требует отключенный swap для корректной работы планировщика
Не забыть убрать запись из /etc/fstab
Kubernetes использует cgroups для управления CPU и памятью контейнеров. Если включен swap, ядро может игнорировать лимит памяти, потому что будет сбрасывать часть данных в swap. Это нарушает работу OOM (Out Of Memory) killer и других механизмов kubelet'а. Когда swap включён, kubelet может не "увидеть", что контейнер превысил лимит памяти. Kubelet считает, что вся доступная память — это только RAM.
-
Включаем модули ядра для корректной сетевой работы подов
-
Для корректной маршрутизации сетевого трафика
-
Время на узлах должно быть синхронизировано, чтобы избежать проблем с сертификатами или ещё чего-нибудь
-
Проверить что ssh-сервис запущен
-
Фаервол для простоты настройки можно отключить, но выставлять весь кластер в интернет очевидно плохая идея
-
Добавим репозиторий docker для установки containerd, Kubernetes не запускает контейнеры напрямую, он использует Container Runtime Interface (CRI), который реализует containerd
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/trusted.gpg.d/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null apt update apt install -y containerd.io -
Kubernetes требует, чтобы containerd использовал systemd как управляющий механизм cgroups, т.е. структуру контроля ресурсов (CPU, память и т.п.)
-
Добавим репозиторий k8s, установим необходимые компоненты k8s
# Добавить GPG-ключ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # Добавить репозиторий Kubernetes echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list # Обновить список пакетов apt update # Установить kubeadm, kubelet, kubectl apt install -y kubelet kubeadm kubectl # Заблокировать от автоматического обновления apt-mark hold kubelet kubeadm kubectl ### # Проверка ### kubeadm version kubelet --version kubectl version --client -
Установим
crictlдля взаимодействия сcontainerd(удобно для отладки) -
Добавим алиас для команды
kubectl -
Базовые команды
-
Автодополнение для
kubectl
DNS-сервер#
Данный DNS-сервре настраивается для коммуникации между нодами (серверами), для организации резолва имен между сущностями кубера, кубер использует свой ДНС (CoreDNS)
-
Установка BIND9
-
vi /etc/bind/named.conf.options
// // ------------------------------------------------------------ // Глобальные параметры BIND 9 // ------------------------------------------------------------ options { // Где BIND хранит кэш и служебные файлы directory "/var/cache/bind"; // Разрешаем рекурсивные запросы recursion yes; // Кому разрешена рекурсия. В лаборатории можно any, // в проде указать свою подсеть. allow-recursion { any; }; // На каких интерфейсах слушать DNS-запросы listen-on { 192.168.122.66; 127.0.0.1; }; listen-on-v6 { none; }; // IPv6 не используем // Куда пересылать внешние запросы forwarders { 8.8.8.8; 1.1.1.1; }; // Включаем автоматическую проверку DNSSEC-подписей dnssec-validation auto; }; -
vi /etc/bind/named.conf.local
// ------------------------------------------------------------ // Авторитетные зоны // ------------------------------------------------------------ // Прямая зона lab.local (имя → IP) zone "lab.local" IN { type master; // главный (= авторитет) file "/etc/bind/zones/db.lab.local"; allow-update { none; }; // динамических правок не ждём }; // Обратная зона 122.168.192.in-addr.arpa (IP → имя) zone "122.168.192.in-addr.arpa" IN { type master; file "/etc/bind/zones/db.192.168.122"; allow-update { none; }; }; -
mkdir -p /etc/bind/zones
-
vi /etc/bind/zones/db.lab.local
$TTL 86400 ; время жизни записей по умолчанию (24 ч) @ IN SOA k8s-infra.lab.local. admin.lab.local. ( 2025062401 ; Serial (YYYYMMDDnn) — увеличивайте при каждой правке 1h ; Refresh — как часто slave (если бы был) проверяет SOA 15m ; Retry — если refresh не удался 7d ; Expire ; после этого зона считается устаревшей 1h ) ; Negative TTL — кэш «NXDOMAIN» ; — NS-запись: кто авторитетен для зоны IN NS k8s-infra.lab.local. ; ---------- A-записи ---------- k8s-master IN A 192.168.122.157 ; control-plane k8s-worker1 IN A 192.168.122.141 ; worker-1 k8s-worker2 IN A 192.168.122.192 ; worker-2 k8s-infra IN A 192.168.122.66 ; infra + DNS -
vi /etc/bind/zones/db.192.168.122
$TTL 3600 @ IN SOA k8s-infra.lab.local. admin.lab.local. ( 2025062401 1h 15m 7d 1h ) IN NS k8s-infra.lab.local. ; ---------- PTR-записи (последний октет → FQDN) ---------- 157 IN PTR k8s-master.lab.local. 141 IN PTR k8s-worker1.lab.local. 192 IN PTR k8s-worker2.lab.local. 66 IN PTR k8s-infra.lab.local. -
Проверка синтаксиса
-
Перезапуск сервиса
-
Добавить на каждой ноде в конфиг netplan
-
Применить
-
Проверка работы DNS
Настройка NFS#
Настройка NFS-сервера#
-
Устанавливаем сервер
-
Создаём каталог который будет экспортироваться
-
vi /etc/exports -
rw- разрешает чтение и запись sync- операции записи выполняются немедленно (безопасно)no_subtree_check- ускоряет работу при экспорте подкаталоговroot_squash- если клиент заходит как root, он будет понижен до "nobody" (безопаснее)fsid=0- нужен для корня экспортов в NFSv4 (в NFSv4 экспортируется только один корень)-
192.168.122.0/8- сеть, которой разрешён доступ -
Экспортировать каталог
Настройка NFS-клиента#
-
Проверить доступность сервера
-
Монтируем расшаренный каталог на клиент
-
Добавить в
/etc/fstab, для автомонтирования при перезагрузке
Разворачиваем кластер#
-
Версии api, которые поддерживает установленная версия
kubeadm -
vi /etc/kubernetes/kubeadm-config.yamlapiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token ttl: 24h0m0s usages: - signing - authentication localAPIEndpoint: advertiseAddress: 192.168.122.157 bindPort: 6443 nodeRegistration: criSocket: "unix:///var/run/containerd/containerd.sock" imagePullPolicy: IfNotPresent name: k8s-master.lab.local taints: - effect: NoSchedule key: node-role.kubernetes.io/master --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration certificatesDir: /etc/kubernetes/pki clusterName: cluster.local controllerManager: {} dns: {} etcd: local: dataDir: /var/lib/etcd imageRepository: "registry.k8s.io" apiServer: timeoutForControlPlane: 4m0s extraArgs: authorization-mode: Node,RBAC bind-address: 0.0.0.0 service-cluster-ip-range: "10.233.0.0/18" service-node-port-range: 30000-32767 kubernetesVersion: "1.30.14" controlPlaneEndpoint: 192.168.122.157:6443 networking: dnsDomain: cluster.local podSubnet: "10.233.64.0/18" serviceSubnet: "10.233.0.0/18" scheduler: {} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration bindAddress: 0.0.0.0 clusterCIDR: "10.233.64.0/18" ipvs: strictARP: True mode: ipvs --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration clusterDNS: - 169.254.25.10 systemReserved: memory: 512Mi cpu: 500m ephemeral-storage: 2Gi # Default: "10Mi" containerLogMaxSize: 10Mi # Default: 5 containerLogMaxFiles: 3 -
Инициализация первой ноды
-
Если приложение долго не завершает свою работу, значит что-то пошло не так. Необходимо отменить все действия и запустить его ещё раз, но с большим уровнем отладки.
-
Смотрим ip для доступа к кластеру
-
Установим драйвер сети (CNI Plugin), Cilium CNI с поддержкой multicast для разворота нод ROS2
CLI_VER=0.16.7 curl -L --remote-name-all \ https://github.com/cilium/cilium-cli/releases/download/v${CLI_VER}/cilium-linux-amd64.tar.gz tar xzvf cilium-linux-amd64.tar.gz sudo mv cilium /usr/local/bin/ cilium version cilium install \ --version 1.17.5 \ --set ipam.mode=kubernetes \ --set tunnel=vxlan \ --set enable-multicast=true # ждём OK cilium status --wait -
Смотрим ноды в кластере
-
Смотрим поды на ноде
-
Регистрируем воркер ноды в кластере (представленная команда выводится в стандартный вывод после инициализации первой контрол ноды)
Общее#
kubectl explain <name>- дока (kubectl explain pod.spec)kubectl edit deployment deployment_name(kubectl edit) - изменение манифеста на лету, нигде не версионируется (использовать только для дебага на тесте)kubectl config get-contexts- информация о текущем контексте
POD#
k8s - кластерная ОС
POD - одно запущенное приложение в кластере k8s, минимальная абстракция k8s (внутри пода может быть несколько контейнеров, и в поде всегда минимум 2 контейнера: приложение, сетевой неймспейс) (контейнер внутри пода, как отдельный процесс в ОС)
kubectl create -f pod.yml- создать под согласно конфигу из файлаkubectl get pod- список подовkubectl describe pod <pod_name>- описание подаkubectl describe pod <pod_name> -n <namespace> | less- описание пода в нсkebectl delete pod <pod_name>илиkubectl delete -f pod.yml- удаление пода-
k -n <ns_name> delete pod <pod_name>- удалить под -
k get pod <pod_name> -n <ns_name> -o yaml | less- посмотреть полный манифест пода kubectl -n <ns_name> logs <pod_name>- логи подаkubectl -n <ns_name> logs <pod_name> -c <container_name>- логи последнего контейнера
Разница между create и apply
create создаёт ресурс только если его ещё нет, если ресурс уже существует — выдаёт ошибку
apply cоздаёт ресурс, если его нет,или обновляет, если он уже существует, поддерживает историю изменений, идемпотентен
# пример описания пода
---
apiVersion: v1
kind: Pod # тип сущности
metadata:
name: mypod # в рамках одного пространства имён имя уникально
spec: # описание объекта
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Ресурсы (QoS)#
Приоритет Pod'ов при выделении ресурсов и при давлении на узел
QoS не управляется напрямую, а автоматически присваивается каждому Pod'у в зависимости от указанных ресурсов (requests и limits) в манифесте.
куб определяет 3 уровня QoS
Guaranteed- requests == limits для всех контейнеров в Pod'е, высший приоритет, удаляется в последнюю очередьBurstable- задан requests, но не равно limits, или не для всех-
BestEffort- не указано ничего (ни requests, ни limits), если ресурсов на ноде не хватает, такие поды убиваются в первую очередь -
Посмотреть QoS пода
Пробы#
- Если проба УСПЕШНА:
Readiness Probe- Под добавляется в эндпоинты Service. Теперь трафик с Load Balancer'а будет направляться на этот подLiveness Probe- Ничего не происходит. Контейнер продолжает работать как обычно
- Если проба НЕУДАЧНА:
Readiness Probe- Под удаляется из эндпоинтов Service. Трафик на этот под прекращается. Контейнер НЕ перезапускаетсяLiveness Probe- Контейнер убивается и перезапускается (согласно политике restartPolicy).
Best practice для описания пода#
Должны быть:
- Метки
- Задан образ контейнера
- Ресурсы контейнера(ов) ограничены
- Пробы
Namespace#
-
Namespace используются для изоляции групп ресурсов в пределах одного кластера kubernetes. Имена ресурсов должны быть уникальными в пределах namespace.
-
kubectl get ns- вывести неймспейсы kubectl create ns <name>- создать нсkubectl delete ns <name>- удалить нсk get ns <name>-
kubectl config set-context --current --namespace=<имя-namespace>- сменить ns чтобы писать команды без флага-n -
нс
kube-systemрасполагаются приложения control-plane -
нс
kube-publicдоступен для чтения всем клиентам -
kubectl config get-contexts- узнать в каком нс находишься
Repcicaset#
Задача Replicaset - обеспечить работу заданного количества реплик Pod'ов, описываемых Deployment
kubectl get rs- вывести репликасетыkubectl delete rs <name>-rs- удалить rsk delete replicaset --all- удалить все rs в nsk describe replicaset <name>k scale --replicas 3 replicaset <name>- заскейлисть репликасет-
k set image replicaset <name> <container_name>=<new_image_name>- обновить образ контейнера (но нужно пересоздать поды, replicaset не решает проблему обновления приложения, rs просто поддерживает заданное количество подов, задачу обновления решает абстрацкия deployment) -
Пример конфигурации
Deployment#
Абстракция, которая управляте replicasetами и podами
Deployment предназначен для stateless приложений
- создаёт и управляет ReplicaSet'ом
- Rolling updates — обновляет приложения без простоя
- Откат (rollback) к предыдущей версии
- Масштабирование (scale up/down)
-
Самовосстановление (если Pod удалён или упал)
-
Пример
deployment -
spec.selector- определяет за какие поды отвечает Deployment -
kubectl rollout restart- перезапуск Deployment
Обновление#
- создаваёт новый ReplicaSet с новой версией образа
- постепенно увеличивает количество новых Pod'ов и уменьшает старые
-
следит, чтобы всегда было достаточно доступных реплик
-
Пример обновления образа
-
Откат на предыдущую версию deployment (на ту версию, которая была применена до последнего успешного обновления)
-
Проверка состояния
-
При каждом изменении (kubectl apply, set image, scale, и т.п.) создаётся новая ревизия, по умолчанию куб хранит 10 ревизий
Service#
Сущность, которая предоставляет постоянную сетевую точку доступа к группе Pod'ов
kubectl get endpoints my-service- оказывает IP-адреса Pod'ов, к которым направляет трафик Service my-servicek get EndpointSlice
Service headless#
Не обеспечивает балансировку трафика к подам (нет ClusterIP), позволяет обращаться к поду по его доменному имени, используется с Statefulset, т.к. поды "статичны"
Statefulset#
Крнтроллер, похожий на Deployment гарантирует уникальность имени пода, порядок запуска, рестарта, удаления пода, постоянство имени пода (стабильное DNS имя), томов
Если PVC создан через StatefulSet (volumeClaimTemplates), то при удалиние sts, PVC не удаляется, это поведение кубера по умолчанию
podManagementPolicy- это параметр StatefulSet, который определяет в каком порядке создаются и удаляются Pod
Есть 2 режима
Тома#
emptyDir#
Обычно используется для:
- размещения кэша файлов
- данные которые необходимо хранить при сбоях в работе контейнера
- обмена файлами между несколькими контейнерами в поде
При удалении пода (например, при перезапуске, обновлении, сбое узла и т.д.) — данные из emptyDir удаляются безвозвратно
- Кусочек конфига
hostPath#
Изпользовать hostPath небезопасно!!!
Контейнер получает прямой доступ к файловой системе хоста
-
Пример
-
Kubernetes может проверять, существует ли путь, и что он из себя представляет
ConfigMap#
ConfigMap - сущность, предназначенная для хранения нечувствительных данных конфигурации в виде пар ключ: значение, позволяет отделить конфигурацию от кода и применять её к контейнерам без необходимости пересборки образа.
-
k get cm -
Пример
-
Пример
-
Передача переменных окружения из ConfigMap в Pod
-
Если переменная уже определена через env, она не будет перезаписана envFrom.
- Можно использовать сразу несколько envFrom (например, ConfigMap и Secret).
- Если переменная в ConfigMap содержит недопустимые символы (например, точки или тире), она не будет импортирована как env.
Secret#
Секрет - это объект, который содержит небольшое количетсво конфиденциальных даннх
k get secret-
k get secret <name> -o yaml -
Типы секрета
generic(Opaque) - пароли/токены для приложенийdocker-registry- данные авторизации в docker registrytls- TLS сертификаты
-
Пример
-
Для удобства админитратора есть поле
strigData, когда манифест примениться содержимое будет закодировано в base64 -
Так подключается в манифест
При добавлении новых секретов, необходимо помнить про правила мерджа манифестов, аннотацию kubectl.kubernetes.io/last-applied-configuration
- Добавление секретов в контейнер в виде тома
downwardAPI#
downwardAPI позволяет передать метаданные Pod'а(например, имя пода, namespace, labels, annotations, ресурсы) в контейнер через переменные окружения или файлы.
-
Пример (как том (файлы))
volumeMounts: - mountPath: "/etc/pod-info" name: pod-info readOnly: true volumes: - name: pod-info downwardAPI: items: - path: limit-cpu-millicores resourceFieldRef: containerName: openresty resource: limits.cpu divisor: 1m - path: limit-memory-kibibytes resourceFieldRef: containerName: openresty resource: limits.memory divisor: 1Ki - path: labels fieldRef: fieldPath: metadata.labels -
Пример (как переменные окружения)
projected#
projected - это том, который объединяет несколько источников данных в одну директорию
secretconfigMapdownwardAPI-
serviceAccountToken -
Пример
volumeMounts: - mountPath: "/etc/pod-data" name: all-values readOnly: true volumes: - name: all-values projected: sources: - downwardAPI: items: - path: limits/cpu-millicore resourceFieldRef: containerName: openresty resource: limits.cpu divisor: 1m - path: limits/memory-kibibytes resourceFieldRef: containerName: openresty resource: limits.memory divisor: 1Ki - path: labels fieldRef: fieldPath: metadata.labels - secret: name: user-password-secret items: - key: user path: secret/user - key: password path: secret/password - configMap: name: example-txt items: - key: example.txt path: configs/example.txt - key: config.yaml path: configs/config.yaml
PV, PVC#
k get pv
PersistentVolume (PV) - это объект, который предоставляет долговременное хранилище для Pod'ов, независимое от их жизненного цикла, под подключается к хранилищу не напрямую, а через PersistentVolumeClaim (PVC)
PVC работает только внутри одного namespace, а PV - кластерный объект
-
Архитектура
- PersistentVolume (PV) - описывает конкретный ресурс хранилища (например, NFS, iSCSI, Ceph, диск в облаке, локальный диск)
- PersistentVolumeClaim (PVC) - это запрос от Pod-а: «Хочу хранилище с такими-то параметрами»
- Kubernetes связывает PVC с подходящим PV (если типы и параметры совместимы)
-
accessModes(способы доступа)ReadWriteOnce(RWO) - том может быть смонтирован на чтение и запись только одним узломReadOnlyMany(ROX) - том может быть смонтирован на чтение многими узламиReadWriteMany(RWX) - том может быть смонтирован на чтение и запись многими узлами. Требуется поддержка со стороны бэкенда (например, NFS, CephFS)ReadWriteOncePod(RWOP) - том может быть смонтирован на чтение и запись только одним подом, гарантирует, что только один под во всем кластере имеет доступ к тому
-
persistentVolumeReclaimPolicy— что делать после удаления PVCRetain- PV остаётся, данные сохраняются (нужно вручную очистить/перепривязать) поведение ПО УМОЛЧАНИЮ для статически созданных томовDelete- PV и данные удаляются автоматическиRecycle- устаревший способ (удаляет файлы, оставляет PV)
-
(Связывание PVC c PV) Куб находит подходящий PV по:
storage(размер — должен быть ≥ запроса)accessModes(PV должен удовлетворять запрошенному)StorageClass(если указан)
Если нет подходящего PV - PVC останется в состоянии Pending
Dynamic Provisioning#
Настраивается один раз специальный компонент - Provisioner, далее Kubernetes автоматически создает новые PV по запросу от PVC
Как это +- работает:
- Администратор создает
StorageClass, который описывает тип хранилища и то, как его следует создавать (какой provisioner использовать) - Разработчик в своем
PVCуказывает, "мне нужно хранилище из такого-то StorageClass" PVCпопадает в Kubernetes (etcd)Provisioner(контроллер, следящий за неудовлетворенными PVC) видит этоProvisionerавтоматически создает новый PV того типа и размера, который был запрошенProvisionerсвязывает этот новый PV с PVC- Том монтируется поду
Provisioner - специальный контроллер, который и реализует функционал динамического создания томов
Примеры Provisioner'ов
pd.csi.storage.gke.io- для создания дисков в Google Cloud (GKE)ebs.csi.aws.com- для создания дисков EBS в AWSdisk.csi.azure.com- для Azure Disksnfs.csi.k8s.io- для динамического создания NFS-шарrancher.io/local-path- для создания томов на локальных дисках нод
Как пример работает
- Проверяет API Kubernetes на предмет появления новых
PVC - Видит
PVC, который ссылается наStorageClassс его именем (provisioner: pd.csi.storage.gke.io) - Вызывает API своего облачного провайдера (Google Cloud, AWS) для создания реального диска
- Создает в Kubernetes объект
PersistentVolume, который указывает на этот только что созданный диск -
Связывает
PVиPVC -
Пример
SrorageClassapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd # Название класса, на которое ссылается PVC provisioner: pd.csi.storage.gke.io # Драйвер, который умеет создавать диски parameters: type: pd-ssd # Тип диска в облаке (SSD) replication-type: regional-pd # Реплицируемый диск reclaimPolicy: Delete # Что делать с томом при удалении PVC? (Delete или Retain) volumeBindingMode: WaitForFirstConsumer # Ждать создания тома до назначения на под allowVolumeExpansion: true # Можно ли потом увеличить размер тома -
Пример
PVC, который используетSrorageClassapiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-database-pvc namespace: my-app spec: accessModes: - ReadWriteOnce # Том может быть смонтирован на чтение и запись только одним узлом storageClassName: fast-ssd # Запрашиваем хранилище из этого класса resources: requests: storage: 100Gi # Запрашиваем 100 Гибибайт -
Пример
Pod, который используетсPVCapiVersion: v1 kind: Pod metadata: name: my-database-pod spec: containers: - name: db image: postgres:16 volumeMounts: - name: data-storage # Монтируем в Pod mountPath: /var/lib/postgresql/data volumes: - name: data-storage persistentVolumeClaim: claimName: my-database-pvc # Указываем имя PVC, который хотим использовать -
reclaimPolicyDelete- при удаленииPVCавтоматически удаляется и связанный с нимPersistentVolume, а также физический диск в облаке, данные будут безвозвратно утеряны, это поведение ПО УМОЛЧАНИЮ для ДИНАМИЧЕСКИ созданных томовRetain- при удаленииPVCсамPVпереходит в состояниеReleased, данные на диске остаются, но том нельзя заново использовать, пока администратор вручную не очистит и не восстановит его, это безопасная политика
-
Access Modes-
DaemonSet#
Для запуска пода на каждой ноде кластера, если нет ограничений (Taints и Tolerations)
Манифест как у Deployment, кроме параметра kind, нет параметра resplicas
k get ds
Taint#
Taint - это свойство ноды, которое действует как ограничение. Взаимодействует с планировщиком.
taint состоит из трёх частей: key=[value]:Effect
key- ключ taint (например, node-role.kubernetes.io/control-plane)value- значение taint. Не обязателен к определению. Если не указано, то любое значение будет считаться совпадением.Effect- действие.NoSchedule- запрещает планирование под на ноде. Поды, запущенные до применения taint не удаляются.NoExecute- запрещает планирование под на ноде. Поды, запущенные до применения taint будут удалены с ноды.PreferNoSchedule- это «предпочтительная» или «мягкая» версия NoSchedule. Планировщик будет пытаться не размещать на узле поды, но это не гарантировано.
Что бы игнорировать taint node-role.kubernetes.io/control-plane:NoSchedule для подов DaemonSet необходимо добавить в манифест толерантность к конкретному типу taint в спецификации пода, например:
spec:
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
Если мы не указываем значение ключа (value), operator должен быть установлен в Exists.
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints- посмотреть taint'ы на нодах- Добавить taint
- Чтобы снять taint, добавить в конце команды
-
NodeSelector#
Если необходимо разместить поды на строго определённых нодах кластера, в этом случае можно использовать nodeselector. В качестве параметра, используемого для отбора нод, можно указать метки (labels), установленные на нодах.
kubectl get nodes --show-labels- метки на нодахkubectl label nodes <node_name> test=test- добавить метку на нодуkubectl label nodes <node_name> test=test-- снять метку с ноды
Toleration#
Toleration не гарантирует, что под будет размещен на помеченном узле. Он лишь разрешает это. Решение все равно принимает планировщик на основе других факторов (достаточно ли ресурсов и т.д.).
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: my-app
image: nvidia/cuda:11.0-base
resources:
limits:
nvidia.com/gpu: 1
# Ключевая секция:
tolerations:
- key: "gpu" # Должен совпадать с key taint'а
operator: "Equal" # Оператор сравнения. "Equal" или "Exists"
value: "true" # Должен совпадать с value taint'а (если operator=Equal)
effect: "NoSchedule" # Должен совпадать с effect taint'а
operator: "Equal" # точное совпадение по value
operator: "Exists" # Toleration сработает для любого taint'а с указанными key и effect. Значение value в этом случае указывать не нужно
Job#
Deployment, например, предназначен для запуска долгоживущих процессов (веб-сервер), которые должны работать постоянно (running), их цель быть всегда доступными
Job предназначен для запуска одноразовых задач, которые должны выполниться и завершиться успешно (Succeeded), их цель - выполнить работу и прекратить существование
apiVersion: batch/v1
kind: Job
metadata:
name: example-job
spec:
# Шаблон пода, который будет выполнять работу
template:
spec:
containers:
- name: worker
image: busybox
command: ["echo", "Hello, Kubernetes Job!"]
restartPolicy: Never # или OnFailure. Для Job НЕ допускается Always.
# Количество успешных завершений, необходимое для успеха всей Job
completions: 1 # (по умолчанию 1)
# Количество Pod'ов, которые могут работать параллельно для достижения цели
parallelism: 1 # (по умолчанию 1)
# Политика перезапуска подов при failure
backoffLimit: 6 # (по умолчанию 6) Макс. количество попыток перезапуска пода
# Таймаут для Job в секундах. Если Job выполняется дольше - она будет убита.
activeDeadlineSeconds: 3600
Как работает Job?
- Вы создаете объект Job (например, через kubectl apply -f job.yaml).
- Job-контроллер видит новую задачу и создает один или несколько Pod'ов на основе template.
-
Контроллер следит за состоянием Pod'ов.
- Успех: Если под завершается с кодом выхода 0, это считается успешным завершением (Succeeded).
- Неудача: Если под завершается с ненулевым кодом выхода, он считается неудачным (Failed).
-
Логика перезапуска:
- Если restartPolicy: OnFailure, kubelet перезапустит контейнер внутри того же пода.
- Если restartPolicy: Never, Job-контроллер создаст новый под.
-
Job продолжает создавать новые поды (с экспоненциальной задержкой, чтобы не заспамить кластер), пока не будет достигнуто либо:
- Успешное завершение количества подов, указанного в completions.
-
Превышено количество попыток backoffLimit — тогда вся Job помечается как Failed.
-
kubectl apply -f job.yaml- создать job из файла kubectl get jobs- список джобовkubectl describe job <job-name>- свойства джобаkubectl logs <pod-name>- логи конкретного пода-
kubectl delete job <job-name>- удалить Job (автоматически удалит и все его Pod'ы) -
Пример манифеста Job
CronJob#
CronJob — это контроллер, который управляет Job'ами, он создает объекты Job по расписанию, используя синтаксис cron
apiVersion: batch/v1
kind: CronJob
metadata:
name: example-cronjob
spec:
# Самое главное: расписание в формате cron
schedule: "*/5 * * * *"
# Шаблон для создания Job
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
command: ["echo", "Hello from CronJob!"]
restartPolicy: OnFailure
# Сколько последних успешных Job хранить в истории
successfulJobsHistoryLimit: 3 # (по умолчанию 3)
# Сколько последних неудачных Job хранить в истории
failedJobsHistoryLimit: 1 # (по умолчанию 1)
# Что делать, если новый запуск по расписанию наступает, а предыдущая Job все еще работает
concurrencyPolicy: Allow # Разрешить параллельные запуски. Другие значения: "Forbid" (запретить), "Replace" (заменить текущую).
# Приостановить работу CronJob (не создавать новые Job), не удаляя уже работающие Job
suspend: false # по умолчанию
kubectl apply -f cronjob.yaml- создать/обновить CronJobkubectl get cronjobs- посмотреть CronJobkubectl get cj- посмотреть CronJobkubectl get jobs- посмотреть Job, созданные CronJobkubectl patch cronjob <cronjob-name> -p '{"spec":{"suspend":true}}'- приостановить CronJobkubectl patch cronjob <cronjob-name> -p '{"spec":{"suspend":false}}'- возобновить CronJobkubectl delete cronjob <cronjob-name>- удалить CronJob (удаляет сам CronJob, но НЕ удаляет созданные им Job)-
kubectl create job --from=cronjob/<cronjob-name> <manual-job-name>- принудительно запустить CronJob немедленно, не дожидаясь расписания -
Пример манифеста CronJob
apiVersion: batch/v1 kind: CronJob metadata: name: nightly-report spec: schedule: "0 2 * * *" # Каждый день в 2:00 ночи successfulJobsHistoryLimit: 2 jobTemplate: spec: template: spec: containers: - name: report-generator image: python:3.9 command: ["python", "/app/generate_daily_report.py"] restartPolicy: OnFailure
Affinity#
Основные вижы Affinity
Node Affinity- привязка пода к определенным характеристикам нодыInter-Pod Affinity/Anti-Affinity- привязка пода к другим подам или отталкивание от них
Node Affinity#
requiredDuringSchedulingIgnoredDuringExecution- Жесткое правило ("Должен"). Под обязательно будет размещен на узле, удовлетворяющем условию. Если подходящего узла нет, под останется в статусе PendingpreferredDuringSchedulingIgnoredDuringExecution- Предпочтение ("Желательно"). Планировщик попытается найти узел, удовлетворяющий условию. Если не найдет - разместит под на любом другом подходящем узле
Часть
IgnoredDuringExecutionозначает, что если метки на узле изменятся после того, как под уже был размещен, это не приведет к выселению пода
Операторы (operator) в matchExpressions:
- In - значение метки узла находится в указанном списке
- NotIn - значение метки узла НЕ находится в указанном списке
- Exists- узел имеет метку с указанным ключом (значение не важно)
- DoesNotExist - у узла НЕТ метки с указанным ключом
- Gt (Greater than),Lt (Less than) - для числовых значений
- Пример манифеста
apiVersion: v1 kind: Pod metadata: name: my-app-pod spec: containers: - name: my-app image: my-app:latest affinity: nodeAffinity: # ЖЕСТКОЕ правило: под должен быть размещен на узле с меткой 'disktype=ssd' requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: disktype operator: In values: - ssd # ПРЕДПОЧТЕНИЕ: и желательно, чтобы это был быстрый NVMe SSD preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 # Относительный вес (важность) среди других предпочтений (1-100) preference: matchExpressions: - key: ssd-type operator: In values: - nvme
Inter-Pod Affinity/Anti-Affinity#
Позволяет указывать правила размещения пода относительно других подов.
Pod Affinity- "Размести этот под рядом/на том же узле, что и эти другие поды"Pod Anti-Affinity- "Размести этот под подальше/на другом узле, от этих других подов"
Ключевые понятия:
-
topologyKey- указывает домен, в котором применяется правило, это метка узла. Может использоватьсяkubernetes.io/hostname(правило применяется в пределах одного узла) илиtopology.kubernetes.io/zone(правило применяется в пределах одной зоны доступности) -
ПРИМЕР. Разместить реплики одного приложения на разных узлах для повышения отказоустойчивости.
apiVersion: apps/v1 kind: Deployment metadata: name: my-web-app spec: replicas: 3 selector: matchLabels: app: my-web-app template: metadata: labels: app: my-web-app # По этой метке будем искать другие поды spec: containers: - name: web image: nginx:latest affinity: podAntiAffinity: # ЖЕСТКОЕ правило: не размещать два пода с app=my-web-app на одном узле requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - my-web-app topologyKey: kubernetes.io/hostname # Где применять Affinity
Affinity-правила могут быть сложными, полезно комментировать их в манифестах
В итоге:
Taint- это свойство ноды, которое действует как ограничение,сообщает планировщику кубера (kube-scheduler), что на этом узле запрещено пускать любые поды, которые не имеютTolerationк даннойTaintToleration- это свойство пода, которое дает ему право быть запланированным на узле с определеннымTaint, несмотря на ограничениеAffinity- это набор правил для пода, которые позволяют ему притягиваться к узлам или другим подам с определенными характеристиками
Pod Topology Spread Constraints#
Для равномерного распределения подов между зонами
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: *name
app.kubernetes.io/instance: *instance
app.kubernetes.io/version: *version
nodeAffinityPolicy: Ignore
nodeTaintsPolicy: Honor
Параметры topologySpreadConstraints
maxSkew- максимальная разница количества подов между доменами топологииtopologyKey- метка на ноде кластера, которая используется для определения доменов топологииwhenUnsatisfiable- что делать с подом, если он не соответствует ограничениюDoNotSchedule- (по умолчанию) запрещает планировщику запускать под на нодеScheduleAnyway- разрешает запускать под на ноде
labelSelector- определяет список меток подов, попадающих под это правилоnodeAffinityPolicy- определят будут ли учитыватьсяnodeAffinity/nodeSelectorпода при расчёте неравномерности распределения подаHonor- (по умолчанию) в расчёт включаются только ноды, соответствующиеnodeAffinity/nodeSelectorIgnore- в расчёты включены все ноды
nodeTaintsPolicy- аналогичноnodeAffinityPolicy, только учитываютсяTaintsHonor- Включаются ноды без установленныхTaints, а так же ноды для которых у пода естьTolerationIgnore- (по умолчанию) в расчёты включены все ноды.
RBAC#
RBAC - это механизм контроля доступа, который определяет:
- Кто (
Subject) может выполнять - Какие действия (
Verbs) над -
Какими ресурсами (
Resources) в Kubernetes. -
Subject- Тот, кто хочет выполнить действие:User(Пользователь)Group(Группа)ServiceAccount(Сервисный аккаунт)
-
Resource- Над чем выполняется действие:pods,services,deployments,secrets,nodesи т.д.
-
Verb- Что можно делать:get,list,create,update,delete,watch,patch
ServiceAccount(Сервисный аккаунт) - Для внутрикластерной аутентификации- Существуют внутри Kubernetes
- Привязаны к namespace
- Имеют формат: system:serviceaccount:
: - Автоматически создаются для каждого namespace (default)
- Используются подами для взаимодействия с Kubernetes API
User(Пользователь) - Для внешней аутентификации- Не управляются Kubernetes
- Создаются внешними системами (сертификаты, OIDC, LDAP)
- Глобальные для всего кластера
User НЕ является объектом Kubernetes API!!!
Role - определяет набор прав в рамках одного namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
ClusterRole - определяет набор прав для всего кластера или для кластерных ресурсов
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
RoleBinding - связывает Role с Subject в рамках namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: alice
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
ClusterRoleBinding - связывает ClusterRole с Subject для всего кластера
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-nodes-global
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: node-reader
apiGroup: rbac.authorization.k8s.io
Как Pod использует ServiceAccount???
-
В Pod монтируется секрет с токеном ServiceAccount
-
Путь: /var/run/secrets/kubernetes.io/serviceaccount
-
Pod использует этот токен для аутентификации в Kubernetes API
При создании Pod без указания serviceAccountName используется default ServiceAccount
Kubernetes имеет несколько полезных встроенных ClusterRoles:
view: Просмотр большинства ресурсов (кроме Secrets, RBAC)edit: Просмотр + изменение (кроме RBAC)admin: Полный доступ в namespace (кроме resource quotas)-
cluster-admin: Полный доступ ко всему кластеру (супер-пользователь) -
Полезные команды для проверки
# Проверить, может ли текущий пользователь создавать pods kubectl auth can-i create pods # Проверить для другого пользователя kubectl auth can-i list secrets --as=system:serviceaccount:default:my-sa # Проверить в конкретном namespace kubectl auth can-i delete pods --namespace production # Посмотреть все права для текущего пользователя kubectl auth can-i --list # Найти все ClusterRoleBinding kubectl get clusterrolebindings -o wide # Найти все RoleBinding в namespace kubectl get rolebindings -n default -
Управление контекстами
# Посмотреть все контексты kubectl config get-contexts # Переключиться на другой контекст kubectl config use-context dev-context # Посмотреть текущий контекст kubectl config current-context # Создать новый контекст kubectl config set-context new-context \ --cluster=my-cluster \ --user=alice \ --namespace=production
ResourceQuota#
ResourceQuota определяет ограничения на namespace
Разное#
Labels — структурированные данные для логики Kubernetes
- для селекторов (
matchLabels,labelSelector) - для группировки объектов (например, связать
PodсReplicaSet,Service,Deployment) - участвуют в логике работы контроллеров, планировщика (
scheduler), сервисов и т.д. - нужны для фильтрации:
kubectl get pods -l app=nginx
Annotations — это метаданные, которые:
- Используются для хранения произвольной информации
- не участвуют в селекции
- используются вспомогательными компонентами:
- Ingress-контроллеры
- cert-manager
- kubectl
- Helm
- CSI (storage drivers)
- операторы
- аннотации часто используются для внутренней логики, дополнительных настроек, или даже инструкций для других систем, в том числе приложений внутри подов
kubectl describe node <node-name>- инфо о ноде кубаkubectl get pods -o wide- расширенный вывод о сущностиkubectl events- события в кластере кубера
Про ограничения ресурсов
requests- информация для schedulerlimits- идут в cgroups
Про self-healing
Если контейнер внутри пода упал, то он рестартится согласно restartPolicy
Если под упал, то он пересоздаётся
Интересно#
-
k8s - Eventually Consistent (Eventually consistent - система не гарантирует мгновенную согласованность, но гарантирует, что состояние со временем станет правильным), потому что куб постороен как набор независимых control loop (контроллеры)
-
Как удаляются ресурсы в кубе
Вот удалилы мы deployment, дальше приходит GC и видит что у rs нет owner, убивает rs, а потом и поды
-
Kubernetes Events — это временные системные сообщения, создаваемые компонентами кластера, которые объясняют изменения состояния объектов и причины ошибок
-
Kubeadm - это по сути утилита бутстрапа для ноды кубернетеса
-
Если iptables, то сервис балансирует рандомно, если ipvs, то сервис балансирует round robin
-
Сервис для l4 трафика, ингресс для l7 трафика
-
Ресурсы создаются в порядке согласно которому они определены в манифесте
-
Selector - immutable, чтобы кластер не мог перейти в некосистентное состояние. Kubernetes требует, чтобы каждый объект имел одного чёткого владельца и мы не можем менять селектор, иначе можем потерять свои поды и захватить чужие и все сломается, ЕЩЕ РАЗ, selector immutable, потому что он определяет границы владения контроллера, и его изменение может привести к потере контроля над ресурсами и конфликтам между контроллерами
-
СЕЛЕКТОР ЭТО ОБЛАСТЬ ОТВЕТСВЕННОСТИ КОНТРОЛЛЕРА
Helm#
Helm - это пакетный менеджер, шаблонизатор для Kubernetes
Template - абстракция над некоторым рекурсом K8s (Pod, Service, Ingress)
Chart - это пакет приложения
Просто папка с структурой
myapp/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ingress.yaml
└── charts/
Release - это установленный chart в кластере.
Release = Chart + Values
Helm - это не просто шаблонизатор, helm позволяет деплоить, он объединяет кучу манифестов в единую сущность релиза и позволяет ей управлять
Встроенные объекты#
.Release- Представляет информацию о текущем релизе
.Release.Name - Имя релиза (helm install myapp ./chart)
.Release.Namespace - Namespace, куда установлен релиз
.Release.Service - Сервис Helm (обычно Helm)
.Release.IsInstall - true, если это установка
.Release.IsUpgrade - true, если это апгрейд
.Release.Revision - Номер ревизии (для rollback)
.Release.Time - Время установки/обновления
.Chart- Содержит информацию о самом чарт, который устанавливаешь
TODO: дописать про built-in object
Разное#
Доллар перед точкой в helm, значит сохранять корневой контекст, пригождается в range, with и тп
Если PVC был создан Helm chart’ом, то по умолчанию он удалится вместе с релизом, но в шаблоне PVC можно добавить аннотацию и тогда Helm не удалит PVC
Архитектура#
Взаимодействие между Нодой и Контрол Плейн#
https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/
Взаимодействие (ноды, пода) идёт через API-сервер (и по умолчанию TLS)
Kubelet постоянно общается с контрол плейн:
- Сообщает статус ноды
- Сообщает статусы подов
- через watch получает задачи
То есть кубилет следит за апи сервером и приводит ноду к желаемому состоянию описанному в апи сервере
Поды тоже могут общаться с апи сервером через сервисный аккаунт, используя сервис:
Апи сервер взаимодействует с подами чтобы, например, посмотреть логи, но в основном компоненты кубера обращаются К апи серверу
Котроллер#
https://kubernetes.io/docs/concepts/architecture/controller/
Контроллер это бесконечный цикл который пытается привести текущие состояние кластера к желаемому
то есть мы имеем:
- desired state (spec) - что мы описали в yaml и сохранили через апи сервер в etcd
- current state (status) - актуально состояние кластера
Контроллер не запускает поды, он создаёт ресурсы через апи сервер
Чаще всего контроллер отвечает за один конкретный тип ресурса в апи куба (например ReplicaSet), и стандартные контроллеры находятся kube-controller-manager
Лизыыы#
https://kubernetes.io/docs/concepts/architecture/leases/
Лиз - объект в кубе который используется для координации блокировок, типа лочка с TTL
то есть
- есть лиз
- компонент держит лиз
- компонент периодически подтверждает, что он жив
- если перестал жить, ttl истекает и другой компонент забирает
Heartbeat нод работает через lease, а именно: для каждой ноды есть lease, кубилет обновляет поле spec.renewTime в объекте lease
Garbage Collection#
https://kubernetes.io/docs/concepts/architecture/garbage-collection/
GC - набор механизмов которые удаляют ненужные ресурсы из кластера
- завершённые Pod’ы
- завершённые Job’ы
- объекты без владельца
- неиспользуемые контейнеры и образы
- PV с policy = Delete
- устаревшие CSR
- мертвые ноды (в cloud)
Есть важная штука: OwnerReference - это связь родителя и зависимого объекта
Например:
то есть если удаляется владелец, gc удаляет все зависимые объекты, например, после удаления deployment удаляется replicaset, затем pod'ы
есть Finalizer - защита от преждевременного удаления
Container Lifecycle Hooks#
Хуки позволяют выполнять пользовательский код при старте (PostStart) и перед остановкой (PreStop) контейнера, что используется для инициализации и graceful shutdown
- есть только PostStart и PreStop хуки
- PostStart - после старта (асинхронно)
- PreStop - перед остановкой (блокирует shutdown)
- есть exec и HTTP handlers для выполнения хука
- hook может выполниться несколько раз
- если hook падает, то контейнер убивается
Managing workloads#
https://kubernetes.io/docs/concepts/workloads/management/
apply думает
- что изменил пользователь?
- что изменили другие?
- как объединить?
patch просто модифицирует поле
Deployment делает rollout только если меняется Pod template, то есть если текущий Pod не соответствует template, куб создает новый