Образы контейнеров могут содержать все необходимые двоичные файлы и файлы конфигурации OpenVPN и всё равно не работать при первом сетевом вызове. Ошибка называет /dev/net/tun, потому что приложение попросило у ядра интерфейс TUN, а в контейнере не было ни узла устройства, ни возможности его создать. Это два отдельных разрешения, и исправление одного оставляет другое ровно там, где оно было.

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

Узел устройства и возможность — это две двери

Узел устройства — объект файловой системы, поэтому контейнер должен видеть его в собственном пространстве имён монтирования. Возможность — это разрешение, поэтому процессу нужен CAP_NET_ADMIN в пространстве имён, где будет создан интерфейс. Передача устройства без возможности даёт узел, который открывается, и ioctl, который завершается ошибкой. Выдача возможности без устройства оставляет приложение неспособным открыть хоть что-нибудь, поэтому в примерах оба обычно появляются вместе.

Параметр devices в Docker отображает устройство хоста внутрь контейнера, а cap_add настраивает набор возможностей. Bind mount того же пути показывает файл, не применяя семантику доступа к устройству.

Воспроизведите это вне файла Compose

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

ls -l /dev/net/tun
docker run --rm --cap-add NET_ADMIN --device /dev/net/tun:/dev/net/tun alpine sh -c 'apk add -q iproute2 && ip tuntap add dev tun9 mode tun && ip link show tun9'

Отсутствие узла на хосте означает, что модуль tun недоступен, и это исправление на стороне хоста, а не контейнера. Узел, который существует, но не открывается внутри контейнера, указывает на доступ к устройству, и сообщение скажет permission denied, а не no such file. Порядок этих двух сбоев и есть весь диагноз. Запишите, какой из них вы видели, прежде чем менять любой флаг.

Ошибки после TUN — это другие проблемы

Как только интерфейс появился, у следующего сбоя другой владелец. Ошибки аутентификации означают, что туннель дошёл до сервера и учётные данные были отклонены, поэтому изменения возможностей не помогут. Туннель, который устанавливается, пока приложения всё ещё терпят неудачу, указывает на маршруты, пересылку, разрешение DNS внутри контейнера или собственную сетевую схему контейнера. Каждый из этих пунктов — обычный сетевой вопрос, на который разрешение TUN не может ответить. Повторение исходного теста после каждого изменения не даёт сбоям слиться в одно впечатление.

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

Проверьте интерфейс внутри контейнера

Проверьте интерфейс и набор возможностей изнутри работающего контейнера, а не с хоста. Команды ниже показывают узел устройства, список возможностей и то, можно ли вообще создать интерфейс. Запустите их в том же контейнере, который запускает клиент, и установите инструменты, если образ минимален.

ls -l /dev/net/tun
capsh --print | grep -i net_admin
ip link add dev tun9 type tun && ip link show tun9

Возможность, присутствующая в bounding set, но отсутствующая в effective set, ведёт себя иначе, чем та, которую никогда не выдавали, и capsh показывает обе. Если узел существует и возможность есть, а создание всё ещё не удаётся, среда выполнения навязывает что-то другое, например профиль seccomp или политику AppArmor, ограничивающую операцию. Это третья граница, документируемая средой выполнения, а не приложением.

Запишите флаги контейнера, образ и точную ошибку каждой попытки. Следующий человек, прочитавший эту заметку, сможет сказать, какая дверь была закрыта, не повторяя всё расследование.

Похожие статьи