Ресурсы, лимиты, 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 присваивает поду класс качества обслуживания, и от него зависит, кого выселят первым при нехватке памяти на ноде:

КлассУсловиеПорядок выселения
Guaranteedrequests равны limits для всех контейнероввыселяют последними
Burstablerequests заданы и меньше limitsвыселяют после BestEffort
BestEffortrequests и 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 и внешний менеджер секретов.»

Проверьте себя
1. Контейнер стабильно упирается в limits.cpu. Что с ним произойдёт?
AБудет убит с кодом 137
BБудет троттлиться: ядро урежет квоту CPU, приложение начнёт медленнее отвечать, но продолжит работать
CПланировщик перенесёт под на другую ноду
DKubernetes автоматически увеличит лимит
2. Под перезапускается, в описании Last State указано OOMKilled и Exit Code 137. Что это значит?
AПриложение завершилось само с ошибкой конфигурации
BКонтейнер превысил limits.memory, и ядро завершило его через OOM killer
CПровалилась liveness probe
DНа ноде закончилось место на диске
3. ConfigMap подключён через envFrom. Вы изменили значение в ConfigMap. Что увидит работающий под?
AНовое значение сразу же
BНовое значение примерно через минуту
CСтарое значение: переменные окружения фиксируются при старте контейнера, нужен перезапуск подов
DПод автоматически перезапустится сам