Если мы говорим о безопасности в Kubernetes, первым делом нужно защитить ключевые компоненты кластера (pod’ы) от внешнего воздействия и ограничить риски внутри самих pod’ов. Чем меньше процессов в pod’е, тем меньше уязвимостей в кластере.

В этой статье мы обсудим, почему рискованно сохранять в pod’е параметры количества процессов по умолчанию, и как решить проблему.

Конфигурация по умолчанию

По умолчанию в Kubernetes pod’ы наследуют параметры количества процессов у хоста, а на хосте установлено очень большое значение. Узнать его можно командой:

$ cat /proc/sys/kernel/pid_max

Риски конфигурации по умолчанию

Если в pod’е число процессов не ограничено или лимит очень большой, злоумышленник может запустить ветвящуюся бомбу (fork bomb), что приведёт к отказу в обслуживании и сбою системы из-за нехватки ресурсов.

Профилактика

В Kuberbetes 1.20 есть стабильная функция, которая позволяет ограничивать количество процессов в pod’е на уровне ноды в конфигурации kubelet.

Полную конфигурацию kubelet см. здесь.

Давайте посмотрим пример. Что нам понадобится:

  • Работающий кластер Kubernetes (можно использовать minikube или kind).

  • Знакомство с командами kubectl.

  • Общее представление об администрировании Linux.

В этом примере мы используем minikube.

Что делать

Часть 1. Сначала мы проверим конфигурацию по умолчанию — сколько процессов в pod’е разрешено.

Запускаем кластер Kubernetes через minikube:
Примечание. Нужна версия Kubernetes не ниже 1.20. На момент написания статьи minikube использовал версию 1.23.3.

$minikube start

Проверяем, что нода кластера запущена:

$ kubectl get nodes

Выполняем команду, чтобы запустить развёртывание nginx:

echo "apiVersion: apps/v1
kind: Deployment
metadata:
  name: pid-limit
  labels:
    app: busybox
spec:
  replicas: 1
  selector:
    matchLabels:
      app: busybox
  template:
    metadata:
      labels:
        app: busybox
    spec:
      volumes:
      containers:
      - name: pid-limit
        image: busybox:1.28
        command: [ "sh", "-c", "sleep 1h" ]
        ports:
        - containerPort: 80
" | kubectl apply -f -

Проверяем, что pod nginx запущен:

$ kubectl get pods

Давайте проверим лимит процессов в этом pod’е. Сначала подключимся к pod’у по SSH:
Примечание. Замените <POD_NAME> именем вашего pod’а.

$ kubectl exec -it <POD_NAME> -- sh

Смотрим значение в /proc/sys/kernel/pid_max:

$ cat /proc/sys/kernel/pid_max    
4194304

Как видите, количество процессов практически не ограничено.

Примечание. Это значение унаследовано из того же пути в ноде Kubernetes.

Мы можем в этом убедиться, подключившись к ноде кластера Kubernetes командой minikube ssh и выполнив ту же команду cat /proc/sys/kernel/pid_max.

Часть 2. В этой части мы проверим, действительно ли большое количество процессов создаёт проблему.

Подключаемся к pod’у:

$ kubectl exec -it <POD_NAME> -- sh

Создаём простой shell-скрипт с циклом для запуска 100 процессов. Я обычно работаю в vi, но вы можете выбрать любой редактор. Я создаю 100 процессов, чтобы показать, что число процессов в pod’е не ограничено. Вы можете указать другое число или выбрать другой способ ветвления процессов.

# vi test.sh

Этот скрипт запустит 100 процессов в фоновом режиме:

#!/bin/sh
for i in `seq 1 100`
do
    sleep 1h &
done

Сохраняем файл, добавляем разрешение на выполнение и выполняем его:

# chmod +x test.sh
# ./test.sh &

Проверяем выполняющиеся процессы:

# ps -ef

Мы видим, что все процессы запустились и им ничего не помешало. В реальной жизни процессов будет не 100. Они будут бесконтрольно ветвиться, поглощая ресурсы системы, пока она не остановится.

Запущенные процессы
Запущенные процессы

Часть3. В этой части мы изменим конфигурацию kubelet, чтобы задать лимит процессов в pod’е.

Выполняем следующую команду и входим на ноду minikube как пользователь root, чтобы можно было редактировать файл конфигурации kubelet:

$ minikube ssh
$ su -
# vi /var/lib/kubelet/config.yaml

Добавляем следующую строку в конец файла, чтобы велеть kubelet запускать pod максимум с 10 процессами, и сохраняем файл:

podPidsLimit: 10

Перезапускаем сервис kubelet, чтобы применить новую конфигурацию:

# systemctl restart kubelet

Сейчас у нас два типа pod’ов:

  • Выполняющиеся pod’ы, которые нужно перезапустить, чтобы к ним применилась конфигурация kubelet.

  • Новые pod’ы, которые будут запущены уже с новой конфигурацией.

Перезапустим развёртывание, используя стратегию rollout:

$ kubectl rollout restart deployment/pid-limit

Подключаемся к pod’у по SSH и выполняем тот же shell-скрипт, что и раньше. Мы видим сообщение о том, что процессы больше ветвить нельзя, потому что мы достигли лимита:

По желанию можно вывести запущенные процессы, чтобы убедиться, что скрипт создавал процессы, пока не был достигнут лимит, указанный в конфигурации kubelet:

Заключение

В этой статье мы настроили в kubelet ограничение на количество процессов, которые можно запустить в pod’e Kubernetes. Без этого ограничения существовал риск того, что процессы будут ветвиться, пока не израсходуют все ресурсы и не уронят кластер.

Курс «Безопасность в Kubernetes» с практикой на стендах.

Комментарии (2)


  1. TyVik
    12.01.2023 21:20

    А для ограничения inode есть что-то подобное?

    Вообще была бы интересна статья о побеге из этой песочницы или хотя бы возможности выглянуть в ноду. Помню, что в моей системе смог узнать модель жёсткого диска, это как пример.


  1. Pycz
    13.01.2023 18:07
    +1

    Вот бы было классно, если можно бы было это на уровне подов/деплойментов организовать. Конкретно интересует в разрезе реализации песочницы для кода, в которой запускается произвольный код - для проверки решений задач по спортивному программированию, например.