### YAML Синтаксис #### Основные сущности - Скалярные значения ```yml string_value: hello number_value: 42 float_value: 3.14 boolean_true: true boolean_false: false ``` - Списки (массивы) ```yml servers: - web01 - web02 - db01 ``` или ```yml servers: [web01, web02, db01] ``` - Словари ```yml user: name: "ivan" age: 30 admin: true ``` или ```yml user: {name: ivan, age: 30, admin: true} ``` - Многострочные строки ```yml description: | Это многострочный текст. Он сохраняет переносы строк. Полезно для документации. command: > Это тоже многострочный текст, но переносы будут заменены пробелами. ``` - `|` - сохраняет всё как есть, включая `\n` - `>` - склеивает строки в одну с пробелами #### Расширенные возможности - Ссылки и якори ($, *) ```yml defaults: &default_settings retries: 3 timeout: 30 server1: <<: *default_settings timeout: 10 # переопределено server2: <<: *default_settings ``` - Линтер ```yml yamllint fine_name.yml ``` #### Разное - Null ```yml empty1: null empty2: ~ empty3: ``` - Boolean ```yml bool1: yes # интерпретируется как true bool2: no # false bool3: on # true bool4: off # false ``` чтобы явно задать строку, необходимо использовать кавычки ```yml literal_string: "yes" # не будет true ``` ### Разворот кластера Буду поднимать 1 мастер ноду, 2 воркер ноды, 1 вспомогательную (DNS, etc) #### Поднимаем ВМ для кластера !!! info "Я делаю всё от рута" - `apt update && apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils` - устанавливаем гипервизор - Создаём ВМ ```bash virt-install \ --name k8s-master \ --ram 4096 \ --vcpus 3 \ --disk path=/var/lib/libvirt/images/k8s-master.qcow2,size=20 \ --os-variant ubuntu24.04 \ --network network=default \ --graphics vnc,listen=127.0.0.1 \ --cdrom /var/lib/libvirt/images/ubuntu-24.04.2-live-server-amd64.iso \ --noautoconsole ``` - `virsh vncdisplay k8s-master` - выводит VNC-дисплей, к которому привязан указанная гостевая ОС, если запущена Пример вывода ```bash 127.0.0.1:0 ``` Значит порт: 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 вспомогательную, не забываем менять выделяемые ресурсы для ВМ ??? info "Дополнительные команды для управления ВМ" - `virsh shutdown ` - штатное выключение ВМ - `virsh destroy ` - жёсткое выключение, например, если ВМ зависла, НЕ УДАЛЯЕТ ВМ - `virsh list --all` - показать список всех виртуальных машин (включая выключенные) - `virsh start ` - запустить виртуальную машину - `virsh undefine ` - удалить ВМ из libvirt (не удаляет диск в /var/lib/libvirt/images/) - `virsh domifaddr ` - показать IP-адрес ВМ (если доступен) - `virsh dumpxml ` - вывести XML-конфигурацию ВМ - `virsh console ` - подключиться к консоли ВМ (если настроен serial-порт) - `virsh domstate ` - показать текущее состояние ВМ - `virsh autostart ` - включить автозапуск ВМ при старте хоста - `virsh autostart --disable ` - отключить автозапуск ВМ - `virsh net-list` - список виртуальных сетей libvirt - `virsh net-dumpxml default` - показать XML-конфигурацию сети default - `virsh dumpxml ` - посмотреть XML-конфиг ВМ - `virsh net-edit default` - отредактировать настройки сети (например, static DHCP) - Клонировать ВМ ```bash # hostname на клонированной вм нужно менять вручную virt-clone \ --original k8s-worker1 \ --name k8s-worker2 \ --file /var/lib/libvirt/images/k8s-worker2.qcow2 ``` #### Подготовка ВМ - Откючаем `swap`, k8s требует отключенный swap для корректной работы планировщика ```bash swapoff -a ``` !!! warn "Не забыть убрать запись из /etc/fstab" Kubernetes использует cgroups для управления CPU и памятью контейнеров. Если включен swap, ядро может игнорировать лимит памяти, потому что будет сбрасывать часть данных в swap. Это нарушает работу OOM (Out Of Memory) killer и других механизмов kubelet'а. Когда swap включён, kubelet может не "увидеть", что контейнер превысил лимит памяти. Kubelet считает, что вся доступная память — это только RAM. - Включаем модули ядра для корректной сетевой работы подов ```bash tee /etc/modules-load.d/k8s.conf < /dev/null apt update apt install -y containerd.io ``` - Kubernetes требует, чтобы containerd использовал systemd как управляющий механизм cgroups, т.е. структуру контроля ресурсов (CPU, память и т.п.) ```bash containerd config default | tee /etc/containerd/config.toml sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml systemctl restart containerd systemctl enable containerd ``` - Добавим репозиторий k8s, установим необходимые компоненты k8s ```bash # Добавить 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` (удобно для отладки) ```bash VERSION="v1.30.0" curl -LO https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz sudo tar -C /usr/local/bin -xzf crictl-$VERSION-linux-amd64.tar.gz rm crictl-$VERSION-linux-amd64.tar.gz ``` ```bash cat <> ~/.bashrc source ~/.bashrc ``` - Базовые команды ```bash crictl info # информация о рантайме crictl ps -a # список всех контейнеров crictl images # список всех образов crictl pods # список подов crictl logs # логи контейнера ``` - Автодополнение для `kubectl` ```bash source <(kubectl completion bash) echo "source <(kubectl completion bash)" >> ~/.bashrc ``` #### DNS-сервер Данный DNS-сервре настраивается для коммуникации между нодами (серверами), для организации резолва имен между сущностями кубера, кубер использует свой ДНС (CoreDNS) - Установка BIND9 ```bash apt update apt install -y bind9 bind9utils bind9-doc ``` - vi /etc/bind/named.conf.options ```bash // // ------------------------------------------------------------ // Глобальные параметры 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 ```bash // ------------------------------------------------------------ // Авторитетные зоны // ------------------------------------------------------------ // Прямая зона 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 ```bash $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 ```bash $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. ``` - Проверка синтаксиса ```bash # Проверяем синтаксис конфигурации named-checkconf # Проверяем каждую зону named-checkzone lab.local /etc/bind/zones/db.lab.local named-checkzone 122.168.192.in-addr.arpa /etc/bind/zones/db.192.168.122 ``` - Перезапуск сервиса ```bash systemctl restart named systemctl enable named ``` - Добавить на каждой ноде в конфиг netplan ```yml nameservers: search: [lab.local] addresses: [192.168.122.66, 8.8.8.8] ``` - Применить ```bash netplan apply # или, если нужен лог sudo netplan apply --debug ``` - Проверка работы DNS ```bash dig +short k8s-worker2.lab.local # prt-запись dig -x 192.168.122.192 +short ``` #### Настройка NFS ##### Настройка NFS-сервера - Устанавливаем сервер ```bash apt update apt install -y nfs-kernel-server ``` - Создаём каталог который будет экспортироваться ```bash mkdir -p /srv/nfs/k8s # пользователь без привилегий chown nobody:nogroup /srv/nfs/k8s chmod 0770 /srv/nfs/k8s ``` - `vi /etc/exports` ``` /srv/nfs/k8s 192.168.122.0/24(rw,sync,no_subtree_check,root_squash,fsid=0) ``` - `rw` - разрешает чтение и запись - `sync` - операции записи выполняются немедленно (безопасно) - `no_subtree_check` - ускоряет работу при экспорте подкаталогов - `root_squash` - если клиент заходит как root, он будет понижен до "nobody" (безопаснее) - `fsid=0`- нужен для корня экспортов в NFSv4 (в NFSv4 экспортируется только один корень) - `192.168.122.0/8` - сеть, которой разрешён доступ - Экспортировать каталог ```bash exportfs -rav # проверить exportfs -v ``` ##### Настройка NFS-клиента ```bash apt install -y nfs-common ``` - Проверить доступность сервера ```bash # показывает доступные каталоги showmount -e 192.168.122.157 ``` - Монтируем расшаренный каталог на клиент ```bash mount -t nfs4 192.168.122.157:/ /mnt ``` - Добавить в `/etc/fstab`, для автомонтирования при перезагрузке ```bash echo "192.168.122.157:/ /mnt nfs4 defaults,_netdev 0 0" | tee -a /etc/fstab ``` #### Разворачиваем кластер - Версии api, которые поддерживает установленная версия `kubeadm` ```bash kubeadm config print init-defaults | grep apiVersion ``` - `vi /etc/kubernetes/kubeadm-config.yaml` ```bash 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 ``` - Инициализация первой ноды ```bash kubeadm init --config /etc/kubernetes/kubeadm-config.yaml ``` - Если приложение долго не завершает свою работу, значит что-то пошло не так. Необходимо отменить все действия и запустить его ещё раз, но с большим уровнем отладки. ```bash kubeadm reset kubeadm init --config /etc/kubernetes/kubeadm-config.yaml -v5 ``` - Смотрим ip для доступа к кластеру ```bash kubectl cluster-info ``` - Установим драйвер сети (CNI Plugin), Cilium CNI с поддержкой multicast для разворота нод ROS2 ```bash 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 ``` - Смотрим ноды в кластере ```bash kubectl get nodes ``` - Смотрим поды на ноде ```bash kubectl get pods -A ``` - Регистрируем воркер ноды в кластере (представленная команда выводится в стандартный вывод после инициализации первой контрол ноды) ```bash kubeadm join 192.168.122.157:6443 --token xp77tx.kil97vo6tlfdqqr4 \ --discovery-token-ca-cert-hash sha256:2bec2613d6f016eee60d9e7af7bf98ef44753cbd26f11cce8d71df694bcebddf ``` ### Общее - `kubectl explain ` - дока (`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 ` - описание пода - `kubectl describe pod -n | less` - описание пода в нс - `kebectl delete pod ` или `kubectl delete -f pod.yml` - удаление пода - `k -n delete pod ` - удалить под - `k get pod -n -o yaml | less` - посмотреть полный манифест пода - `kubectl -n logs ` - логи пода - `kubectl -n logs -c ` - логи последнего контейнера !!! info "Разница между `create` и `apply`" `create` создаёт ресурс только если его ещё нет, если ресурс уже существует — выдаёт ошибку `apply` cоздаёт ресурс, если его нет,или обновляет, если он уже существует, поддерживает историю изменений, идемпотентен ```yml # пример описания пода --- 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 пода ```bash kubectl get pod -o jsonpath='{.status.qosClass}' ``` #### Пробы - Если проба УСПЕШНА: - `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 ` - создать нс - `kubectl delete ns ` - удалить нс - `k get ns ` - `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 -rs` - удалить rs - `k delete replicaset --all` - удалить все rs в ns - `k describe replicaset ` - `k scale --replicas 3 replicaset ` - заскейлисть репликасет - `k set image replicaset =` - обновить образ контейнера (но нужно пересоздать поды, replicaset не решает проблему обновления приложения, rs просто поддерживает заданное количество подов, задачу обновления решает абстрацкия deployment) - Пример конфигурации ```yaml apiVersion: apps/v1 kind: ReplicaSet metadata: name: myapp-rs spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp-container image: nginx ``` ### Deployment Абстракция, которая управляте replicasetами и podами Deployment предназначен для stateless приложений - создаёт и управляет ReplicaSet'ом - Rolling updates — обновляет приложения без простоя - Откат (rollback) к предыдущей версии - Масштабирование (scale up/down) - Самовосстановление (если Pod удалён или упал) - Пример `deployment` ```yml apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp-container image: nginx:1.25 ports: - containerPort: 80 ``` - `spec.selector` - определяет за какие поды отвечает Deployment - `kubectl rollout restart` - перезапуск Deployment #### Обновление - создаваёт новый ReplicaSet с новой версией образа - постепенно увеличивает количество новых Pod'ов и уменьшает старые - следит, чтобы всегда было достаточно доступных реплик - Пример обновления образа ```bash kubectl set image deployment/myapp myapp-container=nginx:1.26 ``` - Откат на предыдущую версию deployment (на ту версию, которая была применена до последнего успешного обновления) ```bash kubectl rollout undo deployment myapp ``` - Проверка состояния ```bash kubectl rollout status deployment myapp kubectl get deployment kubectl describe deployment myapp ``` - При каждом изменении (kubectl apply, set image, scale, и т.п.) создаётся новая ревизия, по умолчанию куб хранит 10 ревизий ```bash # посмотреть историю ревизий kubectl rollout history deployment myapp # откатиться к ревизии kubectl rollout undo deployment myapp --to-revision=3 ``` ### Service Сущность, которая предоставляет постоянную сетевую точку доступа к группе Pod'ов - `kubectl get endpoints my-service` - оказывает IP-адреса Pod'ов, к которым направляет трафик Service my-service - `k get EndpointSlice` #### Service headless Не обеспечивает балансировку трафика к подам (нет ClusterIP), позволяет обращаться к поду по его доменному имени, используется с Statefulset, т.к. поды "статичны" ### Statefulset Крнтроллер, похожий на Deployment гарантирует уникальность имени пода, порядок запуска, рестарта, удаления пода, постоянство ip-адреса, томов ### Тома #### emptyDir Обычно используется для: - размещения кэша файлов - данные которые необходимо хранить при сбоях в работе контейнера - обмена файлами между несколькими контейнерами в поде !!! info "При удалении пода (например, при перезапуске, обновлении, сбое узла и т.д.) — данные из emptyDir удаляются безвозвратно" - Кусочек конфига ```yaml volumeMounts: - name: empty-volume mountPath: /empty volumes: - name: empty-volume emptyDir: {} ``` #### hostPath !!! warning "Изпользовать hostPath небезопасно!!!" Контейнер получает прямой доступ к файловой системе хоста - Пример ```yaml volumes: - name: host-logs hostPath: path: /var/log/nginx type: Directory ``` - Kubernetes может проверять, существует ли путь, и что он из себя представляет ```yaml type: Directory # Должен быть каталог type: DirectoryOrCreate # Создает каталог, если его нет type: File # Должен быть файл type: FileOrCreate # Создает файл, если его нет type: Socket # Должен быть сокет type: CharDevice # Символьное устройство type: BlockDevice # Блочное устройство ``` #### ConfigMap ConfigMap - сущность, предназначенная для хранения нечувствительных данных конфигурации в виде пар ключ: значение, позволяет отделить конфигурацию от кода и применять её к контейнерам без необходимости пересборки образа. - `k get cm` - Пример ```yaml apiVersion: v1 kind: Pod metadata: name: example-pod spec: containers: - name: app image: myapp:latest envFrom: - configMapRef: name: my-config ``` --- - Пример ```yaml apiVersion: v1 kind: ConfigMap metadata: name: my-config data: APP_MODE: production LOG_LEVEL: debug ``` - Передача переменных окружения из ConfigMap в Pod ```yaml apiVersion: v1 kind: Pod metadata: name: configmap-demo spec: containers: - name: app image: busybox command: ["sh", "-c", "env"] env: - name: APP_MODE valueFrom: configMapKeyRef: name: my-config key: APP_MODE - name: LOG_LEVEL valueFrom: configMapKeyRef: name: my-config key: LOG_LEVEL ``` - Если переменная уже определена через env, она не будет перезаписана envFrom. - Можно использовать сразу несколько envFrom (например, ConfigMap и Secret). - Если переменная в ConfigMap содержит недопустимые символы (например, точки или тире), она не будет импортирована как env. #### Secret Секрет - это объект, который содержит небольшое количетсво конфиденциальных даннх - `k get secret` - `k get secret -o yaml` - Типы секрета - `generic` (Opaque) - пароли/токены для приложений - `docker-registry` - данные авторизации в docker registry - `tls` - TLS сертификаты - Пример ```yaml apiVersion: v1 kind: Secret metadata: name: my-secret type: Opaque data: username: YWRtaW4= # base64 от 'admin' password: MWYyZDFlMmU2N2Rm # base64 от '1f2d1e2e67df' ``` - Для удобства админитратора есть поле `strigData`, когда манифест примениться содержимое будет закодировано в base64 ```yaml apiVersion: v1 kind: Secret metadata: name: my-secret type: Opaque stringData: username: admin password: s3cr3t ``` - Так подключается в манифест ```yaml env: - name: username valueFrom: secretKeyRef: name: my-secret key: username ``` !!! warning "" При добавлении новых секретов, необходимо помнить про правила мерджа манифестов, аннотацию `kubectl.kubernetes.io/last-applied-configuration` - Добавление секретов в контейнер в виде тома ```yaml apiVersion: v1 kind: Pod metadata: name: secret-volume-pod spec: containers: - name: app image: alpine command: ["/bin/sh", "-c", "cat /etc/secret/* && sleep 3600"] volumeMounts: - name: secret-volume mountPath: /etc/secret readOnly: true volumes: - name: secret-volume secret: secretName: my-secret ``` ```bash # внутри контейнера cat /etc/secret/username # выведет: user cat /etc/secret/password # выведет: password ``` #### downwardAPI `downwardAPI` позволяет передать метаданные `Pod'а`(например, имя пода, namespace, labels, annotations, ресурсы) в контейнер через переменные окружения или файлы. - Пример (как том (файлы)) ```yaml 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 ``` - Пример (как переменные окружения) ```yaml env: - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: MY_CPU_LIMIT valueFrom: resourceFieldRef: resource: limits.cpu ``` #### projected `projected` - это том, который объединяет несколько источников данных в одну директорию - `secret` - `configMap` - `downwardAPI` - `serviceAccountToken` - Пример ```yaml 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) !!! info "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` — что делать после удаления PVC - `Retain` - PV остаётся, данные сохраняются (нужно вручную очистить/перепривязать) - `Delete` - PV и данные удаляются автоматически - `Recycle` - устаревший способ (удаляет файлы, оставляет PV) - (Связывание PVC c PV) Куб находит подходящий PV по: - `storage` (размер — должен быть ≥ запроса) - `accessModes` (PV должен удовлетворять запрошенному) - `StorageClass` (если указан) !!! info "Если нет подходящего 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 в спецификации пода, например: ```yaml 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 ```bash kubectl taint nodes key=[value]:Effect kubectl taint nodes wr2.kryukov.local test-taint=:NoExecute ``` - Чтобы снять taint, добавить в конце команды `-` ```bash kubectl taint nodes wr2.kryukov.local test-taint=:NoExecute- ``` ### NodeSelector Если необходимо разместить поды на строго определённых нодах кластера, в этом случае можно использовать `nodeselector`. В качестве параметра, используемого для отбора нод, можно указать метки (labels), установленные на нодах. - `kubectl get nodes --show-labels` - метки на нодах - `kubectl label nodes test=test` - добавить метку на ноду - `kubectl label nodes test=test-` - снять метку с ноды ```yaml spec: nodeSelector: special: ds-only ``` ### Toleration Toleration не гарантирует, что под будет размещен на помеченном узле. Он лишь разрешает это. Решение все равно принимает планировщик на основе других факторов (достаточно ли ресурсов и т.д.). ```yaml 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'а ``` ```yaml operator: "Equal" # точное совпадение по value operator: "Exists" # Toleration сработает для любого taint'а с указанными key и effect. Значение value в этом случае указывать не нужно ``` ### Job Deployment, например, предназначен для запуска долгоживущих процессов (веб-сервер), которые должны работать постоянно (running), их цель быть всегда доступными `Job` предназначен для запуска одноразовых задач, которые должны выполниться и завершиться успешно (Succeeded), их цель - выполнить работу и прекратить существование ```yaml 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 ` - свойства джоба - `kubectl logs ` - логи конкретного пода - `kubectl delete job ` - удалить Job (автоматически удалит и все его Pod'ы) - Пример манифеста Job ```yaml apiVersion: batch/v1 kind: Job metadata: name: pi-calculation spec: backoffLimit: 4 template: spec: containers: - name: pi image: perl:5.34 command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restartPolicy: Never ``` ### CronJob CronJob — это контроллер, который управляет Job'ами, он создает объекты Job по расписанию, используя синтаксис cron ```yaml 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` - создать/обновить CronJob - `kubectl get cronjobs` - посмотреть CronJob - `kubectl get cj` - посмотреть CronJob - `kubectl get jobs` - посмотреть Job, созданные CronJob - `kubectl patch cronjob -p '{"spec":{"suspend":true}}'` - приостановить CronJob - `kubectl patch cronjob -p '{"spec":{"suspend":false}}'` - возобновить CronJob - `kubectl delete cronjob ` - удалить CronJob (удаляет сам CronJob, но НЕ удаляет созданные им Job) - `kubectl create job --from=cronjob/ ` - принудительно запустить CronJob немедленно, не дожидаясь расписания - Пример манифеста CronJob ```yaml 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` - Жесткое правило ("Должен"). Под обязательно будет размещен на узле, удовлетворяющем условию. Если подходящего узла нет, под останется в статусе Pending - `preferredDuringSchedulingIgnoredDuringExecution` - Предпочтение ("Желательно"). Планировщик попытается найти узел, удовлетворяющий условию. Если не найдет - разместит под на любом другом подходящем узле > Часть `IgnoredDuringExecution` означает, что если метки на узле изменятся после того, как под уже был размещен, это не приведет к выселению пода Операторы (operator) в `matchExpressions`: - `In` - значение метки узла находится в указанном списке - `NotIn` - значение метки узла НЕ находится в указанном списке - `Exists`- узел имеет метку с указанным ключом (значение не важно) - `DoesNotExist` - у узла НЕТ метки с указанным ключом - `Gt (Greater than)`,` Lt (Less than)` - для числовых значений - Пример манифеста ```yaml 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` (правило применяется в пределах одной зоны доступности) - ПРИМЕР. Разместить реплики одного приложения на разных узлах для повышения отказоустойчивости. ```yaml 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 ``` !!! tip "Affinity-правила могут быть сложными, полезно комментировать их в манифестах" В итоге: !!! info "" - `Taint` - это свойство ноды, которое действует как ограничение,сообщает планировщику кубера (kube-scheduler), что на этом узле запрещено пускать любые поды, которые не имеют `Toleration` к данной `Taint` - `Toleration` - это свойство пода, которое дает ему право быть запланированным на узле с определенным `Taint`, несмотря на ограничение - `Affinity` - это набор правил для пода, которые позволяют ему притягиваться к узлам или другим подам с определенными характеристиками #### Pod Topology Spread Constraints Для равномерного распределения подов между зонами ```yaml 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 ` - инфо о ноде куба - `kubectl get pods -o wide` - расширенный вывод о сущности - `kubectl events` - события в кластере кубера