В первой лабораторной мы подняли кластер с помощью kind, нашли знакомые компоненты Kubernetes и запустили первый Pod hello-k8s.
Но, как вы уже знаете Kubernetes нужен не для 1 пода, а для целых распределенных систем, так что нашему приложению одного Pod мало. Нужно три, а может даже больше. Ну и естественно хочется, чтобы их падения отслеживались автоматически - мы стремимся к уменьшению ручной работы.
Во второй теоретической статье мы разбирали Deployment, ReplicaSet и принцип желаемого состояния. Теперь проверим всё это в работающем кластере
На лабораторную стоит заложить около 20–30 минут. Если образ nginx ещё не загружен, первый запуск может занять дополнительное время.
Сегодня попробуем не просто запустить несколько Pod, а своими глазами увидеть, как Kubernetes поддерживает желаемое (desired) состояние. И для этого даже немного ему помешаем.
Что понадобится
Используем те же инструменты, что и в первой лабораторной: Docker, kind и kubectl.
Если после первой лабы кластер k8s-first-lab у вас остался - отлично, ничего пересоздавать не нужно. Если успели удалить - поднимем заново, предварительно, не забывайте включить Docker Desktop, если пользуетесь Windows:
kind create cluster --name k8s-first-lab --wait 180s
Не забудьте выбрать нужное подключение:
kubectl config use-context kind-k8s-first-lab
Проверим ноду:
kubectl get nodes
В колонке STATUS должно быть Ready. Если видите NotReady, немного подождите и повторите команду.
Помните наш hello-k8s из первой лабы? Если он всё ещё живёт в кластере, давайте его уберём. Сегодня у нас будет другой подопытный:
kubectl delete pod hello-k8s --ignore-not-found

Шаг 1. Описываем приложение в YAML
В прошлый раз хватило одной команды kubectl run. На этот раз сделаем чуть серьёзнее - опишем приложение в YAML. Не пугайтесь объёма файла, большую часть полей мы уже встречали в теории.
Создайте папку для второй лабораторной и перейдите в неё:
mkdir k8s-lab-2 cd k8s-lab-2
Создайте файл deployment.yaml и добавьте в него:
apiVersion: apps/v1 kind: Deployment metadata: name: hello-deployment spec: replicas: 3 selector: matchLabels: app: hello-nginx template: metadata: labels: app: hello-nginx spec: containers: - name: nginx image: nginx:stable-alpine ports: - containerPort: 80
Да, после одной команды из прошлой лабы такой YAML выглядит внушительно. Но давайте посмотрим, за что именно мы здесь заплатили несколькими лишними строками.
Разберём основные части:
Поле | Что означает |
|---|---|
| Создаём Deployment, а не отдельный Pod |
| Имя нашего Deployment |
| Хотим три экземпляра приложения |
| По каким меткам Deployment определяет свои Pod |
| Шаблон, по которому будут создаваться Pod |
| Образ контейнера, знакомый нам по первой лабораторной |
Особое внимание обратите на две части:
selector: matchLabels: app: hello-nginx
и:
template: metadata: labels: app: hello-nginx
Значения меток совпадают. Это важно: selector Deployment должен соответствовать меткам в шаблоне Pod, мы уже обсуждали причины во второй части теоретической серии.
И ещё одна строка:
replicas: 3
Здесь и начинается самое интересное. Мы не говорим Kubernetes: «Создай три Pod и можешь отдыхать». Мы говорим: у приложения должно быть три Pod. Не только сейчас, а пока мы не изменим это требование. Иными словами - мы задали desired (желаемое) состояние нашего кластера. Kubernetes будет стараться его поддерживать

Шаг 2. Применяем YAML и смотрим на результат
Выполните из папки с файлом:
kubectl apply -f deployment.yaml
Ожидаемый ответ:
deployment.apps/hello-deployment created
Команда apply отправила описание объекта в Kubernetes API. Теперь посмотрим, что получилось:
kubectl get deployments
Пример вывода:
NAME READY UP-TO-DATE AVAILABLE AGE hello-deployment 3/3 3 3 1m
В колонке READY нас интересует 3/3: три готовых Pod из трёх желаемых. Если сразу после создания видите 0/3 или 1/3, немного подождите - контейнеры ещё запускаются.
Чтобы дождаться готовности Deployment, можно выполнить:
kubectl rollout status deployment/hello-deployment
А теперь посмотрим сами Pod:
kubectl get pods
Пример:
NAME READY STATUS RESTARTS AGE hello-deployment-xxxxxxxxxx-abc12 1/1 Running 0 1m hello-deployment-xxxxxxxxxx-def34 1/1 Running 0 1m hello-deployment-xxxxxxxxxx-ghi56 1/1 Running 0 1m
Имена у вас будут другими. Важнее, что Pod три и все они перешли в Running.
Итак, мы попросили создать один Deployment, а в результате получили три Pod. Неплохо, но кто именно проделал остальную работу?

Шаг 3. Находим ReplicaSet
Вспомним схему из теории:
Deployment -> ReplicaSet -> Pods
ReplicaSet мы своими руками не создавали. Но ведь он должен где-то быть? Проверим:
kubectl get replicasets
Пример вывода:
NAME DESIRED CURRENT READY AGE hello-deployment-xxxxxxxxxx 3 3 3 2m
Здесь три полезные колонки:
Колонка | Что показывает |
|---|---|
| Сколько Pod должно быть |
| Сколько Pod сейчас находится под управлением ReplicaSet |
| Сколько из них готовы |
Получается, Deployment создал ReplicaSet, а уже тот в свою очередь обеспечил появление трёх Pod.
Посмотрим на связь объектов подробнее:
kubectl describe deployment hello-deployment
Найдите сведения о ReplicaSet в выводе.
Теперь узнаем имя ReplicaSet:
kubectl get rs
И посмотрим его описание, подставив настоящее имя:
kubectl describe rs <имя-replicaset>
В выводе найдите строку Controlled By: Deployment/hello-deployment.
Она показывает, что ReplicaSet управляется нашим Deployment. А если выполнить kubectl describe pod <имя-pod>, в описании Pod можно найти Controlled By: ReplicaSet/.... Без уточнения имени конкретного пода она выведет информацию по всем трем существующим у нас подам.
Вот теперь схема Deployment -> ReplicaSet -> Pods перестала быть просто картинкой из теоретической статьи. Связи мы нашли прямо в кластере.

Шаг 4. Удаляем Pod и наблюдаем за Kubernetes
Перейдём к самой интересной части. Давайте попробуем немного помешать Kubernetes
Сейчас у нас три Pod. В YAML по-прежнему указано:
replicas: 3
Как думаете, что будет, если мы возьмём и удалим один из этих Pod?
Откройте второй терминал и запустите в нём:
kubectl get pods -w
Параметр -w означает watch: команда будет показывать изменения, пока мы её не остановим.
В первом терминале получим список Pod:
kubectl get pods
Скопируйте имя одного из них и удалите его:
kubectl delete pod <имя-pod>
Вернитесь во второй терминал. Вы можете увидеть, как удаляемый Pod переходит в Terminating, а новый - в Pending, затем ContainerCreating и Running.
Порядок строк и промежуточные состояния могут отличаться: Kubernetes выполняет эти действия быстро.
Проверим итог:
kubectl get pods
Снова три Pod, но у одного теперь другое имя. Почему так?
В какой-то момент фактическое состояние перестало соответствовать желаемому:
Желаемое состояние (desired state): 3 Pod Фактическое состояние (actual state): 2 Pod
ReplicaSet обнаружил нехватку и создал новый Pod. Kubernetes не восстановил старый Pod с тем же именем - он создал замену.
Собственно, ради этого эксперимента мы сюда и пришли. Kubernetes заметил расхождение и постарался вернуть всё к состоянию, которое мы описали.

Остановить наблюдение во втором терминале можно сочетанием Ctrl+C.
Шаг 5. Меняем количество реплик
С удалением разобрались. Но теперь, что делать, если мы хотим создать ещё больше реплик приложения, скажем, 5. Не будем же мы создавать новые Pod по одному.
Выполните:
kubectl scale deployment hello-deployment --replicas=5
Команду можно прочитать так: «Измени количество желаемых реплик Deployment hello-deployment на пять».
Проверим:
kubectl get deployments kubectl get pods
После завершения масштабирования ожидаем READY 5/5 и пять Pod.
Обратите внимание! Мы не создавали ещё два Pod вручную. Поменяли одно число - остальное Kubernetes сделал сам
Теперь уменьшим количество:
kubectl scale deployment hello-deployment --replicas=2
Снова посмотрим:
kubectl get pods
Через некоторое время останутся два Pod. ReplicaSet не старается держать как можно больше экземпляров - он поддерживает столько, сколько мы указали - иными словами, вернулись к циклу согласования (reconciliation loop) из теории.

Шаг 6. Возвращаемся к YAML
Тут есть небольшая ловушка. Причём довольно жизненная.
В файле deployment.yaml у нас по-прежнему записано:
replicas: 3
А в кластере после последней команды сейчас две реплики.
Команда kubectl scale поменяла количество реплик в кластере, а наш YAML об этом, конечно, ничего не знает. Сам себя файл не перепишет. Давайте вернёмся к декларативному подходу.
Откройте deployment.yaml и замените:
replicas: 3
на:
replicas: 4
Сохраните файл и выполните:
kubectl apply -f deployment.yaml
Проверим результат:
kubectl get deployments
После запуска новых Pod должно появиться READY 4/4.
Именно поэтому в реальных проектах важно понимать, где хранится актуальная конфигурация. Если вручную менять ресурсы в кластере и забывать обновлять YAML, легко запутаться.

Шаг 7. Проверяем labels и selector
В нашем YAML есть метка:
app: hello-nginx
Посмотрим, какие метки получили Pod:
kubectl get pods --show-labels
В колонке LABELS найдите app=hello-nginx.
Теперь выведем только Pod с этой меткой:
kubectl get pods -l app=hello-nginx
Параметр -l позволяет выбирать объекты по меткам.
Вот почему метки так удобны. Имена Pod после пересоздания меняются, а искать их по app=hello-nginx мы всё ещё можем. Не нужно каждый раз копировать длинное имя.
Важно: совпадение метки само по себе ещё не означает, что произвольный Pod автоматически станет частью нашего Deployment. Kubernetes также учитывает связи управления объектами.

Если что-то не получилось
Что произошло | С чего начать |
|---|---|
| Выполните |
| Проверьте отступы: в YAML они имеют значение; табуляцию лучше не использовать |
Deployment долго показывает | Выполните |
Pod имеет статус | Проверьте образ |
Команда удаления Pod сообщает | Повторите |
После | Посмотрите |
Если что-то пошло не так, не спешите удалять кластер и начинать всё заново. Для начала посмотрим, что Kubernetes пытается нам сказать:
kubectl describe pod <имя-pod>
В конце вывода найдите Events. Там часто видно, на каком этапе возникла проблема. Если контейнер уже запускался, его сообщения можно посмотреть через kubectl logs <имя-pod>.
Материалы лабы на GitHub
Материалы лабораторной на GitHub
Как и в прошлый раз, все необходимые материалы я собрал в отдельном репозитории на GitHub.
Для этой лабораторной нас интересует папка labs/02-deployment.
Там вы найдёте:
deployment.yaml - готовый манифест нашего Deployment, если не хочется переписывать его из статьи вручную.
commands.md - небольшую шпаргалку с командами, которые мы использовали.
tasks.md - задания для самостоятельной практики.
solutions.md - решения этих заданий с пояснениями.
README.md - краткую инструкцию по работе с материалами.
Советую хотя бы пару заданий из tasks.md попробовать выполнить самостоятельно, не заглядывая сразу в решения. В конце концов, ошибки в учебном кластере - это тоже часть обучения.
А если в процессе экспериментов что-то пошло не так, всегда можно вернуться к исходному deployment.yaml и начать заново.
Сам репозиторий буду постепенно пополнять по мере выхода новых лабораторных, чтобы в итоге у нас получился небольшой практический справочник по Kubernetes.
Проверьте себя
Попробуйте выполнить несколько действий без подсказок:
Покажите Deployment, ReplicaSet и Pod нашего приложения тремя командами
kubectl.Определите, сколько реплик желает Deployment и сколько Pod сейчас готовы.
Удалите один Pod и объясните, почему через некоторое время их количество восстановилось.
Измените количество реплик через YAML и примените файл.
Выведите только Pod с меткой
app=hello-nginx.
И главный вопрос: чем создание одного Pod через kubectl run из первой лабораторной отличается от создания Deployment с replicas: 3?
Если теперь можете своими словами объяснить, зачем между Deployment и Pod стоит ReplicaSet, значит, мы сегодня собрались не зря
Что делать с ресурсами после лабы
Удалим созданный Deployment:
kubectl delete -f deployment.yaml
Проверим:
kubectl get deployments kubectl get replicasets kubectl get pods
Наш Deployment и принадлежащие ему ReplicaSet и Pod должны исчезнуть. Если в кластере остались другие объекты, созданные не этой лабораторной, удалять их не нужно.
Сам кластер можно оставить для следующих занятий. Если хотите полностью удалить учебный стенд, выполните:
kind delete cluster --name k8s-first-lab
В следующей лабораторной перейдём к Service. Посмотрим, как обращаться к приложению, если его Pod могут исчезать, пересоздаваться и получать новые IP-адреса.

