K8s
YAML Синтаксис#
Основные сущности#
-
Скалярные значения
-
Списки (массивы)
или -
Словари
или -
Многострочные строки
|
- сохраняет всё как есть, включая\n
>
- склеивает строки в одну с пробелами
Расширенные возможности#
-
Ссылки и якори ($, *)
-
Линтер
Разное#
-
Null
-
Boolean
чтобы явно задать строку, необходимо использовать кавычки
Разворот кластера#
Буду поднимать 1 мастер ноду, 2 воркер ноды, 1 вспомогательную (DNS, etc)
Поднимаем ВМ для кластера#
Я делаю всё от рута
-
apt update && apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
- устанавливаем гипервизор -
Создаём ВМ
-
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 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.yaml
apiVersion: 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
- это том, который объединяет несколько источников данных в одну директорию
secret
configMap
downwardAPI
-
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): один Pod может писать (самый частый случай)ReadOnlyMany
(ROX): много Pod-ов читаютReadWriteMany
(RWX): несколько Pod-ов могут читать и писать (например, NFS)
-
persistentVolumeReclaimPolicy
— что делать после удаления PVCRetain
- PV остаётся, данные сохраняются (нужно вручную очистить/перепривязать)Delete
- PV и данные удаляются автоматическиRecycle
- устаревший способ (удаляет файлы, оставляет PV)
-
(Связывание PVC c PV) Куб находит подходящий PV по:
storage
(размер — должен быть ≥ запроса)accessModes
(PV должен удовлетворять запрошенному)StorageClass
(если указан)
Если нет подходящего PV - PVC останется в состоянии Pending
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
к даннойTaint
Toleration
- это свойство пода, которое дает ему право быть запланированным на узле с определенным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
/nodeSelector
Ignore
- в расчёты включены все ноды
nodeTaintsPolicy
- аналогичноnodeAffinityPolicy
, только учитываютсяTaints
Honor
- Включаются ноды без установленныхTaints
, а так же ноды для которых у пода естьToleration
Ignore
- (по умолчанию) в расчёты включены все ноды.
Разное#
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
- события в кластере кубера