k3s: под не запускается — причины и решение
kubectl get pods показывает Pending, ImagePullBackOff или CrashLoopBackOff, деплой висит, приложение не поднялось. В k3s pod не стартует по одной из полутора десятков причин, и почти каждая написана открытым текстом — в событиях пода, в логе предыдущего контейнера или в журнале systemd. Разберём диагностику по статусам: что означает каждый, какой командой достать точный текст ошибки и что чинить.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Статус пода — это уже половина диагноза
Не перезапускайте деплой вслепую. Статус в kubectl get pods -A -o wide указывает, кто именно не справился, а kubectl describe pod api-6b7f9c4d88-lq7vn даёт точный текст.
| Статус | Кто не смог и куда смотреть |
|---|---|
Pending | планировщик не нашёл ноду — события FailedScheduling |
ContainerCreating | kubelet не собрал под — тома, CNI, sandbox |
ErrImagePull / ImagePullBackOff | containerd не скачал образ — 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/selector—nodeSelectorне совпал ни с одной меткой, сверьтесь с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, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть k3sImagePullBackOff: образ не приехал
Второй по частоте класс, а в России 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 или нет бинарника |
| 137 | SIGKILL, почти всегда OOM |
| 143 | SIGTERM — контейнер попросили выйти (проба, 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 137 | control-plane забрал 600 МБ, подам не осталось | от 4 ГБ RAM |
Evicted, DiskPressure | образы, логи и тома съели диск | от 50 ГБ NVMe |
database is locked, таймауты API | медленный fsync на SATA или сетевом диске | только NVMe |
403 Forbidden при pull | российский IP в чёрных списках реестров | нода за пределами РФ |
FailedScheduling: Insufficient cpu | 2 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 — десятки моделей в одном окне. Оплата картой РФ и по СБП.