MAATRIX / Блог / k3s: под не запускается — причины и решение

k3s: под не запускается — причины и решение

k3s: под не запускается — причины и решение

MAATRIX

kubectl get pods показывает Pending, ImagePullBackOff или CrashLoopBackOff, деплой висит, приложение не поднялось. В k3s pod не стартует по одной из полутора десятков причин, и почти каждая написана открытым текстом — в событиях пода, в логе предыдущего контейнера или в журнале systemd. Разберём диагностику по статусам: что означает каждый, какой командой достать точный текст ошибки и что чинить.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Статус пода — это уже половина диагноза

Не перезапускайте деплой вслепую. Статус в kubectl get pods -A -o wide указывает, кто именно не справился, а kubectl describe pod api-6b7f9c4d88-lq7vn даёт точный текст.

СтатусКто не смог и куда смотреть
Pendingпланировщик не нашёл ноду — события FailedScheduling
ContainerCreatingkubelet не собрал под — тома, CNI, sandbox
ErrImagePull / ImagePullBackOffcontainerd не скачал образ — Failed to pull image
CreateContainerConfigErrorнет Secret или ConfigMap, на который ссылается под
CrashLoopBackOffконтейнер стартовал и умер — logs --previous
Evictedноду вытеснила нехватка диска или памяти
вечный Terminatingзависшие финализаторы или containerd

Первая грабля диагностики: события живут час. kube-apiserver стартует с --event-ttl=1h, и если под упал вчера, describe покажет Events: <none> — пересоздайте под и ловите ошибку заново. Когда kubectl молчит, спускайтесь на уровень рантайма: в k3s kubelet и containerd — части одного процесса, их логи лежат в общем журнале.

journalctl -u k3s -f --no-pager
k3s crictl ps -a
k3s crictl logs --tail 50 9f3ca1b2d4e5

Сырые логи контейнеров — в /var/log/pods/<ns>_<pod>_<uid>/<container>/0.log, лог рантайма — /var/lib/rancher/k3s/agent/containerd/containerd.log. На воркер-ноде сервис зовётся k3s-agent.

Pending: планировщик не нашёл ноду

Под в Pending не доехал до kubelet — его некуда положить. Причина всегда в событиях:

Warning  FailedScheduling  default-scheduler  0/1 nodes are available: 1 Insufficient memory.
         preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.

Ключевое, что понимают неправильно: планировщик считает не фактическое потребление, а сумму requests. По free -m может быть свободный гигабайт, но если запросы расписаны, новый под не поедет:

kubectl describe node k3s-uk-1 | grep -A5 "Allocated resources"
  Resource   Requests      Limits
  cpu        950m (47%)    1200m (60%)
  memory     1740Mi (89%)  2400Mi (123%)

123% в лимитах — норма, их переподписывать можно; requests выше 100% невозможны. На VPS с 2 ГБ allocatable-память после системного резерва — около 1,7 ГБ, и половину съедает control-plane. Другие тексты той же ошибки:

  • 1 node(s) had untolerated taint {node.kubernetes.io/disk-pressure: } — нода в DiskPressure, лечится очисткой диска (шестая секция).
  • 1 node(s) didn't match Pod's node affinity/selectornodeSelector не совпал ни с одной меткой, сверьтесь с kubectl get nodes --show-labels.
  • too many pods — упёрлись в лимит 110 подов на ноду, поднимается через --kubelet-arg=max-pods=200.

Отдельный нюанс, на котором теряют часы: штатный local-path работает в режиме WaitForFirstConsumer, поэтому строка waiting for first consumer to be created before binding в kubectl get pvcне причина, а следствие. Том ждёт под, а под не запланирован по другому поводу.

Лечится Pending честными запросами вместо скопированных из чужого чарта: cpu: 50m и memory: 96Mi при limits.memory: 384Mi для типового веб-приложения. Если задать только limits, Kubernetes скопирует их в requests — манифест с limits: memory: 2Gi на двухгигабайтной ноде не запланируется никогда.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть k3s

ImagePullBackOff: образ не приехал

Второй по частоте класс, а в России 2026 года — первый. Точный текст в событиях:

Failed to pull image "ghcr.io/acme/api:1.4.2": failed to resolve reference: unexpected
status from HEAD request to https://ghcr.io/v2/acme/api/manifests/1.4.2: 403 Forbidden

Прежде чем гадать, проверьте загрузку на ноде напрямую, минуя Kubernetes: k3s crictl pull ghcr.io/acme/api:1.4.2. Дальше разбираем по коду ответа:

  • 403 Forbidden — Docker Hub, ghcr.io и quay.io режут диапазоны российских провайдеров. Лечится зеркалом в /etc/rancher/k3s/registries.yaml; файл читается только при старте, так что после правки обязателен systemctl restart k3s.
  • 401 Unauthorized, pull access denied ... may require 'docker login' — приватный реестр без учётки. Секрет обязан лежать в том же неймспейсе, что и под: секрет в default, под в prod — снова 401.
  • toomanyrequests: You have reached your unauthenticated pull rate limit — Docker Hub с 2025 года даёт анонимному IP 10 пулов в час, за общим NAT лимит выгорает мгновенно. Помогает не зеркало, а авторизация.
  • no matching manifest for linux/amd64 — образ собран только под arm64.
  • dial tcp: lookup registry-1.docker.io on 127.0.0.53:53: server misbehaving — сломан DNS самой ноды, а не CoreDNS.

Рабочий registries.yaml сразу с зеркалом и авторизацией:

mirrors:
  docker.io:
    endpoint:
      - "https://mirror.gcr.io"
configs:
  "ghcr.io":
    auth:
      username: acme-bot
      password: ghp_xxxxxxxxxxxxxxxx

Альтернатива для приватного реестра — секрет kubectl -n prod create secret docker-registry ghcr --docker-server=ghcr.io --docker-username=acme-bot --docker-password=ghp_xxx и строка imagePullSecrets: [{ name: ghcr }] в спеке пода. Ещё мелочь ценой в вечер: тег :latest тянется с политикой Always, и при недоступном реестре под уходит в ImagePullBackOff, хотя образ уже лежит на ноде — прибитая версия плюс imagePullPolicy: IfNotPresent снимают эту зависимость. Подготовка ноды с нуля — в статье про установку и настройку k3s на VPS.

CrashLoopBackOff: контейнер стартует и умирает

Под запустился — значит, с планировщиком и образом порядок. Падает приложение или его убивают.

NAME                   READY   STATUS             RESTARTS        AGE
api-6b7f9c4d88-lq7vn   0/1     CrashLoopBackOff   7 (4m12s ago)   16m

Kubelet удваивает паузу между попытками: 10 с, 20, 40, 80, 160 и дальше потолок 5 минут. После седьмого рестарта экран не меняется не потому, что всё зависло, — идёт пятиминутный откат, и kubectl delete pod api-6b7f9c4d88-lq7vn сбрасывает счётчик. Лог берём у предыдущего контейнера, текущего ещё не существует: kubectl logs api-6b7f9c4d88-lq7vn --previous --tail=200.

Код выходаЧто произошло
1приложение упало само — читайте лог до первой ошибки
126файл найден, но не исполняемый
127команды нет в образе — опечатка в command или нет бинарника
137SIGKILL, почти всегда OOM
143SIGTERM — контейнер попросили выйти (проба, drain, обновление)

Проверка на OOM однозначная:

kubectl get pod api-6b7f9c4d88-lq7vn -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
{"exitCode":137,"reason":"OOMKilled","startedAt":"2026-08-27T09:13:58Z"}

reason: OOMKilled значит, что контейнер пробил свой собственный limits.memory, а не что кончилась память ноды — тогда под получил бы Evicted. Отдельная классика — рантаймы, не видящие cgroup-лимит: Node.js запускайте с --max-old-space-size=384 при лимите 512Mi, JVM — с -XX:MaxRAMPercentage=75.

Вторая массовая причина CrashLoopBackOff — не падение, а убийство пробой:

Warning  Unhealthy  kubelet  Liveness probe failed: Get "http://10.42.0.23:8080/healthz":
                             context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Normal   Killing    kubelet  Container api failed liveness probe, will be restarted

Приложение прогревается 40 секунд, а initialDelaySeconds: 10 — kubelet убивает его на 15-й секунде, и так бесконечно. Именно поэтому контейнер, прекрасно работающий в docker run, в кластере крутится в петле. Лечение — не растягивать liveness, а добавить startup-пробу:

startupProbe:
  httpGet: { path: /healthz, port: 8080 }
  failureThreshold: 30
  periodSeconds: 5

Это даёт 150 секунд на старт, после чего управление берёт liveness с короткими таймаутами. Логика падений совпадает с чистым Docker — см. разбор, почему Docker-контейнер сразу падает.

Ещё две ошибки маскируются под что угодно. exec /usr/local/bin/entrypoint.sh: exec format error — образ собран под arm64 на Apple Silicon и приехал на amd64-ноду; пересобирайте через docker buildx build --platform linux/amd64. exec /app/start.sh: no such file or directory — файл на месте, но нет интерпретатора из шебанга (собрали на Alpine, а скрипт зовёт /bin/bash) либо остались CRLF из Windows.

ContainerCreating и Init: под застрял до старта

ContainerCreating дольше минуты — уже симптом. Первый подозреваемый виден по событию Error: secret "api-env" not found или его младшему брату Error: couldn't find key DATABASE_URL in Secret prod/api-env. Оба дают CreateContainerConfigError и оба почти всегда про неймспейс: манифест применили в prod, секрет создали в default.

Дальше сеть. Самая неприятная ошибка k3s появляется после смены pod-CIDR, восстановления из снапшота или переустановки поверх старой:

Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for
sandbox "a1b2c3": plugin type="flannel" failed (add): failed to delegate add: failed to set
bridge addr: "cni0" already has an IP address different from 10.42.0.1/24

Остался мост от прошлой конфигурации. Удаляем сетевые артефакты — данные кластера не трогаются:

systemctl stop k3s
ip link delete cni0
ip link delete flannel.1
rm -rf /var/lib/cni/networks/cbr0
systemctl start k3s

Третья причина встречается на нодах, где подов больше двух десятков: Failed to create fsnotify watcher: too many open files. Это не файловые дескрипторы, а inotify — на Ubuntu 24.04 по умолчанию fs.inotify.max_user_instances = 128, и containerd вместе с kubelet выбирают их примерно на 25–30 подах.

cat >/etc/sysctl.d/99-k3s-inotify.conf <<'EOF'
fs.inotify.max_user_instances = 1024
fs.inotify.max_user_watches = 524288
EOF
sysctl --system

Статус Init:0/1 или Init:CrashLoopBackOff значит, что основной контейнер даже не начинал стартовать, а kubectl logs без ключа -c покажет пустоту — отсюда вывод «под молчит». Указывайте контейнер явно: kubectl logs api-6b7f9c4d88-lq7vn -c wait-for-db. Ещё один источник зависания: том local-path создаёт временный под-помощник, и при кончившемся диске тот падает молча — проверяйте kubectl -n kube-system get pods | grep helper-pod.

Когда виноват не под, а нода

Если разом встали все поды, диагностику начинают не с них.

kubectl get nodes
NAME       STATUS     ROLES                  AGE   VERSION
k3s-uk-1   NotReady   control-plane,master   26d   v1.34.6+k3s1

kubectl describe node k3s-uk-1 | grep -A6 Conditions

Самая частая беда арендованного сервера — DiskPressure: True. k3s задаёт более жёсткие пороги вытеснения, чем ванильный kubelet, и посмотреть их можно через API:

kubectl get --raw /api/v1/nodes/k3s-uk-1/proxy/configz | jq '.kubeletconfig.evictionHard'
{ "imagefs.available": "5%", "nodefs.available": "5%" }

Когда порог пробит, под получает статус Failed с причиной Evicted и сообщением The node was low on resource: ephemeral-storage. Threshold quantity: 5%, available: 4%. Ищем, что съело диск: на активно деплоящемся узле 20 ГБ уходят за пару месяцев — containerd не удаляет старые образы сам, а логи подов копятся.

df -h /var/lib/rancher
k3s crictl rmi --prune
du -sh /var/log/pods/* | sort -h | tail -5
kubectl delete pods -A --field-selector=status.phase=Failed

Первый rmi --prune на узле, живущем полгода, обычно возвращает 2–4 ГБ — механика та же, что в чистом Docker, подробности в статье, почему Docker занимает всё место на диске. Память ноды проверяется одной строкой:

systemctl status k3s | grep Memory
   Memory: 612.4M (peak: 810.2M)

На VPS с 2 ГБ подам остаётся около 1,1 ГБ с учётом системы — отсюда и бесконечные 137-е коды. Строки etcdserver: request timed out или database is locked в журнале сервиса означают медленный диск, а не проблему Kubernetes.

Специфический случай, когда поды стартуют, но ничего не работает: CoreDNS сам крутится в CrashLoopBackOff с сообщением [FATAL] plugin/loop: Loop (127.0.0.1:38471 -> :53) detected for zone ".". Значит, /etc/resolv.conf ноды указывает на локальный резолвер systemd и CoreDNS пересылает запросы сам себе. Лечится строкой resolv-conf: /run/systemd/resolve/resolv.conf в /etc/rancher/k3s/config.yaml и перезапуском сервиса.

Какой сервер взять под k3s в MAATRIX

Большая часть разобранных поломок — не ошибки в манифестах, а следствие того, что кластер поставили на машину впритык. Соответствие прямое:

Что видитеНастоящая причинаЧто нужно
OOMKilled, exit 137control-plane забрал 600 МБ, подам не осталосьот 4 ГБ RAM
Evicted, DiskPressureобразы, логи и тома съели дискот 50 ГБ NVMe
database is locked, таймауты APIмедленный fsync на SATA или сетевом дискетолько NVMe
403 Forbidden при pullроссийский IP в чёрных списках реестровнода за пределами РФ
FailedScheduling: Insufficient cpu2 vCPU расписаны системными подамиот 4 vCPU

Минимум, на котором не приходится воевать: 2 vCPU / 4 ГБ / 50 ГБ NVMe. Контроллеру хватает своих 600 МБ, вашим 5–8 подам остаётся около 3 ГБ — достаточно для приложения, кэша и небольшой БД. На 2 ГБ кластер тоже поднимется, но 137-й код станет постоянным спутником, и половина времени уйдёт на подбор лимитов вместо работы.

Комфортный вариант: 4 vCPU / 8 ГБ / 80–100 ГБ NVMe. Это 15–25 подов, запас на сборки в CI и спокойные rolling-обновления, когда старый и новый под какое-то время живут вместе.

По локации для k3s мы обычно советуем UK, Лондон, и причина прикладная: чистый европейский IP убирает целый класс проблем — Docker Hub, ghcr.io и quay.io отдают образы без 403 и без зеркал, Let's Encrypt проходит HTTP-01, а до европейских точек обмена 10–20 мс. FR даёт то же для континентальной Европы, US (Нью-Йорк) — если поды ходят к зарубежным ИИ-API. RU берут под 152-ФЗ, и тогда сразу закладывайте registries.yaml с зеркалом, иначе первый же деплой встанет в ImagePullBackOff.

Развернуть ноду можно за несколько минут в конфигураторе VPS, оплата — картами российских банков, по СБП, криптовалютой или токеном MAAT. Не уверены, хватит ли одной ноды — опишите состав подов, честно скажем, где достаточно 4 ГБ, а где без второго узла не обойтись.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть k3s

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Под в CrashLoopBackOff, а kubectl logs пустой — что делать?

Возьмите лог предыдущего запуска: kubectl logs <под> --previous. Если и там пусто, падает init-контейнер (kubectl logs <под> -c <init>) или процесс умирает до вывода — смотрите k3s crictl ps -a и k3s crictl logs <id> прямо на ноде.

Почему контейнер работает в docker run, но падает в k3s?

Три причины по убыванию частоты: liveness-проба убивает приложение до прогрева (лечится startupProbe), limits.memory меньше реального аппетита рантайма (код 137, reason: OOMKilled), либо не подложен Secret или ConfigMap — тогда CreateContainerConfigError.

Сколько ждать, пока под поднимется сам?

Kubelet удваивает паузу: 10, 20, 40, 80, 160 секунд и дальше потолок в 5 минут. Если причина устранена, не ждите отката — kubectl delete pod сбрасывает счётчик и запускает попытку немедленно.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.