Введение
Service предоставляет клиентам один стабильный адрес для набора Pod. Это особенно важно при изменении нагрузки: Deployment может добавлять или удалять реплики, тогда как идентификатор Service остаётся неизменным.
В этом задании каждый бэкенд возвращает собственное имя хоста Pod. Вы увеличите количество реплик с двух до четырёх, отправите отдельные запросы через один Service и увидите ответы от нескольких Pod. Затем вы уменьшите количество реплик обратно до двух и проследите, как Deployment и EndpointSlice синхронизируются с новым желаемым состоянием.
Это ручное горизонтальное масштабирование. Автоматическое масштабирование с помощью HPA зависит от запросов ресурсов, метрик и политики управления, поэтому его следует изучать после полного понимания поведения реплик при ручном масштабировании.
Создание наблюдаемого реплицированного приложения
Запуск среды: В этой лабораторной работе для вас запускается полноценный кластер Kubernetes. Настройка плоскости управления, узла и сетевых компонентов обычно занимает 2–3 минуты. Дождитесь полной загрузки среды, прежде чем начинать работу.
На этом шаге вы создадите два бэкенда, которые показывают, какой Pod обработал каждый запрос. Благодаря этому распределение трафика Service становится наглядным, а не абстрактным.
Первая команда с помощью cd переходит в подготовленную рабочую директорию. Следующая использует here-document: команда cat <<'EOF' > hostname-web.yaml записывает все последующие строки до закрывающего EOF в YAML-файл, заменяя его прежнее содержимое.
cd /home/labex/project/scale-lab
cat <<'EOF' > hostname-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hostname-web
spec:
replicas: 2
selector:
matchLabels:
app: hostname-web
template:
metadata:
labels:
app: hostname-web
spec:
containers:
- name: web
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c"]
args:
- mkdir -p /www; hostname > /www/index.html; exec httpd -f -p 8080 -h /www
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: hostname-web
spec:
selector:
app: hostname-web
ports:
- name: http
port: 80
targetPort: http
EOF
kubectl apply -f hostname-web.yaml
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get pods -l app=hostname-web
После закрывающего EOF команда kubectl apply -f отправляет каждый объект из файла на API-сервер. rollout status ожидает появления обоих требуемых Pod, но не более 60 секунд, а -l app=hostname-web выводит только Pod с указанной меткой.
В команде контейнера точки с запятой выполняют действия последовательно: создают /www, перенаправляют имя хоста в index.html, а затем с помощью exec делают веб-сервер главным процессом контейнера. Имена двух Pod различаются, поэтому каждый бэкенд возвращает своё значение страницы.
Определение исходного набора бэкендов
На этом шаге вы сопоставите количество реплик Deployment с числом готовых бэкендов Service.
Просмотрите три взаимосвязанных представления. get deployment показывает желаемое и готовое количество реплик; -l выбирает Pod приложения, а -o wide добавляет столбцы с IP-адресом и узлом; последний селектор меток находит EndpointSlice, созданный для этого Service:
kubectl get deployment hostname-web
kubectl get pods -l app=hostname-web -o wide
kubectl get endpointslices -l kubernetes.io/service-name=hostname-web
Deployment должен показать 2/2, а EndpointSlice — два адреса. Отправьте несколько запросов к Service из одного долгоживущего клиентского Pod.
kubectl run создаёт отдельный Pod, поскольку задан параметр --restart=Never. Разделитель -- завершает параметры kubectl; sleep 3600 — это команда контейнера, которая не даёт ему завершиться. Цикл for использует seq 1 6 для формирования шести итераций, а kubectl exec POD -- COMMAND каждый раз запускает wget внутри клиентского Pod:
kubectl run load-client --image=busybox:1.36 --image-pull-policy=IfNotPresent --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/load-client --timeout=30s
for i in $(seq 1 6); do kubectl exec load-client -- wget -qO- http://hostname-web; done
Каждый ответ содержит имя Pod. В короткой выборке вы можете увидеть одно или оба имени; Service не гарантирует строгий порядок циклического распределения запросов.
Декларативное масштабирование вверх
На этом шаге вы измените сохранённое желаемое состояние: количество реплик увеличится с двух до четырёх. Редактирование манифеста позволяет синхронизировать файл с объектом в кластере.
sed -i 's/old/new/' file заменяет совпадающий текст непосредственно в файле. Затем grep -n ищет строку replicas: и показывает её номер, позволяя быстро проверить изменение перед применением:
cd /home/labex/project/scale-lab
sed -i 's/replicas: 2/replicas: 4/' hostname-web.yaml
grep -n 'replicas:' hostname-web.yaml
kubectl apply -f hostname-web.yaml
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get deployment hostname-web
Deployment должен показать 4/4. Контроллер создал ещё два Pod, поскольку фактическое состояние не соответствовало новому желаемому количеству.
Наблюдение за расширением набора бэкендов Service
На этом шаге вы убедитесь, что неизменённый Service автоматически обнаруживает новые Pod.
Команда для EndpointSlice использует JSONPath, поскольку обычная табличная форма может сокращать подробности. range повторяет шаблон для каждой конечной точки; при каждом повторении выводятся её первый адрес, слово ready, значение готовности и перевод строки. Обратный слеш переносит одну команду оболочки на несколько отображаемых строк:
kubectl get pods -l app=hostname-web -o wide
kubectl get endpointslices -l kubernetes.io/service-name=hostname-web \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'
Теперь должно быть четыре готовых адреса. Вы не изменяли Service: его селектор продолжил соответствовать каждому готовому Pod с меткой app=hostname-web.
Сравните стабильный IP-адрес Service с расширившимся набором бэкендов:
kubectl get service hostname-web -o wide
ClusterIP остаётся неизменным, тогда как состав EndpointSlice меняется.
Наблюдение за запросами к нескольким Pod
На этом шаге вы отправите отдельные HTTP-запросы через один Service и определите, какие имена хостов Pod возвращают ответы.
Сначала rm -f удаляет старый файл результатов, если он существует; параметр -f также делает отсутствие файла безопасным. Цикл отправляет 20 запросов. >> добавляет каждое имя хоста в конец файла, не затирая предыдущие результаты. Затем конвейер передаёт отсортированные строки в uniq -c, который объединяет соседние дубликаты и добавляет перед каждым именем хоста его количество:
rm -f /tmp/hostname-responses.txt
for i in $(seq 1 20); do
kubectl exec load-client -- wget -qO- http://hostname-web >> /tmp/hostname-responses.txt
done
sort /tmp/hostname-responses.txt | uniq -c
Вы должны увидеть более одного имени хоста. Количества могут отличаться, и короткая серия запросов не обязана обратиться ко всем четырём бэкендам. Маршрутизация Service распределяет соединения, но не гарантирует идеально равномерную или упорядоченную последовательность.
Проверьте, сколько уникальных бэкендов обслужили вашу выборку. sort -u оставляет по одной копии каждого имени хоста, конвейер передаёт эти строки в wc -l, а wc -l подсчитывает количество строк:
sort -u /tmp/hostname-responses.txt | wc -l
Значение больше единицы напрямую подтверждает, что стабильный адрес Service направлял запросы к нескольким Pod.
Императивное масштабирование вниз
На этом шаге вы воспользуетесь kubectl scale, чтобы быстро изменить количество реплик в работающем кластере: с четырёх обратно до двух.
kubectl scale немедленно изменяет желаемое количество реплик в работающем Deployment. Параметр --replicas=2 задаёт новое количество; YAML-файл при этом не изменяется:
kubectl scale deployment/hostname-web --replicas=2
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get pods -l app=hostname-web
Kubernetes завершит работу двух Pod и оставит два. Дождитесь, пока набор бэкендов Service также синхронизируется.
Этот ограниченный цикл опроса выполняется не более 30 раз. На каждой итерации количество готовых конечных точек сохраняется в переменную count; jq фильтрует JSON EndpointSlice, оставляя готовые конечные точки, и возвращает длину массива. [ "$count" -eq 2 ] — числовая проверка в оболочке, && break завершает цикл при успешной проверке, а sleep 1 делает паузу перед следующей попыткой:
for i in $(seq 1 30); do
count=$(kubectl get endpointslices -l kubernetes.io/service-name=hostname-web -o json | jq '[.items[].endpoints[] | select(.conditions.ready == true)] | length')
[ "$count" -eq 2 ] && break
sleep 1
done
echo "Ready backends: $count"
Теперь работающий Deployment запрашивает две реплики, но в файле по-прежнему указано четыре. Это различие специально оставлено для следующего шага.
Синхронизация манифеста и анализ событий контроллера
На этом шаге вы приведёте сохранённый манифест в соответствие с текущим состоянием из двух реплик и свяжете действия масштабирования с записями контроллера.
Сначала сравните два желаемых состояния. grep -n показывает строку в сохранённом файле; JSONPath извлекает только текущее поле .spec.replicas и добавляет перевод строки:
grep -n 'replicas:' /home/labex/project/scale-lab/hostname-web.yaml
kubectl get deployment hostname-web -o jsonpath='Live replicas: {.spec.replicas}{"\n"}'
Измените в файле значение с четырёх обратно на два и примените его:
sed -i 's/replicas: 4/replicas: 2/' /home/labex/project/scale-lab/hostname-web.yaml
kubectl apply -f /home/labex/project/scale-lab/hostname-web.yaml
Поскольку в текущем состоянии уже указаны две реплики, это применение манифеста не должно создавать новые Pod. Просмотрите события Deployment. Конвейер передаёт полный вывод describe команде sed -n; /Events:/,$p означает «вывести всё начиная со строки, содержащей Events:, до конца»:
kubectl describe deployment hostname-web | sed -n '/Events:/,$p'
Найдите сообщения ScalingReplicaSet, отражающие решения об увеличении и уменьшении масштаба. В завершение удалите временный клиент. Параметр --ignore-not-found позволяет успешно выполнить очистку, даже если Pod уже исчез:
kubectl delete pod load-client --ignore-not-found
Ручное масштабирование изменяет желаемое количество реплик; контроллер Deployment создаёт или завершает работу Pod, а Service автоматически отслеживает готовые элементы набора. Синхронизация манифеста предотвращает неожиданное восстановление старого количества реплик при последующем выполнении kubectl apply.
Итоги
Вы вручную изменили количество реплик Deployment с двух до четырёх, а затем обратно до двух. Вы наблюдали, как контроллер синхронизирует Pod, как состав EndpointSlice отслеживает готовые бэкенды без изменения Service, а также получили прямое подтверждение того, что один адрес Service направлял отдельные запросы к нескольким Pod.
Основная модель такова: количество реплик — это желаемое состояние, контроллер Deployment приводит фактический набор Pod в соответствие с ним, а Service отслеживает готовые бэкенды с нужными метками. Декларативные файлы следует согласовывать с осознанными изменениями в работающем кластере, чтобы последующие применения манифестов оставались предсказуемыми.


