Manual scheduling: Pod мимо шедулера
Что делает поле spec.nodeName, чем оно опасно в проде и при чём тут static pods control-plane.
Что вообще такое manual scheduling
Обычно за выбор ноды для Pod отвечает kube-scheduler. Он смотрит на
Pod с пустым spec.nodeName, фильтрует ноды по taints, affinity,
ресурсам, скорит и выбирает лучшую. Pod получает spec.nodeName = node-3, kubelet на этой ноде подхватывает его и запускает.
Manual scheduling это, когда spec.nodeName пишут в манифесте руками.
Шедулер видит, что поле уже заполнено, и пропускает такой Pod мимо.
Kubelet на указанной ноде видит «это моё» и запускает. Никаких
проверок ресурсов, никаких taints, никакого affinity. Просто
запускает.
обычный Pod → scheduler → kubelet
nodeName Pod ──────────────→ kubelet
Зачем оно вообще нужно: для static pods и для разовых диагностик. В проде workload так не деплоят, потому что обходишь все защиты шедулера. Поставишь Pod на ноду без свободной памяти, он встанет и тут же поймает OOMKilled.
Минимальный YAML
apiVersion: v1
kind: Pod
metadata:
name: pinned-pod
spec:
nodeName: worker-2
containers:
- name: nginx
image: nginxinc/nginx-unprivileged:1.25
Где живёт nodeName
В spec самого Pod, не в spec.template.spec у Deployment. У
Deployment задавать nodeName бессмысленно: ReplicaSet будет
создавать Pod, шедулер у всех будет пропускать выбор, и все
реплики поедут на одну ноду. Получишь single point of failure.
Для «привязки Deployment к группе нод» правильный путь это
nodeSelector или nodeAffinity, не nodeName.
В кластере из одной ноды разницу между обычным Pod и Pod с nodeName
по факту размещения не увидеть, оба окажутся на одном узле. Видно её в
другом месте: у обычного Pod есть события от шедулера
(Scheduled: Successfully assigned ...), у Pod с nodeName таких
событий нет вообще.
Опечатка в имени ноды оставляет Pod в Pending навсегда и без единого
события: шедулер на него не смотрит, а kubelet с таким именем не
существует.
Static pods
Static pods это особый случай manual scheduling. Их создаёт сам
kubelet, читая YAML-файлы из директории
/etc/kubernetes/manifests/ (путь задаётся полем staticPodPath в
конфиге kubelet; старый CLI-флаг --pod-manifest-path устарел).
apiserver про эти pods не знает, но kubelet делает их видимыми как
mirror pods (отсюда суффикс с именем ноды в названии).
Через static pods поднимают control-plane в kubeadm-кластерах: apiserver, controller-manager, scheduler сами стартуют как static pods на control-plane-ноде. Это решает курицу и яйцо: apiserver не нужен apiserver-у, чтобы запуститься.
Связанные команды
kubectl get nodes
kubectl get nodes -o name
kubectl get pod <name> -o jsonpath='{.spec.nodeName}'
kubectl get pods -o wide # видно NODE для каждого pod
kubectl describe node <name> # ресурсы и pods на ноде
kubectl explain pod.spec.nodeName
Что часто ломается
- Поставил nodeName на ноду без памяти. Pod стартует, kubelet убивает OOM, рестарт, опять OOM. CrashLoopBackOff на ровном месте.
- nodeName прописан в Deployment.template. Все реплики едут на одну ноду, отказоустойчивости нет.
- Static pod не появляется. Kubelet логи: путь не тот, kubeconfig не
тот, манифест невалидный. Смотри
journalctl -u kubelet.
Домашка
Теорию закрепляет задание на своём кластере: 1 шаг с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.
./run.sh start k8s-manual-scheduling # развернуть стартовое состояние
./run.sh verify k8s-manual-scheduling # проверить текущий шаг
Коротко
- Заполненный
spec.nodeNameвыключает шедулер: Pod забирает kubelet названной ноды напрямую. - Проверок ресурсов, taints и affinity при этом не происходит.
- Имя ноды с опечаткой оставляет Pod в
Pendingнавсегда, событий от шедулера не будет. - В Deployment
nodeNameсобирает все реплики на одном узле. Для групп нод нужныnodeSelectorилиnodeAffinity. - Static pods из
/etc/kubernetes/manifests/работают по тому же принципу, на них держится control-plane в kubeadm-кластерах.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс