Введение
Теперь вы умеете развертывать, публиковать и масштабировать приложение. Следующий операционный вопрос: как заменить версию приложения, не отключая весь сервис?
Kubernetes Deployment решает эту задачу с помощью поэтапного обновления. Когда шаблон его Pod изменяется, Deployment создает новый ReplicaSet, постепенно запускает новые Pod и удаляет старые только после того, как замена становится доступной. На протяжении всего процесса Service сохраняет один и тот же стабильный адрес.
В этой лабораторной работе вы переведете приложение NGINX с одного зафиксированного образа на другой, свяжете ревизии Deployment с ReplicaSet и Pod, намеренно выполните неудачный выпуск и откатитесь к последней работоспособной ревизии. Вы также приведете сохраненный манифест в соответствие с восстановленным состоянием кластера — это важная привычка, которая не позволит последующей команде kubectl apply повторно внести неисправность.
Проверка исходной среды
Запуск среды: В этой лабораторной работе для вас запускается полноценный кластер Kubernetes. Настройка плоскости управления, узла и сетевых компонентов обычно занимает 2–3 минуты. Дождитесь полной загрузки среды, прежде чем начинать работу.
На этом этапе вы проверите подключение к кластеру и подготовите HTTP-клиент внутри кластера.
В этой учебной среде уже запущен кластер Kubernetes, поэтому создавать его не нужно. Сначала проверьте, куда kubectl будет отправлять команды. config current-context показывает активное подключение, get node считывает состояние узла, а version сообщает версии локального клиента и доступного API-сервера:
kubectl config current-context
kubectl get node
kubectl version
Контекст и узел называются labex-v135, узел находится в состоянии Ready, а сервер сообщает о Kubernetes версии v1.35.x. Контекст — это выбранная в kubeconfig конфигурация, которая подключает kubectl к определенному кластеру, пользователю и пространству имен по умолчанию.
Теперь создайте небольшой клиентский Pod. Позже вы будете использовать его для обращения к приложению через Service. kubectl run создает Pod; --image выбирает BusyBox, --restart=Never оставляет его отдельным Pod, а разделитель -- передает команду длительного выполнения sleep 3600. Затем kubectl wait ожидает не более 30 секунд, пока Pod перейдет в состояние Ready:
kubectl run release-client --image=busybox:1.36 --image-pull-policy=IfNotPresent \
--restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/release-client --timeout=30s
Так вызывающая сторона отделяется от веб-Pod, что ближе к реальному взаимодействию между рабочими нагрузками внутри кластера.
Развертывание стабильной базовой версии
На этом этапе вы создадите заведомо работоспособную ревизию приложения, прежде чем вносить изменения.
Перед практикой обновления подготовьте стабильную рабочую ревизию. Создайте один манифест, содержащий Deployment с тремя репликами и стабильный Service типа ClusterIP.
Команда cd переходит в подготовленное рабочее пространство. Синтаксис here-document cat <<'EOF' > release-web.yaml записывает в файл все содержимое до закрывающего EOF. Строка --- разделяет два объекта Kubernetes в одном YAML-файле:
cd /home/labex/project/update-lab
cat <<'EOF' > release-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: release-web
annotations:
kubernetes.io/change-cause: "Initial release: nginx 1.26"
spec:
replicas: 3
strategy:
type: RollingUpdate
selector:
matchLabels:
app: release-web
template:
metadata:
labels:
app: release-web
spec:
containers:
- name: nginx
image: nginx:1.26-alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: release-web
spec:
selector:
app: release-web
ports:
- name: http
port: 80
targetPort: http
EOF
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s
kubectl get deployment,service,pods -l app=release-web
Deployment управляет Pod, а Service независимо выбирает их по метке. Обновление Deployment не изменит адрес Service.
Проверьте базовую версию с клиентского Pod. В kubectl exec POD -- COMMAND разделитель -- отделяет аргументы kubectl от команды, выполняемой внутри контейнера. wget -qO- в тихом режиме получает данные и выводит их в стандартный поток, а конвейер передает ответ команде head, чтобы показать только его начало:
kubectl exec release-client -- wget -qO- http://release-web | head
Декларативный выпуск нового образа
На этом этапе вы измените сохраненный шаблон Pod и позволите Deployment выполнить поэтапное обновление.
Deployment запускает выпуск, когда изменяется его шаблон Pod. Образ находится внутри этого шаблона, поэтому его изменение создает новую ревизию и новый ReplicaSet.
Измените образ и понятное человеку описание причины изменения в сохраненном манифесте. Каждая подстановка sed -i 's/old/new/' file редактирует файл непосредственно. Затем grep -nE выводит номера строк для любого из двух выражений расширенного регулярного поиска (change-cause или image:), позволяя проверить оба изменения перед применением:
cd /home/labex/project/update-lab
sed -i 's/Initial release: nginx 1.26/Release nginx 1.27/' release-web.yaml
sed -i 's/nginx:1.26-alpine/nginx:1.27-alpine/' release-web.yaml
grep -nE 'change-cause|image:' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s
kubectl apply изменяет желаемое состояние. Затем контроллер Deployment асинхронно работает, пока все три обновленные реплики не станут доступными. Для получения компактной таблицы Pod параметр -l выбирает метку приложения, а -o custom-columns сопоставляет заголовки с путями к полям объектов:
kubectl get deployment release-web
kubectl get pods -l app=release-web \
-o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[0].image,READY:.status.containerStatuses[0].ready'
Все три текущие реплики используют новый образ. После успешного завершения обновления вы также можете ненадолго увидеть Pod со старым образом в состоянии Terminating. Этот Pod больше не является требуемой репликой; Kubernetes завершает его корректное выключение в фоновом режиме.
Связь ревизий, ReplicaSet и Pod
На этом этапе вы изучите объекты, стоящие за успешным обновлением, и убедитесь, что Service оставался доступным.
Обновление завершено, но понять, что именно изменилось, полезнее, чем просто увидеть сообщение «успешно». kubectl rollout history читает сохраненные ревизии Deployment. Следующая команда get replicasets использует -l, чтобы оставить только связанные объекты, а custom-columns позволяет сравнить количество реплик и образы:
kubectl rollout history deployment/release-web
kubectl get replicasets -l app=release-web \
-o custom-columns='NAME:.metadata.name,DESIRED:.spec.replicas,CURRENT:.status.replicas,READY:.status.readyReplicas,IMAGE:.spec.template.spec.containers[0].image'
Вы должны увидеть два ReplicaSet. Новый владеет тремя Pod, а старый остается с нулевым числом реплик, чтобы его шаблон Pod был доступен для отката. Имя ReplicaSet содержит хеш, вычисленный на основе шаблона Pod, поэтому после изменения образа имена Pod изменились.
Проверьте используемый образ и количество бэкендов Service. Оба выражения JSONPath используют range, чтобы повторять шаблон вывода для каждого элемента списка. Литералы пробела и {"\n"} формируют по одной удобной для чтения строке на каждый Pod или endpoint:
kubectl get pods -l app=release-web \
-o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[0].image}{"\n"}{end}'
kubectl get endpointslices -l kubernetes.io/service-name=release-web \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'
kubectl exec release-client -- wget -qO- http://release-web | head
Ревизия изменилась, но у Service по-прежнему три готовых бэкенда и то же стабильное имя.
Диагностика неудачного выпуска
На этом этапе вы внесете контролируемую ошибку в образ и с помощью событий Pod определите, почему обновление не может завершиться.
Теперь смоделируйте распространенную ошибку при выпуске: тег образа, которого не существует. Оставьте эту неисправность в том же Deployment, чтобы механизм обновления продемонстрировал важное свойство безопасности. Две команды sed -i изменяют текст аннотации и образа непосредственно в файле. Ожидаемая команда проверки выпуска обычно завершится с ошибкой по тайм-ауту; || true намеренно позволяет продолжить выполнение задания:
cd /home/labex/project/update-lab
sed -i 's/Release nginx 1.27/Broken release: missing image/' release-web.yaml
sed -i 's/nginx:1.27-alpine/nginx:does-not-exist-course/' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=20s || true
kubectl get deployment release-web
kubectl get pods -l app=release-web
Обновление не завершается, потому что новый Pod не может загрузить свой образ. || true позволяет продолжить выполнение после ожидаемого тайм-аута.
Найдите неисправный Pod и изучите его события. Конструкция $(...) сохраняет результат команды в переменной BAD_POD. Первый конвейер передает JSON Kubernetes в jq; select(...) оставляет Pod с неисправным образом, а -r возвращает его имя в виде обычного текста. Второй конвейер с head -n1 оставляет только одно имя. Наконец, sed -n '/Events:/,$p' выводит результат describe, начиная с Events: и до конца:
BAD_POD=$(kubectl get pods -l app=release-web \
-o json | jq -r '.items[] | select(.spec.containers[0].image == "nginx:does-not-exist-course") | .metadata.name' | head -n1)
echo "$BAD_POD"
kubectl describe pod "$BAD_POD" | sed -n '/Events:/,$p'
kubectl get replicasets -l app=release-web
Ищите ErrImagePull или ImagePullBackOff. Обратите внимание, что предыдущий работоспособный ReplicaSet по-прежнему имеет доступные Pod, поэтому Service продолжает отвечать:
kubectl exec release-client -- wget -qO- http://release-web | head
Это свидетельствует о доступности во время неудачного обновления, но не доказывает работоспособность нового выпуска.
Откат и согласование манифеста
На этом этапе вы восстановите предыдущую работоспособную ревизию, а затем исправите сохраненный манифест.
У работающего Deployment есть история ревизий, поэтому Kubernetes может восстановить предыдущий шаблон Pod. rollout undo просит контроллер Deployment повторно использовать предыдущую ревизию; rollout status ожидает завершения восстановления, а custom-columns выводит восстановленный образ и количество готовых реплик:
kubectl rollout history deployment/release-web
kubectl rollout undo deployment/release-web
kubectl rollout status deployment/release-web --timeout=60s
kubectl get deployment release-web \
-o custom-columns='NAME:.metadata.name,IMAGE:.spec.template.spec.containers[0].image,READY:.status.readyReplicas'
Образ снова работоспособен, и готовы три реплики. rollout undo создает новую ревизию на основе более старого шаблона Pod; счетчик ревизий при этом не перематывается назад.
Остается выполнить еще одну задачу. В YAML-файле по-прежнему указан неисправный образ, поэтому будущая команда kubectl apply снова сломает Deployment. Синхронизируйте сохраненное желаемое состояние с восстановленным состоянием кластера. Подстановки исправляют файл, apply синхронизирует его, а последняя команда просмотра истории подтверждает получившуюся последовательность ревизий:
cd /home/labex/project/update-lab
sed -i 's/Broken release: missing image/Rollback to nginx 1.27/' release-web.yaml
sed -i 's/nginx:does-not-exist-course/nginx:1.27-alpine/' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s
grep -nE 'change-cause|image:' release-web.yaml
kubectl rollout history deployment/release-web
Теперь и кластер, и файл описывают одну и ту же работоспособную версию.
Выбор более безопасных ограничений обновления
На этом этапе вы явно зададите допустимую дополнительную емкость и ограничения доступности во время обновления Deployment.
Параметры стратегии Deployment определяют временную емкость, разрешенную во время будущих обновлений:
maxUnavailable— количество требуемых реплик, которые могут быть недоступны во время обновления.maxSurge— количество дополнительных Pod, которые могут временно существовать сверх требуемого числа реплик.
Для этого небольшого приложения с тремя репликами потребуйте, чтобы все три требуемые реплики оставались доступными, и разрешите один дополнительный Pod.
Команда sed использует адрес /type: RollingUpdate/, чтобы найти строку стратегии. Действие a\ добавляет следующие строки YAML с необходимыми отступами. После применения JSONPath извлекает оба значения стратегии, чтобы вы могли проверить изменение, не просматривая весь объект:
cd /home/labex/project/update-lab
sed -i '/type: RollingUpdate/a\ rollingUpdate:\n maxUnavailable: 0\n maxSurge: 1' release-web.yaml
kubectl apply -f release-web.yaml
kubectl get deployment release-web \
-o jsonpath='maxUnavailable={.spec.strategy.rollingUpdate.maxUnavailable}{"\n"}maxSurge={.spec.strategy.rollingUpdate.maxSurge}{"\n"}'
kubectl get deployment release-web
Изменение только параметров стратегии не заменяет Pod, поскольку шаблон Pod не изменился. При следующем обновлении образа Kubernetes сможет создать один дополнительный Pod и не должен намеренно снижать количество доступных реплик ниже трех.
Такая настройка отдает приоритет доступности, но требует свободной емкости кластера. Универсального оптимального значения не существует: для крупных приложений часто используют проценты, а реальный выбор зависит от доступной емкости, времени запуска и допустимого уровня перебоев.
Итоги
Вы проследили за полным операционным жизненным циклом выпуска приложения:
- создали работоспособный Deployment и стабильный Service;
- декларативно изменили зафиксированный образ и дождались завершения обновления;
- связали ревизии Deployment со старым и новым ReplicaSet;
- диагностировали ошибку загрузки образа, пока предыдущий выпуск продолжал обслуживать запросы;
- выполнили откат к последнему работоспособному шаблону Pod;
- синхронизировали манифест, чтобы последующее применение не восстановило неисправный образ; и
- настроили явные ограничения доступности и дополнительной емкости для будущих обновлений.
Главная идея заключается в том, что Deployment управляет желаемым состоянием во времени. Выпуск — это не просто изменение образа, а контролируемый переход между ReplicaSet, который необходимо наблюдать, проверять и при необходимости уметь отменять.


