Всем привет, я Саша архитектор ИБ проекта по защите контейнерных сред. Если кратко — сканируем образы, защищаем кластеры от запуска «плохих» образов и защищаем поды в реальном времени.

При разработке runtime‑безопасности под Kubernetes легко попасть в ловушку: локально всё работает идеально, но в реальном кластере начинает вести себя непредсказуемо. В этой статье разберу три реальные проблемы, с которыми мы столкнулись при интеграции Tetragon: потеря событий, дубли и ошибки дедупликации.

Для подобного рода защиты нужно подписываться на системные вызовы Linux. Мы не стали строить велосипед и взяли open source решение (Tetragon) для отслеживания и управления событий уровня ядра Linux.

Что такое  Tetragon?
Обзорная схема tetragon
Обзорная схема tetragon

Tetragon сидит в ядре Linux на уровне eBPF, слушает системные вызовы и события (типа открытия файлов, запуска процессов, сетевых соединений), сравнивает их с заданными политиками и либо логирует, либо блокирует. По сути — это такой невидимый охранник внутри ядра, который видит всё и решает, что с этим делать.

Коротко об устройстве системы

В нашем случае используется специфическая архитектура. Есть сервисы ядра и агентские сервисы. Сервисы ядра — это мозг системы, агенты занимаются защитой конкретных кластеров\нод. Специфика заключается в том, что между агентской стороной и ядром используется прокси сервис. Это сделано для одностороннего сетевого взаимодействия, чтобы заказчик меньше заморачивался с настройками сетевых доступов.

Почти вся связь между сервисами осуществляется при помощи брокера сообщений Nats(на стороне ядра и агентов).

Упрощенная архитектураня схема.
Упрощенная архитектураня схема.


Основная задача — создать политику, отловить нарушения политик и показать пользователю. Казалось бы ничего сложного — подключился по gRPC к Tetragon, слушаем его логи, прокидываем из агента и сохраняем в ClickHouse.

Задача понятна, политики сделали, нарушения отлавливаем. Для подготовки фичи к релизу протестировали на dev стендах — все работает. Спасибо за то, что дочитали статью до конца. Увы и ах, при проведении нагрузочного тестирования на кластере с несколькими нодами показало, что большая часть логов из Tetragon не доходит до ClickHouse.

Первая проблема — одно ядро, что правит всеми.

Проблему мы нашли. Дело в том, что Tetragon загружает eBPF‑программы в ядро Linux, слушает события и принимает решения, что с ними делать.

При локальной разработке мы используем minikube, который поднимает легковесный Kubernetes‑кластер на одной ноде (одной виртуальной машине с одним экземпляром ядра Linux). Соответственно, не было возможности проверить работу на нескольких нодах. И локально мы подключались к одному поду Tetragon и слушали его логи.

На dev‑стенде у нас жил не полноценный K8s, а его младший брат K3s. В стандартной сборке он использует общую базу данных SQLite, а не распределённый etcd. На одном узле проблему потери логов воспроизвести не удалось — сколько ни пытались. А когда развернули настоящий многокластерный k8s с etcd — тут же появилась.

Есть проблема — начинаем ее чинить. Решение довольно простое. Подключаться ко всем инстансам Tetragon на каждой ноде.

Tetragon работает как DaemonSet: один под на каждой ноде. Делаем list pods по селектору, получаем список и коннектимся к каждому отдельно«. Новая нода? DaemonSet поднял pod, агент увидел его на следующем sync»е, добавил в пул.

Подключаемся ко всем нодам кластера  k8s.
Подключаемся ко всем нодам кластера  k8s.

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

Спам нарушений политики

Ладно, вынужден продолжить, потому что не всё так радужно. Пошли тестировать другие виды политик. Если раньше на одно действие приходил один лог, то теперь — два, три одинаковых события. Непорядок. Копаем дальше.

Чтобы понять, почему полезли дубли, нужно заглянуть под капот политик Tetragon. Берём простейшую команду touch file. Для нас это одно действие. Для ядра — целых два системных вызова: сначала openat (создать файл), потом utimensat (выставить время). На выходе получаем два лога. И два лога не предел, может быть очень много. Показывать дубли, как потенциальную угрозу безопасности — плохой подход. Можно сделать такие политики, что за считанные минуты на активном кластере она будет генерировать миллионы дублей в минуту.

Появление дубликатов событий.
Появление дубликатов событий.

Проблему поняли - пора чинить. Сделали дедупликацию по трём признакам: PID, время поступления и ключевые поля из лога Tetragon.

Дедупликация.
Дедупликация.

Снова нагрузочное тестирование, разные политики, разные ноды — все круто, всем спасибо.

Повторяем нагрузочное тестирование, ловим проблемы c дедупликацией

Снова пришел тестировщик и начал давать нагрузку. Он изменил подход. Раньше он спамил нарушение от разных PID. Сейчас он решил сделать все в рамках одного скрипта на питоне. И вот незадача — дедупликация сломалась. Он делает сотню разных нарушений для разных политик — мы склеиваем их в одно. Беда. Снова идём чинить, на этот раз убираем PID из ключей дедупликации.

Окей, но теперь два разных процесса в одну временную метку делают одно и то же нарушение — и мы их склеиваем в одно событие. В нагруженном кластере это нормально.

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

Вывод

Основная сложность разработки под Kubernetes — не в API и не в YAML, а в том, что система изначально распределённая и это всегда нужно держать в голове.

eBPF даёт низкоуровневые события — фактически системные вызовы (например, open, execve, connect).

Но политики безопасности описываются на более высоком уровне:
«процесс внутри контейнера не должен создавать файлы в /etc» или «не должен запускать shell».

Проблема в том, что одно такое «действие» пользователя почти всегда раскладывается на несколько системных вызовов. Например, банальный touch file превращается в open и utimensat. В итоге между уровнем политики и уровнем событий появляется разрыв, который нужно как‑то склеивать.

На самом деле, есть еще интересные моменты, связанные с интеграцией политик(которыми поделюсь в следующих статьях). В планах есть нарастить rps примерно в 8 раз. С пропускной способностью тоже решали разные проблемы в виду необычной архитектуры. Есть понимание, что и как нужно делать. Как‑нибудь опишу и эту историю.

Мы не первый год занимаемся контейнерной безопасностью, чаще всего смотрим на k8s. И как только начинает казаться, что мы достигли дна, в хорошем смысле слова, снизу всегда кто‑то стучит. Приходится лучше тестировать, находить больше корнер‑кейсов, устраивать совместные мозговые штурмы. Но нам это нравится!

Любая система, которая в одиночной среде кажется простой, в Kubernetes превращается в распределённую. А значит, её поведение нужно проверять не на уровне «работает/не работает», а на уровне «как ведёт себя при масштабировании и конкуренции».

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


  1. trabl
    29.04.2026 10:23

    Dev, stage и прод сделайте максимально похожими, в k8s, без minikube и k3s. Dev и stage максимально урезать по ресурсам, в выходные дни вообще выключать если есть возможность, в идеале на прерываемых, дешевых машинах. И большая часть проблем с которыми столкнулись и ещё в перспективе столкнетесь уйдет.


    1. gromyko21 Автор
      29.04.2026 10:23

      Да, пришли после всех проблем