Привет, Хабр! Меня зовут Никита Бахилин, я студент DevOps-курса YADRO. Во время обучения мы с сокурсником Даниилом Уткиным столкнулись с неочевидной проблемой при развертывании кластера Kubernetes. Мы не могли сделать пинг внутри пода K8s. Материалов, которые полноценно описывали бы проблему, я не нашел, поэтому мы написали эту статью. Надеемся, она поможет тем, кто только начинает работать с известным оркестратором.
Предыстория
Настал момент знакомства с Kuber****s, или K8s, в рамках DevOps-курса. Задача стояла сложная для опытного инженера, но трудная и непонятная для человека, который никогда не сталкивался с K8s.
Задача: Организовать k8s-кластеры в ручном режиме с помощью kubeadm и kubectl на базе cri-o (1.28+) и использовать Calico как CNI-плагин.
Кластер доступен для взаимодействия через kubectl, команда возвращает корректную информацию о кластере. Есть возможность сделать ping 8.8.8.8 с образом busybox.
Поясним некоторые термины для новичков:
kubectl — клиент, консольная утилита для взаимодействия с API кластеров Kubernetes.
kubeadm — утилита, упрощающая настройку кластера и компонентов Kubernetes.
cri-o — высокоуровневый runtime для создания контейнеров, аналог containerd (Docker машет ручкой).
Обратите внимание на последний пункт задания. В нем будет основная загвоздка.
Если для вас это плевая задачка и вам интересна работа в YADRO, участвуйте в спринт-оффере для DevOps-специалистов. Здесь вы cможете получить работу в дивизионе Телеком всего за три дня.
Начинаем решать задачу
Сразу уточним, что в этом тексте мы опустим процессы установки kubectl, kubelet, kubeadm, cri-o на ваших узлах. Мы подразумеваем, что они уже у вас установлены.
На этом этапе все worker-ноды должны быть добавлены в наш кластер. Чтобы это проверить, можете воспользоваться следующей командой на master-узле:
$ kubectl get nodes

Результат команды показывает, что у нас есть два worker-узла и один управляющий. Если у вас примерно такой же stdout и STATUS: Ready всех доступных в кластере узлов, то можно идти дальше.
Выполняем команду, которую требует задание:
$ kubectl run -it --tty test --image=busybox -- sh

Распарсим команду, которую только что отправили безжалостному kube-api-server:
-it
— интерактивный режим.- - tty
— явно указываем, что нужен псевдотерминал (-t уже есть, можно не указывать).test
— название будущего пода.-- image=busybox
— образ busybox (базовые Unix-утилиты).- - sh
— запуск оболочки Shell внутри контейнера.
Мы запускаем под (pod) — базовую единицу управления в кластере, который представляет собой группу одного или нескольких контейнеров, разделяющих между собой сетевые ресурсы и хранилище.
После запуска пода нас перебрасывает внутрь контейнера test
. Внутри — стандартный Shell.
Теперь проверяем возможность выполнить команду ping 8.8.8.8
:
/ # ping 8.8.8.8

И тут мы ловим запрет — permission denied
— на отправку ICMP-пакетов внутри контейнера. Главное — не паниковать.
Напомню, что команда
ping
— это базовая утилита для проверки доступности узла в сети с помощью ICMP-протокола. Последний работает на сетевом уровне вместе с IP, но все равно инкапсулируется в IP-пакет с добавлением соответствующих заголовков. Можно убедиться в этом, используя утилиту для анализа сетевого трафика — wireshark.

Решение проблемы
После этого опыта стало понятно, что в работе с Kubernetes нужны хорошие знания в области компьютерных сетей.
Мы перепробовали множество способов решения проблемы:
Писали манифесты.
Добавляли securityContext в поды.
Пытались настраивать параметры безопасности кластера.
Когда ни один из вариантов не сработал, обратились к куратору DevOps-курса Артуру Франку, старшему инженеру по разработке ПО в YADRO. Он ответил, что образ должен запускаться напрямую из консоли без дополнительных манифестов и настроек.
Это заставило задуматься. Значит, проблема не в Kubernetes, а на более низком уровне.
Начали анализировать систему, переключив внимание с уровня контейнеров на уровень хостовой ОС (в нашем случае — Ubuntu).
И тут мы здороваемся с мистером cri-o.
Cri-o — это реализация Kubernetes CRI (Container Runtime Interface), позволяющая использовать среды выполнения, совместимые с OCI (Open Container Initiative).
Тут мы просто обязаны познакомить вас с героями, которых в Linux/Gnu кличут как capabilities.

Сapabilities — это механизм в Linux, который позволяет детально управлять правами процессов без необходимости запуска от root. Они разделяют привилегии суперпользователя на отдельные «способности».
Давайте проверим capabilities нашего cri-o. Для начала проверим текущую конфигурацию cri-o, выполнив команду:
$ crio config

Как же нам понять какая именно суперспособность нужна нашему контейнеру?
Открываем в терминале документацию man по ping и ищем раздел security:
$ man ping

Видим, что нам нужен именно NET_RAW (CAP_NET_RAW). Проверим наличие этой capability у нашего cri-o:
$ crio config | grep NET_RAW

По умолчанию этот механизм отключен. Это сделано в целях безопасности, о чем мы еще поговорим далее.
Добавим эту способность в наше cri-o runtime:
$ vim /etc/crio/crio.conf.d/20-default-caps.conf
Теперь список default_capabilities выглядит так:
[crio.runtime]
default_capabilities = [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"FSETID",
"KILL",
"SETGID",
"SETUID",
"SETPCAP",
"NET_BIND_SERVICE",
"NET_RAW"
]
Про каждую из этих capabilities можно почитать отдельно.
Теперь перезапустим cri-o-службу, чтобы изменения вошли в силу. Воспользуемся командной управления демонами Linux — systemctl
:
$ systemctl restart crio.service
Проделываем эту махинацию на всех узлах, на которых собираемся запускать поды и где нужна поддержка ICMP-протокола.
Результат:

В итоге мы отправили 10 пакетов с эхо-запросами на DNS Google — получили 10 эхо-ответов. Проблема решена.
Что можно сделать иначе (комментарий Даниила):
«Единственное, что я сделал по-другому, — разместил default_capabilities не в файле
/etc/crio/crio.conf
, а в файле/etc/crio/crio.conf.d/20-default-caps.conf
. Это связано с множеством удобств работы с *.d-конфигурациями (но это мое личное предпочтение).
Анализ проблемы
В чем же причина проблемы ping: permission denied (are you root?)? Она возникает из-за того, что для работы по протоколу ICMP (ping) требуется наличие capability CAP_NET_RAW, который разрешает создавать RAW- и PACKET-сокеты, работающие на сетевом и канальном уровнях соответственно.
Протокол ICMP как раз должен упаковываться сразу в IP-пакет без использования TCP и UDP, для чего ему и требуется создание RAW-сокета. Следовательно, нам нужно каким-то образом разрешить его использование. Что интересно, Wget, cURL и прочие функции будут работать без ошибок, потому что они используют стандартные сокеты.

Причины возникновения
Исходя из ченджлога cri-o, NET_RAW и SYS_CHROOT были удалены из default_capabilities в версии v1.18.0 из-за уязвимостей, связанных с их использованием. Мы посмотрели стандартные capabilities в moby/docker, и CAP_NET_RAW ими предоставляется. Хотя Docker не является эталоном безопасности, но, по всей видимости, его разработчики не видят проблемы в этом разрешении.
В дистрибутивах с поддержкой cri-o — Minikube и K3s — также нет патчей для решения проблемы. Да и в issue-трекерах cri-o, Minikube, K3s и прочих не так много упоминаний об этом. Делаем вывод: большинству поддержка этой функциональности не очень-то и требуется.
А является ли это вообще проблемой?
Ответ скорее отрицательный. В большинстве случаев во время работы программы пинги на уровне ICMP используют редко — чаще это пинги с использованием «вышележащих» протоколов.
Как правило, разработчикам используют ping внутри дебаг-контейнера или при поиске причин неполадок. Поэтому возникает вопрос: насколько обоснованно включать NET_RAW на уровне всего container runtime? Кажется, не очень. Да, это решает проблему, но создает дыру, которую умные разработчики зачем-то все-таки закрыли.
Альтернативное решение проблемы
Решение проблемы, описанное в первой части статьи, удоволетворительное, но всегда можно капнуть поглубже.
Изучая Minikube и K3s, обратили внимание, что поды, которым нужна была эта функциональность, просто добавляли ее в securityContext.capabilities.add
. И это, пожалуй, самое лучшее решение в данном случае, исходя из принципа минимизации привилегий.
А если все-таки хочется добиться добавления capability — например, на уровне определенных namespace или label? Тут на помощь придут способы динамического управления профилями безопасности: Kubernetes Security Profiles Operator или Dynamic Admission Control. Выбор подходящего решения — за вами!
Сталкивались ли вы с подобной проблемой, работай с ping в Kubernetes? И если да, то как ее решали? Ждем ваших историй в комментариях!
Комментарии (10)
rendov
30.05.2025 14:35Материалов, которые полноценно описывали бы проблему, я не нашел, поэтому мы написали эту статью
А разве детская проблема, об которую спотыкались все, кто мало мальски пробовал использовать rootless в podman/docker нуждается в статье? Тем более, которую можно ужать до "Впишите в конфиг
NET_RAW
но секьюрность всех запускаемых контейнеров рухнет"? Вот чел за 4 минуты уложился аж 4 года назад. А вот самое важное, про включение этого разрешения на конкретные контейнеры, черезsecurityContext
у вас буквально просто один абзац, а не основное тело статьи. В общем такое.Bakhilin Автор
30.05.2025 14:35Спасибо за обратную связь!
Статья преследует следующие цели:Разобраться почему именно мы не можем отправлять пакеты 'наружу'. (Собственно и название статьи говорит само за себя)
Предложить решение, соответствующее условиям задания в лоб.
Показать как нужно делать.
nikweter
30.05.2025 14:35Решение проблемы, описанное в первой части статьи, удоволетворительное, но всегда можно капнуть поглубже.
Капать и копать - разные слова. Вы использовали первое в значении второго. Прошу прощения, если нужно было поправить как-либо иначе.
cdn_crz
30.05.2025 14:35Ты ему слово, он тебе "Спасибо за обратную связь! Статья преследует следующие цели"
trabl
30.05.2025 14:35Мне кажется что любой ИИ в наше время подскажет, почему из пода ты не можешь пингать какой-то другой хост. Много ситуаций, много решений. На статью для компании, которую я кстати когда-то зауважал, будучи в гостях на производстве, к сожалению не тянет. Завод в Дубнах уже открыли?
Bakhilin Автор
30.05.2025 14:35ИИ не совсем подходит под статью которую я написал. Много ситуаций - много решений, вот еще одно решение. Хорошая вводная статья, можно посмотреть на проблему с разных сторон. Почитайте внимательно введение, сразу все поймете.
jurikolo
Я что-то упустил в статье или вот этот пункт в середине текста вы пробовали плохо:
? Ведь именно это и предлагается в конце статьи как более ювелюрное решение.
mard_en
Видимо пытались пофиксить правами
Bakhilin Автор
Спасибо за обратную связь!
Статья преследует следующие цели:
Разобраться почему именно мы не можем отправлять пакеты 'наружу'. (Собственно и название статьи говорит само за себя)
Предложить решение, соответствующее условиям задания в лоб.
Показать как нужно делать.