Kubernetes для самых маленьких. Часть 1: Зачем Kubernetes

Все части серии «Kubernetes для самых маленьких»

Kubernetes — система, которая запускает контейнеры на группе машин и следит, чтобы запущено было ровно то, что описано. Звучит просто, но за этой фразой стоит смена способа думать о серверах: вместо «зайти на машину и запустить процесс» администратор пишет, каким должно быть состояние, а дальше десятки независимых циклов в кластере сами приводят мир к этому описанию и держат его таким. Эта часть — о том, откуда взялась такая модель, как устроен цикл согласования, чем декларативное управление отличается от императивного и чего Kubernetes не делает, хотя от него часто этого ждут. Команд здесь немного: учебный кластер мы поставим в части 4, а в конце этой части посмотрим, как описанное выглядит на нём.

Серия рассчитана на читателя, который знает Linux и сети. Сетевые протоколы, на которые опирается кластер, разобраны в серии «Сети для самых маленьких» — например, VXLAN, которым сетевой плагин соединяет узлы, описан в её части 18, — и там, где они понадобятся, будут ссылки на неё. Отказоустойчивый control plane на трёх мастерах с BGP — тема отдельной серии «Отказоустойчивый Kubernetes на BGP»; здесь кластер проще — один мастер и два рабочих узла, — зато каждый его компонент разобран подробно.


Один сервер и процессы

Классическая схема: на сервере стоит Ubuntu, на ней nginx, приложение на Python под gunicorn (HTTP-сервер для Python-приложений), воркер очереди, cron с задачами. Каждое приложение — юнит systemd, конфигурация лежит в /etc, код раскладывается скриптом или Ansible (системой, которая по описанию в YAML приводит серверы к нужной конфигурации через SSH), перезапуск при падении делает systemd (Restart=on-failure). Для одного-двух серверов это хорошая схема, и ей не нужно ничего больше.

Проблемы начинаются с ростом:

  • Зависимости. Два приложения хотят разные версии Python, OpenSSL или libc. Пакетный менеджер держит одну версию на систему, и обновление ради одного ломает другое. Виртуальные окружения помогают для Python, но не для системных библиотек.
  • Воспроизводимость. Сервер, который настраивали руками три года, уникален. Второй такой же собрать трудно, а сказать, чем именно он отличается от описания в Ansible, — ещё труднее.
  • Размещение. Какое приложение на каком сервере работает, решает человек. Когда серверов десять, а приложений сорок, раскладка живёт в таблице или в голове, и при добавлении нового приложения кто-то ищет, где осталась память.
  • Отказ машины. systemd перезапустит упавший процесс, но не перенесёт его на другой сервер, если упал сам сервер. Это делает дежурный, ночью.
  • Обновление без простоя. Чтобы выкатить новую версию, не прервав запросы, нужно по очереди выводить экземпляры из балансировщика, обновлять, проверять и возвращать. Это пишется скриптом под каждое приложение.

Виртуальные машины решают первые две проблемы ценой целой ОС на каждое приложение, остальные три — нет.


Контейнер и образ

Контейнер — обычный процесс Linux, запущенный в своих пространствах имён (namespaces: свой список процессов, своя сеть, свои точки монтирования, своё имя хоста) и в своей cgroup (группе с ограничениями на процессор, память, ввод-вывод). Ядро у всех контейнеров общее, отдельной ОС внутри нет. Корневая файловая система процесса берётся из образа.

Образ — набор слоёв файловой системы (tar-архивов, каждый из которых добавляет или удаляет файлы поверх предыдущего) и конфигурация запуска: команда, переменные окружения, рабочий каталог, пользователь. Формат описан спецификацией OCI (Open Container Initiative — организация, которая стандартизовала формат образа, способ запуска контейнера и протокол реестра), поэтому образ, собранный Docker, запускается чем угодно, что понимает OCI. Образы хранятся в реестре — HTTP-сервисе, откуда их скачивают по имени и тегу (nginx:1.29) или по дайджесту, SHA-256 манифеста образа, который однозначно определяет содержимое.

Запускают контейнеры среды выполнения. Нижний уровень — runc: маленькая программа, которая по описанию в формате OCI создаёт пространства имён и cgroup, переключает корень и запускает процесс. Над ней — containerd: служба, которая скачивает образы, распаковывает слои, готовит файловую систему и вызывает runc. Docker — инструмент для разработчика поверх того же containerd: он добавляет сборку образов, удобный CLI и сеть на одном хосте. В учебном кластере Docker не будет: Kubernetes говорит с containerd напрямую, как — в следующей части. Подробно сборка образов и реестр — в части 6.

Контейнер решает проблему зависимостей и воспроизводимости: всё, что нужно приложению, кроме ядра, лежит в образе, и образ неизменен. Тот же registry.example.com/demo/hello:1.0 одинаково запускается на ноутбуке разработчика и на сервере. Но размещение, перенос при отказе и обновление без простоя остаются: docker run работает на одной машине.


От контейнеров к кластеру

Если взять десять серверов с containerd, понадобится кто-то, кто:

  • выбирает, на каком сервере запустить очередной контейнер, учитывая свободные ресурсы и ограничения («не два экземпляра на одной машине»);
  • замечает, что сервер пропал, и запускает его контейнеры на других;
  • даёт контейнерам на разных серверах адреса, по которым они видят друг друга, и постоянное имя для группы одинаковых экземпляров, за которым скрывается балансировка;
  • обновляет приложение по одному экземпляру, проверяя, что новый готов принимать запросы;
  • раздаёт конфигурацию и секреты, подключает тома хранилища к той машине, где оказался контейнер.

Такая система называется оркестратором. Их было несколько: Docker Swarm (режим кластера, встроенный в Docker: простой, но небогатый возможностями), HashiCorp Nomad (планировщик задач, который умеет запускать не только контейнеры, но и обычные бинарники и виртуальные машины), Apache Mesos (менеджер ресурсов кластера, поверх которого работали отдельные планировщики; сейчас почти не развивается). Kubernetes вырос из опыта Google с внутренними системами Borg и Omega, которые десятилетие запускали все сервисы компании; открыт в 2014 году, версия 1.0 вышла в 2015-м, и проект передан фонду CNCF (Cloud Native Computing Foundation, часть Linux Foundation, которая развивает Kubernetes и проекты вокруг него). Сегодня это стандарт де-факто: облака продают его как сервис, а все окружающие инструменты — мониторинг, хранилища, балансировщики — пишутся в первую очередь под него.

Кластер Kubernetes состоит из двух видов машин. Control plane (управляющий слой) хранит описание кластера и принимает решения: что где запускать. Рабочие узлы (worker nodes) запускают контейнеры. В учебном кластере это так:

Узел Адрес Роль
k8s-master-1.example.com 192.168.10.17 control plane
k8s-node-1.example.com 192.168.10.20 рабочий узел
k8s-node-2.example.com 192.168.10.21 рабочий узел

Контейнеры в Kubernetes запускаются не по одному, а группами — подами (Pod): под — это один или несколько контейнеров, которые всегда запускаются на одном узле, делят сетевой адрес и могут делить тома. Чаще всего в поде один контейнер, и пока под можно считать «контейнером с адресом». Подробно — в части 7.


Желаемое состояние

Главная идея Kubernetes: администратор не говорит «запусти контейнер на узле 1», он описывает, что должно быть: «должно работать три экземпляра такого-то образа с такими-то параметрами». Описание сохраняется в кластере как объект — запись в базе с полями. Вот такой объект:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.29
          ports:
            - containerPort: 80

kind: Deployment — тип объекта, «приложение без состояния, которое нужно держать в нескольких одинаковых экземплярах». spec — желаемое состояние: три реплики, у каждой метка app: web и один контейнер из образа nginx:1.29. Ни слова о том, на каких узлах их запускать и что делать, если один упадёт.

У объекта есть вторая половина — status, наблюдаемое состояние. Её пишет не человек, а кластер: сколько реплик сейчас запущено, сколько готовы принимать запросы, когда что менялось. Работа Kubernetes — сводить status к spec и держать их совпадающими. Устройство объекта (apiVersion, metadata, spec, status) подробно разобрано в части 3.

Объекты хранятся в etcd — распределённом хранилище ключ-значение, — но напрямую в него никто, кроме kube-apiserver, не ходит. kube-apiserver — HTTP-сервис, через который читаются и меняются все объекты: kubectl, контроллеры, агенты на узлах — все говорят только с ним.


Цикл согласования

Кто превращает spec: replicas: 3 в три работающих контейнера? Контроллеры — программы, каждая из которых отвечает за один тип объектов и крутит один и тот же цикл, который называют циклом согласования (reconciliation loop):

 ┌──────────────┐     ┌───────────────┐     ┌─────────────────┐
 │ наблюдать:   │     │ сравнить      │     │ действовать:    │
 │ что есть     ├────►│ с желаемым    ├────►│ создать,        │
 │ сейчас       │     │ (spec)        │     │ удалить,        │
 │ (через API)  │     │               │     │ записать status │
 └──────────────┘     └───────────────┘     └────────┬────────┘
        ▲                                            ▼
        └───────────── ждать изменений ──────────────┘

На псевдокоде контроллер реплик выглядит так:

for {
    desired := rs.Spec.Replicas                       // сколько должно быть
    pods := listPods(rs.Spec.Selector)                // поды с нужными метками
    actual := countNotTerminating(pods)               // сколько есть сейчас

    if actual < desired {
        createPods(rs.Spec.Template, desired-actual)  // не хватает: создать
    } else if actual > desired {
        deletePods(pods, actual-desired)              // лишние: удалить
    }

    rs.Status.Replicas = actual                       // записать, что видим
    updateStatus(rs)
    waitForChange()                                   // событие из API или периодическая сверка
}

Три свойства этого цикла объясняют почти всё поведение Kubernetes.

Контроллер смотрит на состояние, а не на события. Он не реагирует на «под удалён» как на команду, он каждый раз заново сравнивает «сколько должно быть» и «сколько есть». Такой подход называют level-triggered — реакция на уровень, а не на фронт сигнала (edge-triggered). Если контроллер пропустил событие, был перезапущен или два события пришли одновременно, ничего не ломается: на следующем проходе он увидит ту же разницу и сделает то же действие. Отсюда устойчивость: можно перезапустить любой компонент кластера в любой момент, и он догонит реальность.

Каждый контроллер делает одно дело. Контроллер Deployment не создаёт поды — он создаёт объект ReplicaSet (набор реплик) с нужным шаблоном. Контроллер ReplicaSet не запускает контейнеры — он создаёт объекты Pod. Планировщик (scheduler) не запускает контейнеры — он вписывает в под имя узла. И только kubelet, агент на каждом узле, видя под со своим именем, просит containerd запустить контейнеры. Каждое звено читает объекты из API и пишет объекты в API; друг о друге они не знают.

 администратор
   │ kubectl apply -f web.yaml
   ▼
 ┌────────────────┐   только он читает   ┌────────────┐
 │ kube-apiserver │◄──── и пишет etcd ──►│    etcd    │
 └────────────────┘                      └────────────┘
   ▲             ▲
   │ watch       │ watch
 ┌─┴───────────┐ ┌┴────────────────────────┐
 │ контроллеры │ │ kubelet на каждом узле  │
 │ scheduler   │ │ → containerd → runc     │
 └─────────────┘ └─────────────────────────┘

Изменения доставляются подпиской. Контроллеры не опрашивают API каждую секунду: они открывают к kube-apiserver долгое соединение watch и получают события «объект добавлен, изменён, удалён» по мере появления. Поэтому реакция на изменение почти мгновенная, а нагрузка на API небольшая. Как устроен watch — в части 3.

Посмотрим, как цепочка отрабатывает отказ узла. Пусть k8s-node-2 внезапно выключили.

  1. kubelet на каждом узле раз в 10 секунд продлевает свою «аренду» — маленький объект Lease с отметкой времени. С k8s-node-2 продления прекращаются.
  2. Контроллер жизненного цикла узлов (node lifecycle controller) через 50 секунд без продления помечает узел NotReady и вешает на него метку-запрет (taint) node.kubernetes.io/unreachable.
  3. У каждого пода по умолчанию есть допуск (toleration) к этому запрету на 300 секунд: кластер ждёт пять минут, вдруг узел вернётся после перезагрузки. После этого поды на узле помечаются удаляемыми.
  4. Контроллер ReplicaSet видит, что подов, которые не удаляются, стало два вместо трёх, и создаёт новый объект Pod.
  5. Планировщик видит под без узла и назначает его на k8s-node-1.
  6. kubelet на k8s-node-1 видит новый под на своём узле и запускает контейнер.

Никто не отдавал команду «перенеси под». Каждый компонент сделал свой маленький шаг, увидев расхождение в своей зоне ответственности. Тайминги (10 секунд, 50 секунд, 5 минут) настраиваются, и почему их не стоит уменьшать без нужды, будет в части 14.


Декларативно и императивно

Управлять объектами можно тремя способами, и kubectl поддерживает все три.

Способ Пример Что передаётся
Императивные команды kubectl create deployment web --image=nginx:1.29 --replicas=3, kubectl scale deployment web --replicas=5 операция над объектом; описание нигде не сохраняется
Императивная работа с файлами kubectl create -f web.yaml, kubectl replace -f web.yaml, kubectl delete -f web.yaml объект целиком из файла; операция выбрана человеком
Декларативная работа с файлами kubectl apply -f web.yaml, kubectl apply -f каталог/ желаемое состояние; kubectl и сервер сами решают, создать, изменить или ничего не делать

Императивный подход отвечает на вопрос «что сделать»: создать, изменить, удалить. Он удобен для разовых действий и экспериментов, но результат нигде не записан: через месяц никто не скажет, почему у приложения пять реплик и кто поменял образ. Повторный kubectl create упадёт с ошибкой «уже существует».

Декларативный подход отвечает на вопрос «что должно быть». Файл web.yaml описывает итог; kubectl apply сравнивает его с тем, что сейчас в кластере, и отправляет только разницу. Повторный запуск ничего не меняет — операция идемпотентна. Файлы лежат в Git, проходят ревью, история изменений видна в git log, а разницу между репозиторием и кластером показывает kubectl diff.

Важное следствие: в декларативной модели кластер всегда возвращается к файлу. Если кто-то вручную сделал kubectl scale --replicas=5, следующий kubectl apply с replicas: 3 вернёт три. Это не ошибка, а смысл подхода: источник правды — файлы, а не текущее состояние кластера. Отсюда правило: все постоянные изменения вносятся в манифесты, ручные команды — только для диагностики и экспериментов. Высшая форма этого правила — GitOps, когда изменения в кластер вносит не человек, а контроллер, который следит за Git-репозиторием; это тема заключительной части серии.

Обратите внимание, что декларативность в Kubernetes двухуровневая. На уровне kubectl apply администратор декларирует объект. А на уровне кластера сам объект — декларация для контроллеров: Deployment не «команда запустить три пода», а постоянное требование, которое проверяется снова и снова.


Что Kubernetes делает и чего не делает

Делает:

  • Размещает поды на узлах с учётом ресурсов, меток, запретов и правил соседства (часть 14).
  • Восстанавливает упавшее: перезапускает контейнеры, пересоздаёт поды на других узлах, не направляет трафик в неготовые экземпляры (части 7 и 8).
  • Даёт сеть и обнаружение сервисов: у каждого пода свой адрес, у группы подов — постоянный виртуальный адрес и DNS-имя с балансировкой между ними (части 15–17).
  • Обновляет без простоя: постепенная замена подов с проверкой готовности и откат (часть 8).
  • Раздаёт конфигурацию и секреты в виде файлов и переменных окружения (часть 13).
  • Подключает хранилище: запрашивает том у внешней системы и монтирует его на тот узел, где оказался под (части 22–26).
  • Масштабирует: меняет число реплик по нагрузке (часть 33).
  • Расширяется: свои типы объектов и свои контроллеры к ним (часть 29) — так устроены почти все дополнения, от Calico до cert-manager, который выпускает и продлевает TLS-сертификаты.

Не делает:

  • Не PaaS. PaaS (platform as a service, «платформа как сервис») вроде Heroku — облачной платформы, куда разработчик отправляет код командой git push, — берёт исходный код и сама решает, как его собрать, запустить, подключить базу и логи. Kubernetes запускает готовые образы и даёт строительные блоки, из которых такую платформу можно собрать, но сам ею не является.
  • Не собирает код и образы. Сборка — дело CI-системы (continuous integration: сервис, который на каждый коммит собирает и тестирует код) и инструментов вроде Docker или Buildah — сборщика OCI-образов, которому не нужен работающий демон (часть 6). Kubernetes только скачивает образ из реестра.
  • Не хранит данные приложений. etcd хранит только объекты кластера — описания, а не данные. Файлы и базы приложений лежат во внешнем хранилище: на файловых серверах NFS и SMB (протоколы сетевых файловых систем из мира Unix и Windows), на блочных устройствах, в распределённых системах хранения, а Kubernetes лишь подключает его к подам через драйверы CSI (Container Storage Interface — стандартный интерфейс между оркестратором и системой хранения). Если хранилища нет, данные пода живут ровно столько, сколько сам под.
  • Не реализует сеть сам. Модель сети описана, а реализует её сетевой плагин CNI (Container Network Interface — стандарт, по которому среда выполнения вызывает внешнюю программу, чтобы подключить контейнер к сети). В учебном кластере это Calico — сетевой плагин, который раздаёт подам адреса, прописывает маршруты между узлами и применяет сетевые политики. Без плагина поды не получат адресов.
  • Не даёт балансировщик снаружи на голом железе. В облаке сервис типа LoadBalancer получает внешний адрес от облака; в своей сети для этого нужен отдельный компонент, у нас — MetalLB (часть 18).
  • Не делает приложение отказоустойчивым. Он перезапустит экземпляр, но если приложение хранит сессии в памяти или база не умеет реплики, три пода не помогут. Отказоустойчивость состояния — забота приложения или оператора, который им управляет.
  • Не управляет самими узлами. Обновление ядра, пакетов, настройка ОС — по-прежнему задача администратора и средств вроде Ansible.
  • Не собирает логи и метрики в хранилище. Он отдаёт логи контейнеров и базовые метрики, а хранить и показывать их — дело Prometheus (система сбора и хранения метрик), Loki (хранилище логов) и подобных систем (части 33 и 34).

И честное замечание о цене. Kubernetes — это минимум три-пять служб управления, сетевой плагин, сертификаты, обновления раз в несколько месяцев и новая модель мышления для команды. Для одного сервиса на одной машине systemd или Docker Compose (описание нескольких контейнеров одного хоста в одном файле) проще и надёжнее. Kubernetes окупается, когда машин и приложений больше, чем удобно держать в голове, или когда нужны самовосстановление и обновления без простоя.


Смотрим руками

Ниже — как это выглядит на учебном кластере, который мы соберём в части 4. Команды выполняются на рабочей станции администратора с kubectl, неймспейс default.

Создаём Deployment из манифеста выше и смотрим, что появилось:

$ kubectl apply -f web.yaml
deployment.apps/web created

$ kubectl get deployment,replicaset,pod -l app=web -o wide
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES       SELECTOR
deployment.apps/web   3/3     3            3           18s   nginx        nginx:1.29   app=web

NAME                             DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES       SELECTOR
replicaset.apps/web-7c5ddbdf54   3         3         3       18s   nginx        nginx:1.29   app=web,pod-template-hash=7c5ddbdf54

NAME                       READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
pod/web-7c5ddbdf54-4xkqp   1/1     Running   0          18s   172.20.109.66   k8s-node-1   <none>           <none>
pod/web-7c5ddbdf54-9mtzr   1/1     Running   0          18s   172.20.140.3    k8s-node-2   <none>           <none>
pod/web-7c5ddbdf54-qh2lw   1/1     Running   0          18s   172.20.109.67   k8s-node-1   <none>           <none>

Цепочка видна в именах: Deployment web создал ReplicaSet web-7c5ddbdf54 (суффикс — хеш шаблона пода), а тот — три пода с ещё одним случайным суффиксом. Планировщик разложил их по двум рабочим узлам, адреса выданы Calico из сети подов 172.20.0.0/16.

Удалим один под и посмотрим, как контроллер реплик восстанавливает число. Во втором терминале запущено наблюдение kubectl get pods -l app=web -w:

$ kubectl delete pod web-7c5ddbdf54-9mtzr
pod "web-7c5ddbdf54-9mtzr" deleted

$ kubectl get pods -l app=web -w
NAME                   READY   STATUS              RESTARTS   AGE
web-7c5ddbdf54-4xkqp   1/1     Running             0          2m
web-7c5ddbdf54-9mtzr   1/1     Running             0          2m
web-7c5ddbdf54-qh2lw   1/1     Running             0          2m
web-7c5ddbdf54-9mtzr   1/1     Terminating         0          2m
web-7c5ddbdf54-z8v6d   0/1     Pending             0          0s
web-7c5ddbdf54-z8v6d   0/1     Pending             0          0s
web-7c5ddbdf54-z8v6d   0/1     ContainerCreating   0          0s
web-7c5ddbdf54-z8v6d   1/1     Running             0          2s

Новый под z8v6d появился в ту же секунду, когда старый получил отметку об удалении: контроллер не ждёт, пока старый завершится, он считает только поды, которые не удаляются. Первая строка Pending — под создан, вторая — планировщик вписал узел, ContainerCreating — kubelet запускает контейнер. Что именно сделал контроллер, видно в событиях ReplicaSet:

$ kubectl describe replicaset web-7c5ddbdf54 | sed -n '/^Events:/,$p'
Events:
  Type    Reason            Age    From                   Message
  ----    ------            ----   ----                   -------
  Normal  SuccessfulCreate  2m10s  replicaset-controller  Created pod: web-7c5ddbdf54-4xkqp
  Normal  SuccessfulCreate  2m10s  replicaset-controller  Created pod: web-7c5ddbdf54-9mtzr
  Normal  SuccessfulCreate  2m10s  replicaset-controller  Created pod: web-7c5ddbdf54-qh2lw
  Normal  SuccessfulCreate  12s    replicaset-controller  Created pod: web-7c5ddbdf54-z8v6d

Теперь императивное изменение и возврат к файлу. Увеличим число реплик командой, а потом спросим, чем кластер отличается от манифеста:

$ kubectl scale deployment web --replicas=5
deployment.apps/web scaled

$ kubectl diff -f web.yaml
diff -u -N /tmp/LIVE-2817364092/apps.v1.Deployment.default.web /tmp/MERGED-1650433871/apps.v1.Deployment.default.web
--- /tmp/LIVE-2817364092/apps.v1.Deployment.default.web  2026-10-21 10:42:17.351825902 +0300
+++ /tmp/MERGED-1650433871/apps.v1.Deployment.default.web  2026-10-21 10:42:17.355825946 +0300
@@ -6,7 +6,7 @@
     deployment.kubernetes.io/revision: "1"
   creationTimestamp: "2026-10-21T07:38:02Z"
-  generation: 2
+  generation: 3
   labels:
     app: web
   name: web
@@ -14,7 +14,7 @@
   uid: 0b6f4f0e-3d5a-4a51-9a3c-7e1d2f8c6b10
 spec:
   progressDeadlineSeconds: 600
-  replicas: 5
+  replicas: 3
   revisionHistoryLimit: 10
   selector:
     matchLabels:

$ kubectl apply -f web.yaml
deployment.apps/web configured

kubectl diff спрашивает у сервера, каким стал бы объект после apply, и показывает разницу с текущим: файл хочет три реплики, в кластере пять. После apply лишние два пода будут удалены. Ручное изменение не пережило декларативного описания — так и задумано.

Последний опыт — отказ узла. Остановим kubelet на k8s-node-2, имитируя пропажу узла для control plane (контейнеры при этом продолжат работать: kubelet лишь агент, а не их родительский процесс):

user@k8s-node-2:~$ sudo systemctl stop kubelet
$ kubectl get nodes
NAME           STATUS     ROLES           AGE   VERSION
k8s-master-1   Ready      control-plane   12d   v1.36.5
k8s-node-1     Ready      <none>          12d   v1.36.5
k8s-node-2     NotReady   <none>          12d   v1.36.5

$ kubectl describe node k8s-node-2 | grep -A2 Taints
Taints:             node.kubernetes.io/unreachable:NoExecute
                    node.kubernetes.io/unreachable:NoSchedule
Unschedulable:      false

Через пять минут после пометки NotReady под на k8s-node-2 переходит в Terminating, а на k8s-node-1 появляется замена:

$ kubectl get pods -l app=web -o wide
NAME                   READY   STATUS        RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
web-7c5ddbdf54-4xkqp   1/1     Running       0          21m   172.20.109.66   k8s-node-1   <none>           <none>
web-7c5ddbdf54-qh2lw   1/1     Running       0          21m   172.20.109.67   k8s-node-1   <none>           <none>
web-7c5ddbdf54-r5n7c   1/1     Running       0          40s   172.20.109.71   k8s-node-1   <none>           <none>
web-7c5ddbdf54-z8v6d   1/1     Terminating   0          19m   172.20.140.4    k8s-node-2   <none>           <none>

Под z8v6d будет висеть в Terminating, пока kubelet на k8s-node-2 не вернётся и не подтвердит, что контейнер остановлен: кластер не знает, жив ли узел на самом деле, и не может удалить запись о поде, который, возможно, ещё работает. Возвращаем узел:

user@k8s-node-2:~$ sudo systemctl start kubelet

Через несколько секунд узел снова Ready, kubelet остановит старый контейнер, запись о поде исчезнет. С точки зрения кластера реплик всё время было три, но пять минут одна из них фактически была недоступна; длину этой паузы можно задать для каждого пода. Убираем за собой: kubectl delete -f web.yaml.


Что будет дальше в серии

Серия идёт снизу вверх. Сначала — устройство кластера: из каких компонентов он состоит, как выглядит его API изнутри, как поставить кластер kubeadm на три виртуальные машины и чем от такой установки отличаются лёгкие дистрибутивы для ноутбука. Затем — образы и реестр, потом рабочие нагрузки: под, Deployment, StatefulSet (реплики с постоянными именами и томами), DaemonSet (по поду на каждом узле), Job (задача до завершения), ресурсы и классы QoS — уровни гарантий по памяти и процессору. Дальше — конфигурация и планирование, затем сеть от модели до сертификатов и сетевых политик, хранилище с отдельными частями про NFS и SMB, упаковка манифестов в Kustomize (наложение патчей на общий набор манифестов) и Helm (пакеты шаблонов с параметрами), свои ресурсы и операторы, безопасность, наблюдаемость, обслуживание etcd и обновление кластера. Заканчивается серия GitOps, который собирает всё предыдущее в доставку изменений из Git.

Каждый объект появляется в виде YAML-манифеста и команд kubectl, а в каждой части есть раздел, где мы смотрим, что изменилось в API, на узлах и в сети.

Дальше — архитектура кластера: какие компоненты работают на мастере и на узлах, кто с кем и по каким портам говорит и что происходит между kubectl run и запущенным контейнером.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.