Как ограничение ресурсов защищает от DoS-атак?
Ограничение ресурсов (resource limits) в Kubernetes и Docker предотвращает ситуацию, когда один контейнер потребляет все ресурсы хоста, лишая остальные контейнеры возможности нормально функционировать. Это защита от DoS (Denial of Service) — как от целенаправленных атак, так и от ошибок в коде (утечки памяти, бесконечные циклы, неконтролируемый рост числа потоков).
Requests и Limits в Kubernetes:
Пример
apiVersion: apps/v1
kind: Deployment
metadata:
name: banking-service
namespace: banking
spec:
template:
spec:
containers:
- name: app
image: registry.bank.local/banking-service:1.0.0
resources:
requests:
memory: "256Mi" # Гарантированные ресурсы для планировщика
cpu: "250m" # 0.25 CPU core
limits:
memory: "512Mi" # Максимум — при превышении контейнер получит OOMKill
cpu: "1000m" # 1 CPU core — при превышении контейнер будет троттлиться
env:
- name: JAVA_OPTS
value: >-
-XX:MaxRAMPercentage=75.0
-XX:+UseG1GC
-XX:+ExitOnOutOfMemoryError
Разница между requests и limits:
- requests — минимальный гарантированный объём ресурсов. Планировщик Kubernetes использует requests при выборе ноды для размещения пода: под будет размещён только на той ноде, где имеется достаточно свободных ресурсов для покрытия requests всех контейнеров.
- limits — максимально допустимое потребление. При превышении memory limit ядро Linux немедленно завершает процесс через OOMKill. При превышении CPU limit контейнер подвергается троттлингу (throttling) — ему выделяется меньше процессорного времени, но процесс продолжает работать.
Настройка JVM с учётом container limits:
Пример
ENTRYPOINT ["java", \
"-XX:MaxRAMPercentage=75.0", \
"-XX:InitialRAMPercentage=50.0", \
"-XX:+UseContainerSupport", \
"-XX:+ExitOnOutOfMemoryError", \
"-jar", "/app/app.jar"]
Флаг -XX:+UseContainerSupport (включён по умолчанию с Java 10+) позволяет JVM корректно определять memory и CPU limits контейнера через cgroups и соответствующим образом настраивать размер heap и количество потоков GC. Параметр -XX:MaxRAMPercentage=75.0 выделяет 75% от доступной памяти контейнера под Java heap, оставляя 25% для metaspace, стеков потоков, native memory (NIO-буферы, JNI) и самой операционной системы.
Флаг -XX:+ExitOnOutOfMemoryError приказывает JVM немедленно завершить процесс при OutOfMemoryError, чтобы Kubernetes мог перезапустить под через restartPolicy. Без этого флага JVM может оказаться в «зомби»-состоянии: процесс жив, но не обрабатывает запросы.
LimitRange — ограничения по умолчанию для namespace:
Пример
apiVersion: v1
kind: LimitRange
metadata:
name: banking-limits
namespace: banking
spec:
limits:
- type: Container
default: # Значения limits по умолчанию (если не указаны в Pod spec)
memory: "512Mi"
cpu: "500m"
defaultRequest: # Значения requests по умолчанию
memory: "256Mi"
cpu: "250m"
max: # Максимально допустимые limits
memory: "2Gi"
cpu: "2"
min: # Минимально допустимые requests
memory: "64Mi"
cpu: "50m"
- type: Pod
max:
memory: "4Gi"
cpu: "4"
LimitRange выполняет две функции: автоматически назначает limits/requests контейнерам, для которых они не указаны явно, и валидирует, что указанные значения попадают в допустимый диапазон. Это предотвращает ситуацию, когда разработчик забывает указать limits или устанавливает неоправданно высокие значения.
ResourceQuota — ограничение суммарных ресурсов namespace:
Пример
apiVersion: v1
kind: ResourceQuota
metadata:
name: banking-quota
namespace: banking
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
pods: "50"
services: "20"
secrets: "30"
configmaps: "30"
ResourceQuota ограничивает суммарное потребление ресурсов всеми подами в namespace. Это защита от ситуации, когда одна команда создаёт слишком много подов или потребляет непропорционально много ресурсов кластера. Поле pods: "50" также ограничивает количество подов, что защищает от атаки через массовое создание ресурсов.
Ограничение ресурсов в Docker:
Пример
docker run \
--memory=512m \
--memory-swap=512m \ # Запретить использование swap
--cpus=1.0 \
--pids-limit=200 \ # Ограничение числа процессов (защита от fork bomb)
--ulimit nofile=1024:2048 \
my-java-app
Параметр --memory-swap=512m, равный --memory, полностью запрещает использование swap. Swap может скрывать проблемы с потреблением памяти и ухудшать производительность, поэтому в production-среде его рекомендуется отключать. Параметр --pids-limit=200 ограничивает количество процессов внутри контейнера, что защищает от fork bomb (атака, при которой процесс рекурсивно создаёт копии самого себя, исчерпывая PID-ы системы).
Сценарии атак, которые предотвращают limits:
| Атака | Без ограничений | С ограничениями |
|---|---|---|
| Утечка памяти (memory leak) | Падение всего хоста из-за исчерпания RAM | OOMKill только конкретного пода, остальные продолжают работать |
| Бесконечный цикл (100% CPU) | Деградация производительности всех сервисов на ноде | Throttling только этого пода, другие получают своё процессорное время |
| Fork bomb | Исчерпание PID-ов хоста, невозможность запустить новые процессы | pids-limit ограничит число процессов внутри контейнера |
| Заполнение диска логами | Диск заполнен, все сервисы на ноде перестают писать логи и данные | emptyDir.sizeLimit + ephemeral-storage limits ограничат потребление дискового пространства |