Перейти к содержанию

K8s

YAML Синтаксис#

Основные сущности#

  • Скалярные значения

    string_value: hello
    number_value: 42
    float_value: 3.14
    boolean_true: true
    boolean_false: false
    

  • Списки (массивы)

    servers:
      - web01
      - web02
      - db01
    
    или
    servers: [web01, web02, db01]
    

  • Словари

    user:
      name: "ivan"
      age: 30
      admin: true
    
    или
    user: {name: ivan, age: 30, admin: true}
    

  • Многострочные строки

    description: |
      Это многострочный текст.
      Он сохраняет переносы строк.
      Полезно для документации.
    
    command: >
      Это тоже многострочный текст,
      но переносы будут заменены пробелами.
    

  • | - сохраняет всё как есть, включая \n
  • > - склеивает строки в одну с пробелами

Расширенные возможности#

  • Ссылки и якори ($, *)

    defaults: &default_settings
      retries: 3
      timeout: 30
    
    server1:
      <<: *default_settings
      timeout: 10  # переопределено
    
    server2:
      <<: *default_settings
    

  • Линтер

    yamllint fine_name.yml
    

Разное#

  • Null

    empty1: null
    empty2: ~
    empty3:
    

  • Boolean

    bool1: yes   # интерпретируется как true
    bool2: no    # false
    bool3: on    # true
    bool4: off   # false
    
    чтобы явно задать строку, необходимо использовать кавычки
    literal_string: "yes"   # не будет true
    

Разворот кластера#

Буду поднимать 1 мастер ноду, 2 воркер ноды, 1 вспомогательную (DNS, etc)

Поднимаем ВМ для кластера#

Я делаю всё от рута

  • apt update && apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils - устанавливаем гипервизор

  • Создаём ВМ

    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-дисплей, к которому привязан указанная гостевая ОС, если запущена

Пример вывода

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 вспомогательную, не забываем менять выделяемые ресурсы для ВМ

Дополнительные команды для управления ВМ
  • 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 - список виртуальных сетей libvirt
  • virsh net-dumpxml default - показать XML-конфигурацию сети default
  • virsh dumpxml <vm-name> - посмотреть XML-конфиг ВМ
  • virsh net-edit default - отредактировать настройки сети (например, static DHCP)
  • Клонировать ВМ
    # hostname на клонированной вм нужно менять вручную
    virt-clone \   
    --original k8s-worker1 \
    --name k8s-worker2 \
    --file /var/lib/libvirt/images/k8s-worker2.qcow2
    

Подготовка ВМ#

  • Откючаем swap, k8s требует отключенный swap для корректной работы планировщика
    swapoff -a
    

Не забыть убрать запись из /etc/fstab

Kubernetes использует cgroups для управления CPU и памятью контейнеров. Если включен swap, ядро может игнорировать лимит памяти, потому что будет сбрасывать часть данных в swap. Это нарушает работу OOM (Out Of Memory) killer и других механизмов kubelet'а. Когда swap включён, kubelet может не "увидеть", что контейнер превысил лимит памяти. Kubelet считает, что вся доступная память — это только RAM.

  • Включаем модули ядра для корректной сетевой работы подов

    tee /etc/modules-load.d/k8s.conf <<EOF 
    overlay 
    br_netfilter 
    EOF
    modprobe overlay 
    modprobe br_netfilter 
    

  • Для корректной маршрутизации сетевого трафика

    tee /etc/sysctl.d/k8s.conf <<EOF 
    net.bridge.bridge-nf-call-ip6tables = 1 
    net.bridge.bridge-nf-call-iptables = 1 
    net.ipv4.ip_forward = 1 
    EOF
    # перечитываем конфигурации, применяем
    sysctl --system
    

  • Время на узлах должно быть синхронизировано, чтобы избежать проблем с сертификатами или ещё чего-нибудь

    apt install -y chrony
    

  • Проверить что ssh-сервис запущен

    systemctl enable --now 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, память и т.п.)

    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

    # Добавить 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 (удобно для отладки)

    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
    
    cat <<EOF | sudo tee /etc/crictl.yaml
    runtime-endpoint: unix:///run/containerd/containerd.sock
    image-endpoint: "unix:///run/containerd/containerd.sock"
    timeout: 0
    debug: false
    pull-image-on-create: false
    disable-pull-on-run: false
    EOF
    

  • Добавим алиас для команды kubectl

    echo "alias k='kubectl'" >> ~/.bashrc
    source ~/.bashrc
    

  • Базовые команды

    crictl info                      # информация о рантайме
    crictl ps -a                    # список всех контейнеров
    crictl images                   # список всех образов
    crictl pods                     # список подов
    crictl logs <container_id>      # логи контейнера
    

  • Автодополнение для kubectl

    source <(kubectl completion bash)
    echo "source <(kubectl completion bash)" >> ~/.bashrc
    

DNS-сервер#

Данный DNS-сервре настраивается для коммуникации между нодами (серверами), для организации резолва имен между сущностями кубера, кубер использует свой ДНС (CoreDNS)

  • Установка BIND9

    apt update
    apt install -y bind9 bind9utils bind9-doc
    

  • 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.
    

  • Проверка синтаксиса

    # Проверяем синтаксис конфигурации
    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
    

  • Перезапуск сервиса

    systemctl restart named
    systemctl enable named
    

  • Добавить на каждой ноде в конфиг netplan

    nameservers:
      search: [lab.local]
      addresses: [192.168.122.66, 8.8.8.8]
    

  • Применить

    netplan apply
    # или, если нужен лог
    sudo netplan apply --debug
    

  • Проверка работы DNS

    dig +short k8s-worker2.lab.local
    # prt-запись
    dig -x 192.168.122.192 +short
    

Настройка NFS#

Настройка NFS-сервера#
  • Устанавливаем сервер

    apt update
    apt install -y nfs-kernel-server
    

  • Создаём каталог который будет экспортироваться

    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 - сеть, которой разрешён доступ

  • Экспортировать каталог

    exportfs -rav
    # проверить
    exportfs -v
    

Настройка NFS-клиента#
apt install -y nfs-common
  • Проверить доступность сервера

    # показывает доступные каталоги
    showmount -e 192.168.122.157
    

  • Монтируем расшаренный каталог на клиент

    mount -t nfs4 192.168.122.157:/ /mnt
    

  • Добавить в /etc/fstab, для автомонтирования при перезагрузке

    echo "192.168.122.157:/ /mnt nfs4 defaults,_netdev 0 0" | tee -a /etc/fstab
    

Разворачиваем кластер#

  • Версии api, которые поддерживает установленная версия kubeadm

    kubeadm config print init-defaults | grep apiVersion
    

  • 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
    

  • Инициализация первой ноды

    kubeadm init --config /etc/kubernetes/kubeadm-config.yaml
    

  • Если приложение долго не завершает свою работу, значит что-то пошло не так. Необходимо отменить все действия и запустить его ещё раз, но с большим уровнем отладки.

    kubeadm reset
    kubeadm init --config /etc/kubernetes/kubeadm-config.yaml -v5
    

  • Смотрим ip для доступа к кластеру

    kubectl cluster-info
    

  • Установим драйвер сети (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 get nodes
    

  • Смотрим поды на ноде

    kubectl get pods -A
    

  • Регистрируем воркер ноды в кластере (представленная команда выводится в стандартный вывод после инициализации первой контрол ноды)

    kubeadm join 192.168.122.157:6443 --token xp77tx.kil97vo6tlfdqqr4 \
        --discovery-token-ca-cert-hash sha256:2bec2613d6f016eee60d9e7af7bf98ef44753cbd26f11cce8d71df694bcebddf 
    

Общее#

  • 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 пода

    kubectl get pod <pod-name> -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 <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 - удалить rs
  • k delete replicaset --all - удалить все rs в ns
  • k describe replicaset <name>
  • k scale --replicas 3 replicaset <name> - заскейлисть репликасет
  • k set image replicaset <name> <container_name>=<new_image_name> - обновить образ контейнера (но нужно пересоздать поды, replicaset не решает проблему обновления приложения, rs просто поддерживает заданное количество подов, задачу обновления решает абстрацкия deployment)

  • Пример конфигурации

    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

    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'ов и уменьшает старые
  • следит, чтобы всегда было достаточно доступных реплик

  • Пример обновления образа

    kubectl set image deployment/myapp myapp-container=nginx:1.26
    

  • Откат на предыдущую версию deployment (на ту версию, которая была применена до последнего успешного обновления)

    kubectl rollout undo deployment myapp
    

  • Проверка состояния

    kubectl rollout status deployment myapp
    kubectl get deployment  
    kubectl describe deployment myapp
    

  • При каждом изменении (kubectl apply, set image, scale, и т.п.) создаётся новая ревизия, по умолчанию куб хранит 10 ревизий

    # посмотреть историю ревизий
    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#

Обычно используется для:

  • размещения кэша файлов
  • данные которые необходимо хранить при сбоях в работе контейнера
  • обмена файлами между несколькими контейнерами в поде

При удалении пода (например, при перезапуске, обновлении, сбое узла и т.д.) — данные из emptyDir удаляются безвозвратно

  • Кусочек конфига
      volumeMounts:
          - name: empty-volume
            mountPath: /empty
    volumes:
    - name: empty-volume
        emptyDir: {}
    

hostPath#

Изпользовать hostPath небезопасно!!!

Контейнер получает прямой доступ к файловой системе хоста

  • Пример

    volumes:
      - name: host-logs
        hostPath:
          path: /var/log/nginx
          type: Directory
    

  • Kubernetes может проверять, существует ли путь, и что он из себя представляет

    type: Directory          # Должен быть каталог
    type: DirectoryOrCreate  # Создает каталог, если его нет
    type: File               # Должен быть файл
    type: FileOrCreate       # Создает файл, если его нет
    type: Socket             # Должен быть сокет
    type: CharDevice         # Символьное устройство
    type: BlockDevice        # Блочное устройство
    

ConfigMap#

ConfigMap - сущность, предназначенная для хранения нечувствительных данных конфигурации в виде пар ключ: значение, позволяет отделить конфигурацию от кода и применять её к контейнерам без необходимости пересборки образа.

  • k get cm

  • Пример

    apiVersion: v1
    kind: Pod
    metadata:
      name: example-pod
    spec:
      containers:
      - name: app
        image: myapp:latest
        envFrom:
        - configMapRef:
            name: my-config
    


  • Пример

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: my-config
    data:
      APP_MODE: production
      LOG_LEVEL: debug
    

  • Передача переменных окружения из ConfigMap в Pod

    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 <name> -o yaml

  • Типы секрета

    • generic (Opaque) - пароли/токены для приложений
    • docker-registry - данные авторизации в docker registry
    • tls - TLS сертификаты
  • Пример

    apiVersion: v1
    kind: Secret
    metadata:
      name: my-secret
    type: Opaque
    data:
      username: YWRtaW4=     # base64 от 'admin'
      password: MWYyZDFlMmU2N2Rm   # base64 от '1f2d1e2e67df'
    

  • Для удобства админитратора есть поле strigData, когда манифест примениться содержимое будет закодировано в base64

    apiVersion: v1
    kind: Secret
    metadata:
      name: my-secret
    type: Opaque
    stringData:
      username: admin
      password: s3cr3t
    

  • Так подключается в манифест

        env:
        - name: username
          valueFrom:
            secretKeyRef:
              name: my-secret
              key: username
    

При добавлении новых секретов, необходимо помнить про правила мерджа манифестов, аннотацию kubectl.kubernetes.io/last-applied-configuration

  • Добавление секретов в контейнер в виде тома
    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
    
    # внутри контейнера
    cat /etc/secret/username     # выведет: user
    cat /etc/secret/password     # выведет: password
    

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
    

  • Пример (как переменные окружения)

    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

  • Пример

              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 — что делать после удаления PVC

    • Retain - 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
    kubectl taint nodes <node_name> key=[value]:Effect
    kubectl taint nodes wr2.kryukov.local test-taint=:NoExecute
    
  • Чтобы снять taint, добавить в конце команды -
    kubectl taint nodes wr2.kryukov.local test-taint=:NoExecute-
    

NodeSelector#

Если необходимо разместить поды на строго определённых нодах кластера, в этом случае можно использовать nodeselector. В качестве параметра, используемого для отбора нод, можно указать метки (labels), установленные на нодах.

  • kubectl get nodes --show-labels - метки на нодах
  • kubectl label nodes <node_name> test=test - добавить метку на ноду
  • kubectl label nodes <node_name> test=test- - снять метку с ноды
spec:   
    nodeSelector:
        special: ds-only

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

    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

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 <cronjob-name> -p '{"spec":{"suspend":true}}' - приостановить CronJob
  • kubectl patch cronjob <cronjob-name> -p '{"spec":{"suspend":false}}' - возобновить CronJob
  • kubectl 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 - Жесткое правило ("Должен"). Под обязательно будет размещен на узле, удовлетворяющем условию. Если подходящего узла нет, под останется в статусе Pending
  • preferredDuringSchedulingIgnoredDuringExecution - Предпочтение ("Желательно"). Планировщик попытается найти узел, удовлетворяющий условию. Если не найдет - разместит под на любом другом подходящем узле

Часть 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 - события в кластере кубера