コンテナイメージは必要な OpenVPN のバイナリと設定ファイルをすべて含んでいても、最初のネットワーク呼び出しで失敗することがあります。エラーが /dev/net/tun を挙げるのは、アプリケーションがカーネルに TUN インターフェースを要求したのに、コンテナにデバイスノードも、それを作る権限もなかったためです。これは別々の二つの許可であり、片方を直してももう片方は元のままです。

両者を切り分けるには、それぞれに一つずつコマンドが必要です。その後のエラーメッセージが、実際にどちらの境界に立っていたかを教えてくれます。

デバイスノードと権限は二つの扉

デバイスノードはファイルシステムのオブジェクトなので、コンテナは自分のマウント名前空間でそれを見る必要があります。権限は許可なので、プロセスはインターフェースが作成される名前空間で CAP_NET_ADMIN を必要とします。権限なしでデバイスを渡すと、開くノードと失敗する ioctl が得られます。デバイスなしで権限を与えると、アプリケーションは何も開けなくなり、そのため例では両方が一緒に登場します。

Docker の devices 設定はホストデバイスをコンテナにマッピングし、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 モジュールが利用できないということで、コンテナ側ではなくホスト側の修正です。ノードはあるのにコンテナ内で開けない場合はデバイスアクセスを示し、メッセージは no such file ではなく permission denied と言います。この二つの失敗の順序が診断のすべてです。フラグを変える前に、どちらを見たかを書き留めてください。

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 ポリシーのように操作を制限する別のものを適用しています。それはアプリケーションではなくランタイムが文書化する第三の境界です。

コンテナのフラグ、イメージ、そして各試行の正確なエラーを記録してください。そのメモを次に読む人は、調査全体を繰り返さずに、どの扉が閉じていたかを判断できます。

関連記事