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