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 гарантирует уникальность имени пода, порядок запуска, рестарта, удаления пода, постоянство ip-адреса, томов
Тома#
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- события в кластере кубера
Интересно#
-
k8s - Eventually Consistent (Eventually consistent - система не гарантирует мгновенную согласованность, но гарантирует, что состояние со временем станет правильным), потому что куб постороен как набор независимых control loop (контроллеры)
-
Как удаляются ресурсы в кубе
Вот удалилы мы deployment, дальше приходит GC и видит что у rs нет owner, убивает rs, а потом и поды
- Kubernetes Events — это временные системные сообщения, создаваемые компонентами кластера, которые объясняют изменения состояния объектов и причины ошибок
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