ヘアピンNATが使えない環境でDockerコンテナから自分自身にアクセスするメモ
はじめに
私はLinuxカーネルの管理などにセルフホストのGiteaを利用しており、CI/CD環境としてGitea Actionsを利用しています。
で、実は最近サーバの引っ越しを行いまして、ホストOSがUbuntu 20.04からArch Linuxとなりました。 その結果、これまで動いていたDocker on systemd-nspawnが動作しなくなってしまったためホスト上にDockerをインストールしたのですが、そのままでは同一ホスト上のGiteaインスタンスに接続できませんでした。
具体的には、act_runnerがグローバルなIPアドレスにpingを打って失敗し、先に進む事ができません。
ヘアピンNATに対応したルータであれば、パケットが転送されてサーバに戻ってきますが、我が家の民生用ルータにそのような機能はありません。
そこで、DNAT(Destination Network Address Translation)を設定し、docker0
からグローバルIPに向けて出ていくパケットをローカルアドレスに変換する事で対処します。
私の環境は
- Arch Linux
- Firewalld(nftables backend)
- nginxを用いてsystemd-nspawn上のGiteaを公開
- docker0(172.19.0.1/16)
- グローバルIPはDDNSで可変
という構成です。
実験
DDNSの可変IPに対応するのは後回しとして、現在のアドレスに対してDNATを設定してみます。
-
nftables設定
1 2 3
nft create table ip docker_redirect nft add chain ip docker_redirect PREROUTING { type nat hook prerouting priority dstnat\; } nft add rule ip docker_redirect PREROUTING ip daddr <グローバルIP> iif docker0 dnat to 172.19.0.1
-
firewalldで許可
1 2 3
firewall-cmd --add-service=http --zone=docker --permanent firewall-cmd --add-service=https --zone=docker --permanent firewall-cmd --reload
*Arch Linuxに限るかもしれませんが、Dockerを入れると自動的にdockerゾーンが作られるようです。
その結果
level=info msg="Registering runner, arch=amd64, os=linux, version=v0.2.6."
level=debug msg="Successfully pinged the Gitea instance server"
act_runnerは正常動作したようです。
しかし、act_runnerが起動する子コンテナでは相変わらずアドレス変換が動作しておらず、グローバルなアドレスに向けてcloneしようとして失敗しました。(ログ取り忘れ)
そこで、act_runnerを設定してネットワークをhostに変更します。
act_runnerの設定
子コンテナのネットワーク設定はデフォルトでbridge
のようですが、host
にしてDNATが効くようにします。
やり方は色々だと思いますが、私は/opt/gitea/act_runner/
に設定ファイルを作成し、docker run時にマウントするようにしました。
/opt/gitea/act_runner/config.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
log: # The level of logging, can be trace, debug, info, warn, error, fatal level: warn runner: file: /.runner capacity: 1 env_file: .env timeout: 3h insecure: false fetch_timeout: 5s fetch_interval: 2s cache: enabled: true dir: "" host: "" port: 0 container: network: host privileged: false # And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway). options: workdir_parent:
そして開始
|
|
正常に動作するようになりました。
コメントに残している通り設定のoptionsにホストを追加することもできるのですが、どのみちgo製のact_runnerの為に何かしら対策を打つ必要があるのでhostにするだけで良いと思います。
DDNSに対応
いつぞや作ったルーター用設定を流用してこんな感じにしました。
-
/usr/local/bin/docker_loopback.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
#!/bin/bash # DNSからAレコードを取得しdiffにて前回のアドレスと比較 dig -t A dynamic.haru3.me +short > /tmp/iptest.b.txt diff /tmp/iptest.a.txt /tmp/iptest.b.txt if [ $? -eq 0 ]; then echo "IP is same" rm /tmp/iptest.b.txt exit 0 fi rm /tmp/iptest.a.txt mv /tmp/iptest.b.txt /tmp/iptest.a.txt GLOBAL_IP=$(cat /tmp/iptest.a.txt) # docker0 export DOCKER_IF=docker0 export DOCKER_IP=172.19.0.1 # 既存のテーブル削除 set +e nft flush chain ip docker_redirect PREROUTING nft delete chain ip docker_redirect PREROUTING nft flush table ip docker_redirect nft delete table ip docker_redirect set -e # テーブル・ルール追加 nft create table ip docker_redirect nft add chain ip docker_redirect PREROUTING { type nat hook prerouting priority dstnat\; } nft add rule ip docker_redirect PREROUTING ip daddr $GLOBAL_IP iif $DOCKER_IF dnat to $DOCKER_IP
-
/etc/systemd/system/docker_loopback.service
[Unit] Description=docker loopback RefuseManualStart=no RefuseManualStop=yes After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/docker_loopback.sh
-
/etc/systemd/system/docker_loopback.timer
[Unit] Description=Run docker_loopback hourly [Timer] OnCalendar=hourly Persistent=true [Install] WantedBy=timers.target
-
有効化
1 2
systemctl daemon-reload systemctl enable --now docker_loopback.timer
おわりに
DNATを設定する事で違和感なくact_runnerが動作するようになりました。
最近Alpine Linuxなどでもnftablesを使っているのですが、firewalld等のフロントエンドが無くても十分設定しやすいのでありがたいです。
ところで、調べている途中でebtablesのbroute風に動作させる方法が出てきたのですが、この方法でブルータを更新したいですね…