Ресурсы, лимиты, ConfigMap и Secret
Вопросы, на которых видно, эксплуатировал ли кандидат кластер под реальной нагрузкой.
Вопрос: «Чем
requestsотличается отlimits? Что произойдёт с подом, если он превысит limit по CPU и что — если по памяти?»
Что на самом деле проверяет интервьюер
Это концентрированный вопрос про эксплуатацию. В нём сразу три темы: планирование подов, поведение cgroups при превышении лимитов и понимание, что CPU и память ведут себя принципиально по-разному. Ответ «под упадёт» неверен наполовину, и уточняющий вопрос это немедленно вскроет.
Развёрнутый ответ: requests и limits
requests — это гарантия и одновременно вход для планировщика. Планировщик суммирует requests всех подов на ноде и размещает новый под только туда, где остаётся достаточно свободного. Фактическое потребление при этом не учитывается: под с requests.memory: 2Gi, потребляющий 100 МБ, всё равно занимает 2 ГБ в расчётах планировщика.
limits — это потолок, который применяет ядро через cgroups. И вот здесь ключевое различие:
- CPU — сжимаемый ресурс. Превышение лимита не убивает под, а вызывает throttling: планировщик ядра просто не даёт процессу больше квоты в текущем периоде. Приложение не падает, но начинает тормозить, и в метриках это видно как рост
container_cpu_cfs_throttled_seconds_total. Классический симптом: latency выросла, а CPU по графику «не упирается в потолок». - Память — несжимаемый ресурс. Забрать уже выделенные байты нельзя, поэтому при превышении лимита ядро вызывает OOM killer и контейнер получает статус OOMKilled с кодом выхода 137. Deployment поднимет его заново, и при повторении вы увидите
CrashLoopBackOff.
resources:
requests:
cpu: "200m" # 0.2 ядра — гарантия и вход для планировщика
memory: "256Mi"
limits:
cpu: "1" # потолок: сверх него — throttling
memory: "512Mi" # потолок: сверх него — OOMKilled
# почему под перезапустился
kubectl describe pod api-7f9c | grep -A3 "Last State"
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
kubectl top pods # фактическое потребление
kubectl get events --sort-by=.lastTimestamp | tail
Классы QoS
По соотношению requests и limits Kubernetes присваивает поду класс качества обслуживания, и от него зависит, кого выселят первым при нехватке памяти на ноде:
| Класс | Условие | Порядок выселения |
| Guaranteed | requests равны limits для всех контейнеров | выселяют последними |
| Burstable | requests заданы и меньше limits | выселяют после BestEffort |
| BestEffort | requests и limits не заданы вовсе | выселяют первыми |
Отдельный практический вопрос, который любят задавать сеньорам: стоит ли вообще ставить CPU limit. Многие команды сознательно задают requests по CPU и не задают limits: throttling на квоте CFS способен ощутимо испортить latency даже при незагруженной ноде. Для памяти limits, наоборот, ставят почти всегда — иначе один протекающий под утащит ноду целиком. Знать эту дискуссию и уметь аргументировать позицию — сильный плюс на собеседовании.
Вопрос 2: ConfigMap и Secret
Оба объекта — это набор пар «ключ-значение», отвязывающий конфигурацию от образа: один и тот же образ едет в dev, stage и prod, меняется только конфигурация. Разница в предназначении: Secret нужен для чувствительных данных, значения в нём хранятся в base64, доступ к нему ограничивают через RBAC, а etcd для него шифруют.
Важная честность на собеседовании: base64 — это кодирование, а не шифрование. Из коробки Secret защищён не сильнее ConfigMap, безопасность даёт только связка «RBAC + шифрование etcd at rest + внешний менеджер секретов» (Vault, облачный KMS, External Secrets Operator).
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
data:
LOG_LEVEL: "info"
app.yaml: |
timeout: 30s
retries: 3
---
apiVersion: v1
kind: Secret
metadata:
name: api-secrets
type: Opaque
stringData:
DB_PASSWORD: "s3cr3t" # stringData принимает открытый текст, кодирует API-сервер
Подключить их можно двумя способами, и разница между ними — тоже частый вопрос:
containers:
- name: api
image: registry.local/api:1.5.0
envFrom:
- configMapRef: { name: api-config } # все ключи как переменные окружения
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: api-secrets
key: DB_PASSWORD
volumeMounts:
- name: cfg
mountPath: /etc/app
readOnly: true
volumes:
- name: cfg
configMap:
name: api-config
Через переменные окружения: просто, но значения фиксируются в момент старта контейнера — изменение ConfigMap не подхватится, нужен перезапуск подов. Через том: файлы в смонтированном каталоге kubelet обновляет автоматически (с задержкой порядка минуты), и если приложение умеет перечитывать конфиг, перезапуск не потребуется. Стандартный приём для явного передеплоя при смене конфигурации — аннотация с хэшем ConfigMap в шаблоне пода: меняется хэш, меняется шаблон, Deployment сам катит новую версию.
Типичные ошибки кандидатов
- Говорят, что при превышении CPU limit под убивают. Убивают только за память; CPU троттлится.
- Не знают код 137 и статус OOMKilled — а это первое, что смотрят при
CrashLoopBackOff. - Считают, что requests ограничивают потребление. Requests — это гарантия и вход для планировщика, ограничивает limits.
- Утверждают, что Secret «зашифрован». Он закодирован в base64.
- Ожидают, что под подхватит новый ConfigMap, подключённый через
env, без перезапуска. - Не задают resources вообще — под попадает в класс BestEffort и выселяется первым при любом дефиците памяти.
Как ответить кратко (20–30 секунд)
«Requests — гарантия и то, по чему планировщик выбирает ноду; limits — жёсткий потолок, который применяет ядро через cgroups. CPU сжимаем: превышение лимита даёт throttling и рост latency, но под живёт. Память несжимаема: превышение лимита — это OOMKilled с кодом 137 и последующий CrashLoopBackOff. По соотношению requests и limits под получает класс QoS, который определяет порядок выселения при нехватке памяти на ноде. Конфигурацию выношу в ConfigMap, чувствительные данные — в Secret, помня, что там base64, а не шифрование: реальную защиту дают RBAC, шифрование etcd и внешний менеджер секретов.»