Развернуть кластер - это день один. Дальше начинается то, ради чего платят senior-инженерам: день-2 операции. Версия Kubernetes устаревает каждые 3-4 месяца, узлы надо патчить и перезагружать под ядро, диски умирают, сертификаты протухают через год, а однажды кто-то выкатывает кривой манифест и ложит весь namespace. И тогда выясняется, есть ли у тебя бэкап etcd или нет.
Эксплуатация kubernetes - это не про "развернул и забыл". Это набор повторяемых, отрепетированных процедур, каждую из которых ты должен уметь выполнить ночью под телефонный звонок, не уронив прод. В этом уроке разберём четыре кита: апгрейд версии без даунтайма, обслуживание узлов через cordon и drain, бэкап и disaster recovery (etcd snapshot + Velero), и ротацию сертификатов. С механикой под капотом, реальным выводом команд и граблями, на которые наступали все.
Сразу важная развилка. Если у тебя managed-кластер - Yandex Managed Service for Kubernetes, VK Cloud, GKE - то апгрейд control plane и бэкап etcd за тебя делает провайдер, ты их даже не видишь. Твоя зона ответственности там сужается до апгрейда node group, drain узлов и бэкапа приложений через Velero. Если кластер self-managed (kubeadm, k3s, Deckhouse on-prem) - всё на тебе, включая etcd. Дальше я отмечаю, что где.

Апгрейд версии: skew policy и почему порядок священен
Kubernetes - распределённая система из компонентов разных версий, которые должны уметь говорить друг с другом во время апгрейда. Это описывает version skew policy, и её надо знать наизусть, иначе кластер развалится посреди процедуры.
Главные правила skew (актуально и для свежих релизов 2026):
- kube-apiserver - точка отсчёта, самый новый компонент в кластере.
- kubelet может отставать от apiserver на 3 минорных версии, но не должен быть новее. То есть apiserver 1.33 терпит kubelet 1.30-1.33.
- kube-controller-manager, kube-scheduler - на одну минорную версию старше apiserver максимум.
- kubectl - плюс-минус одна минорная версия от apiserver.
Шаг ноль, который пропускают и потом плачут - deprecated API. Каждый релиз выпиливает старые apiVersion. Классика: networking.k8s.io/v1beta1 для Ingress, policy/v1beta1 для PodDisruptionBudget давно удалены. Если в кластере или в твоих Helm-чартах остались манифесты на мёртвых версиях, после апгрейда они просто перестанут применяться, а CI выкатки начнут падать. Прогоняй проверку ЗАРАНЕЕ:
Код: Выделить всё
# pluto - сканит манифесты и Helm-релизы на deprecated/removed API
pluto detect-helm -o wide --target-versions k8s=v1.33.0
# kubent (kube-no-trouble) - то же по живым ресурсам в кластере
kubent --target-version 1.33.0
Код: Выделить всё
KIND NAMESPACE NAME API_VERSION REPLACE_WITH (SINCE)
Ingress shop web-ingress networking.k8s.io/v1beta1 networking.k8s.io/v1 (1.22.0)
PodDisruptionBudget ci runner-pdb policy/v1beta1 policy/v1 (1.21.0)
Сама процедура kubernetes upgrade на kubeadm. Делается по одному control plane узлу за раз. На первом мастере:
Код: Выделить всё
# 1. Поднимаем пакет kubeadm до целевой версии (пример для deb)
apt-get update && apt-get install -y kubeadm=1.33.1-1.1
# 2. План: kubeadm покажет, что и куда можно обновить
kubeadm upgrade plan
# 3. Применяем - kubeadm перекатывает статик-поды control plane
kubeadm upgrade apply v1.33.1
Код: Выделить всё
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.33.1". Enjoy!
Дальше - kubelet и kubectl НА КАЖДОМ узле, и вот тут впервые появляется drain (о нём ниже):
Код: Выделить всё
kubectl drain master-1 --ignore-daemonsets
apt-get install -y kubelet=1.33.1-1.1 kubectl=1.33.1-1.1
systemctl daemon-reload && systemctl restart kubelet
kubectl uncordon master-1
Код: Выделить всё
kubeadm upgrade nodeКод: Выделить всё
kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 Ready control-plane 210d v1.33.1
worker-1 Ready <none> 210d v1.33.1
worker-2 Ready <none> 210d v1.32.4 <- ещё не обновлён, это нормально по skew
Обслуживание узлов: cordon, kubectl drain и uncordon
Узел надо вывести из строя десятки раз за жизнь кластера: апгрейд kubelet, патч ядра и ребут, замена диска, вывод старого железа. Делать это грубо (просто выключить машину) нельзя - поды умрут жёстко, посреди обработки запросов. Для аккуратного вывода есть три команды.
cordon - помечает узел unschedulable. Новые поды на него больше не поедут, но существующие продолжают работать. Это "перестань мне сюда что-либо планировать". Под капотом - просто проставляется поле spec.unschedulable: true и taint node.kubernetes.io/unschedulable.
Код: Выделить всё
kubectl cordon worker-1
kubectl get node worker-1
NAME STATUS ROLES AGE VERSION
worker-1 Ready,SchedulingDisabled <none> 210d v1.33.1
Код: Выделить всё
kubectl drain worker-1 --ignore-daemonsets --delete-emptydir-data --timeout=300s
- --ignore-daemonsets - поды DaemonSet (kube-proxy, CNI-агент, агент логов) привязаны к узлу и пересоздадутся им же, выселять их бессмысленно. Без этого флага drain встанет с ошибкой.
- --delete-emptydir-data - разрешает грохнуть поды с томом emptyDir (данные в нём потеряются - ты подтверждаешь, что не против).
- --timeout - сколько ждать, прежде чем сдаться. Без таймаута зависший под подвесит тебе всю операцию.
Код: Выделить всё
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
namespace: shop
spec:
minAvailable: 2
selector:
matchLabels:
app: web
Код: Выделить всё
kubectl uncordon worker-1
Добавление и вывод узлов. Новый воркер в kubeadm-кластере вводится токеном:
Код: Выделить всё
# на control plane генерим команду join (токен живёт 24ч)
kubeadm token create --print-join-command
# на новом узле выполняем то, что она выдала:
kubeadm join 10.0.0.10:6443 --token <...> --discovery-token-ca-cert-hash sha256:<...>
Код: Выделить всё
kubectl delete node worker-9Код: Выделить всё
kubeadm resetБэкап и DR: etcd backup как единственная страховка от катастрофы
Всё состояние кластера - каждый Deployment, Secret, ConfigMap, ServiceAccount - живёт в etcd. Потеряешь etcd без бэкапа - потеряешь кластер целиком, восстановить будет неоткуда. Поэтому etcd backup это не "хорошо бы", а абсолютный минимум для self-managed. Снимок снимается одной командой с control plane узла:
Код: Выделить всё
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F-%H%M).db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
Код: Выделить всё
etcdutl snapshot status /backup/etcd-2026-06-15-0300.db -w table
+----------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| a1b2c3d4 | 4821990 | 1843 | 38 MB |
+----------+----------+------------+------------+
Код: Выделить всё
etcdutl snapshot restoreПрактика 2026: снимать каждые 2-6 часов (чаще - если много выкаток в день), хранить 7 дней локально и 30 дней в объектном хранилище (S3, Yandex Object Storage) с версионированием бакета, и обязательно гонять snapshot status в пайплайне. Версия etcdctl должна совпадать с версией работающего etcd.
Velero - второй слой, для того, что etcd не покрывает. etcd-снимок - это всё-или-ничего на уровне control plane: им нельзя восстановить один namespace и нельзя забэкапить данные в персистентных томах (PV). Здесь работает velero: он бэкапит Kubernetes-ресурсы (через apiserver, в объектное хранилище) ПЛЮС снапшотит тома через CSI. Им делают гранулярное восстановление и миграцию между кластерами.
Код: Выделить всё
# бэкап одного namespace вместе с томами
velero backup create shop-daily --include-namespaces shop --snapshot-volumes
velero backup describe shop-daily
Phase: Completed
Namespaces: included: shop
Resources: included: *
Volume Snapshots: 4 of 4 completed
Код: Выделить всё
# восстановление в тот же или другой кластер
velero restore create --from-backup shop-daily
Сертификаты и здоровье control plane
kubeadm выдаёт сертификаты компонентов сроком на один год. Это самая тихая и самая злая граница: кластер работает, никто его не трогает, и ровно через год apiserver перестаёт пускать kubelet и kubectl - "x509: certificate has expired". Прод стоит, а ты в три часа ночи гуглишь. Проверяй срок заранее:
Код: Выделить всё
kubeadm certs check-expiration
CERTIFICATE EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
apiserver Jun 14, 2026 09:12 UTC 29d no
etcd-server Jun 14, 2026 09:12 UTC 29d no
Код: Выделить всё
kubeadm certs renew all
# затем перезапустить статик-поды control plane (kubelet их подхватит)
Код: Выделить всё
kubectl get --raw='/readyz?verbose'
[+]etcd ok
[+]poststarthook/start-kube-apiserver-admission-initializer ok
[+]shutdown ok
readyz check passed
Типичные грабли и антипаттерны
- drain без PDB - выносит все реплики разом, ловишь даунтайм. Антипаттерн номер один. PDB на каждое stateful/важное приложение.
- Перепрыгнул минорную версию (1.31 -> 1.33) - нарушение skew, апгрейд ломается. Только по одной.
- Забыл прогнать pluto/kubent - после апгрейда отвалились Ingress и PDB на мёртвых API, CI красный.
- Снимок etcd есть, но его никто не проверял через snapshot status и никто не репетировал restore - в момент DR выясняется, что бэкап битый или процедура не работает.
- Сертификаты протухли через год на тихом кластере. Лечится регулярным апгрейдом или плановым certs renew.
- Сначала апгрейднул узлы, потом control plane - kubelet новее apiserver, всё развалилось. Порядок: control plane первым, всегда.
Подними тестовый kubeadm-кластер (или kind/minikube для drain-части) и пройди цикл:
- Прогони и
Код: Выделить всё
kubent, найди хоть один deprecated API.Код: Выделить всё
pluto detect-helm - Создай Deployment на 3 реплики и PDB с minAvailable: 2. Сделай узла и параллельно следи
Код: Выделить всё
kubectl drain- убедись, что приложение ни на секунду не упало ниже двух живых реплик. Потом убери PDB и повтори - почувствуй разницу.Код: Выделить всё
kubectl get pods -w - Сними etcd snapshot, проверь его через , прочитай число ключей.
Код: Выделить всё
etcdutl snapshot status -w table - Установи Velero (с MinIO или Yandex Object Storage как backend), сделай backup namespace, удали из него Deployment и восстанови через restore.
- Глянь и
Код: Выделить всё
kubeadm certs check-expiration.Код: Выделить всё
kubectl get --raw='/readyz?verbose'
- Почему control plane апгрейдят раньше воркеров и на сколько минорных версий kubelet может отставать от apiserver?
- Чем kubectl drain отличается от cordon и какую роль в drain играет PodDisruptionBudget?
- Что хранится в etcd, почему etcd snapshot - это последняя линия обороны и зачем после снимка делать snapshot status?
- Какую задачу решает Velero, которую не закрывает снимок etcd?
Эксплуатация kubernetes держится на отрепетированных процедурах. Апгрейд - строго по skew, control plane раньше узлов, с предварительной проверкой deprecated API через pluto/kubent. Узлы обслуживаются через cordon/drain/uncordon, и drain без PDB рвёт доступность. etcd backup плюс Velero - два слоя страховки, и оба надо ПРОВЕРЯТЬ, а restore - репетировать. Сертификаты живут год, регулярный апгрейд их ротирует сам. Делай это руками на тесте, пока не дошло до автоматизма - в проде времени читать доку не будет.