記憶力が無い

プログラミングと室内園芸と何か

WireGuard を使い自宅サーバーの Docker コンテナを VPS 経由で公開する

はじめに

私は自宅サーバーで動かしてるサービスをインターネットに公開する際、VPS を経由させる形で公開しています。

これまで自宅サーバーと VPS の間は SSH のポートフォワード(リモートフォワード)を使ってきましたが、もっといいやり方がないかということで WireGuard に可能性を見出したので、検証してみたいと思います。

サービスを Docker コンテナ上で動かすことが多い都合上、Docker を使った検証となっています。

参考にされる場合は自己責任でお願いします。

環境

  • VPS
    • どこかの VPS
    • OS は Ubuntu 20.04 Server
  • 自宅サーバー
    • VirtualBox
    • OS は Ubuntu 20.04 Server
    • ネットワークアダプターは NAT

自宅サーバーと VPS を WireGuard で繋ぐ

まずは準備として自宅サーバーと VPS に WireGuard をインストールして ping が通ること確認します。

WireGuard は Ubuntu 20.04 の標準リポジトリにあるので以下のコマンドですぐにインストールできます:

# VPS と自宅サーバー両方で実行
apt install -y wireguard

通信に使う秘密鍵と公開鍵のペアを作ります:

# VPS と自宅サーバー両方で実行
umask 0077
wg genkey > privatekey
wg pubkey < privatekey > publickey

作成した鍵を使って WireGuard の設定を作成します。 鍵の部分は作成したものに置き換えて実行してください。

VPS (WireGuard サーバー) 側の設定:

cat << 'EOS' > /etc/wireguard/wg0.conf
[Interface]
PrivateKey = <サーバーの公開鍵>
Address = 10.0.100.1/24
ListenPort = 51820

[Peer]
PublicKey = <クライアントの公開鍵>
AllowedIPs = 10.0.100.2/32
EOS

自宅サーバー (WireGuard クライアント) 側の設定:

cat << 'EOS' > /etc/wireguard/wg0.conf
[Interface]
PrivateKey = <クライアントの秘密鍵>
Address = 10.0.100.2/24

[Peer]
PublicKey = <サーバーの公開鍵>
Endpoint = <VPS のグローバル IP アドレス>:51820
AllowedIPs = 10.0.100.1/32
PersistentKeepalive = 25
EOS

WireGuard 上のネットワークは 10.0.100.0/24 で、VPS 側に 10.0.100.1, 自宅サーバー側に 10.0.100.2 を割り当てています。

設定が終わったら自宅サーバー、VPS 両方で WireGuard を起動します:

# VPS と自宅サーバー両方で実行
systemctl start wg-quick@wg0

自宅サーバー側から VPS 側に ping が通ることを確認します:

root@wg-client:~# ping 10.0.100.1 -c 3
PING 10.0.100.1 (10.0.100.1) 56(84) bytes of data.
64 bytes from 10.0.100.1: icmp_seq=1 ttl=64 time=516 ms
64 bytes from 10.0.100.1: icmp_seq=2 ttl=64 time=39.6 ms
64 bytes from 10.0.100.1: icmp_seq=3 ttl=64 time=60.6 ms

--- 10.0.100.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2006ms
rtt min/avg/max/mdev = 39.570/205.475/516.266/219.929 ms

VirtualBox の NAT を使っているせいか rtt が残念な感じですが、疎通できていることが確認できました。

Docker ネットワークのデフォルトゲートウェイを VPS にする

WireGuard の基本的な設定ができたので、サービスを動かす Docker コンテナの通信だけを WireGuard を経由させるようにしてみようと思います。

(Docker のインストール部分は省略)

Docker はコンテナが通信出来るように iptables の設定をいい感じに書き換える機能を持っていますが、今回はその挙動が邪魔になるため、あらかじめ無効化しておきます:

# 自宅サーバー上で実行
cat << 'EOS' > /etc/docker/daemon.json
{
  "iptables": false
}
EOS
systemctl restart docker

# 設定リセット (★)
iptables -t nat -F
iptables -t nat -X
iptables -t filter -F
iptables -t filter -X
iptables -t filter -P INPUT ACCEPT
iptables -t filter -P OUTPUT ACCEPT
iptables -t filter -P FORWARD ACCEPT

設定が残ってると邪魔になる可能性があるのでリセットしておきます。 また、細かく設定するのは面倒なのでデフォルトポリシーはすべて ACCEPT のガバガバ設定にしておきます。

まずは WireGuard を経由して任意の IP アドレスと通信ができるように自宅サーバー側の WireGuard 設定の AllowedIPs0.0.0.0/0 に変更します。

WireGuard は AllowedIPs の値に応じてルートテーブルを書き換えます。 そのままでは自宅サーバーの通信すべてが WireGuard を経由することになるため、ルートテーブルを変更しない設定(Table = off)も合わせて行います:

[Interface]
(省略)
Table = off

[Peer]
(省略)
AllowedIPs = 0.0.0.0/0

設定変更後 WireGuard を再起動します:

# 自宅サーバー上で実行
systemctl restart wg-quick@wg0

次に、通信を WireGuard 経由にする対象の Docker ネットワークを作成します:

# 自宅サーバー上で実行
docker network create \
  --driver bridge \
  --subnet=10.1.1.0/24 \
  --gateway=10.1.1.1 \
  --opt com.docker.network.bridge.name=wg_test \
  --opt com.docker.network.driver.mtu=1500 \
  wg_test

サブネットは何でもいいですが今回は適当に 10.1.1.0/24 にしてあります。

そして 10.1.1.0/24 から送信されるパケットが WireGuard 経由になるようにデフォルトゲートウェイを VPS 側 (10.0.100.1) に設定します:

# 自宅サーバー上で実行
ip rule add from 10.1.1.0/24 table 100
ip route add default via 10.0.100.1 dev wg0 table 100

また、Docker コンテナの通信が外に出れるように IP マスカレードの設定を行います:

# 自宅サーバー上で実行
iptables -t nat -A POSTROUTING ! -o wg_test -s 10.1.1.0/24 -j MASQUERADE

ここまでで Docker コンテナから WireGuard を通して VPS にパケットが流れるようになりました。 ただし、このままでは VPS からインターネットに通信が出ていかないので VPS に net.ipv4.ip_forward の設定と IP マスカレードの設定を行います:

# VPS 上で実行
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING ! -o wg0 -s 10.0.100.0/24 -j MASQUERADE

必要に応じて (★) のコマンドのように iptables の設定のリセットも行ってください。

これで準備は整いました。 自宅サーバーの Docker コンテナ上から 1.1.1.1 に ping を実行してみます :

# 自宅サーバー上で実行
root@wg-client:~# docker run -it --rm --network wg_test test_ping /bin/ping 1.1.1.1 -c 3
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=55 time=454 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=55 time=474 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=55 time=494 ms

--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2009ms
rtt min/avg/max/mdev = 454.253/474.037/494.346/16.372 ms

--network wg_test で先ほど作成したネットワークを指定してます。 test_ping は iputils-ping をインストールしただけの自作のイメージです。

相変わらず rtt が残念な感じですが、無事返ってきていますね。

念のため VPS 上で tcpdump を実行し、パケットが行き来していることを確認してみます:

# VPS 上で実行
root@wg-server:~# tcpdump -i wg0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wg0, link-type RAW (Raw IP), capture size 262144 bytes
22:14:11.528312 IP 10.0.100.2 > one.one.one.one: ICMP echo request, id 24, seq 1, length 64
22:14:11.528738 IP one.one.one.one > 10.0.100.2: ICMP echo reply, id 24, seq 1, length 64
22:14:12.553010 IP 10.0.100.2 > one.one.one.one: ICMP echo request, id 24, seq 2, length 64
22:14:12.553468 IP one.one.one.one > 10.0.100.2: ICMP echo reply, id 24, seq 2, length 64
22:14:13.577950 IP 10.0.100.2 > one.one.one.one: ICMP echo request, id 24, seq 3, length 64
22:14:13.578431 IP one.one.one.one > 10.0.100.2: ICMP echo reply, id 24, seq 3, length 64

問題なさそうですね。

Docker コンテナ上の Web サーバーを VPS 経由でインターネットに公開する

最後に自宅サーバーの Nginx コンテナに VPS 経由でアクセスできるようにしてみましょう。

VPS には 80/tcp ポートに来た通信を 10.0.100.2:80 に繋ぐ DNAT の設定を入れます:

# VPS 上で実行
iptables -t nat -A PREROUTING -p tcp --dport 80 -m addrtype --dst-type LOCAL -j DNAT --to-destination 10.0.100.2:80

自宅サーバーの方には 80/tcp ポートに来た通信を 10.1.1.100:80 (ここに Nginx を立てる想定) に繋ぐ DNAT の設定を入れます:

# 自宅サーバー上で実行
iptables -t nat -A PREROUTING -p tcp --dport 80 -m addrtype --dst-type LOCAL -j DNAT --to-destination 10.1.1.100:80

--ip オプション指定で 10.1.1.100:80 に Nginx コンテナを立ち上げます:

# 自宅サーバー上で実行
docker run -it --rm --network wg_test --ip 10.1.1.100 nginx

ブラウザから http://<VPS のグローバル IP アドレス> にアクセスしてみます:

無事 Nginx の画面が表示されました。

Nginx のログを見てみます:

一部隠していますが、アクセス元の IP アドレスが自宅のグローバル IP アドレスになっています。

Docker ネットワークのデフォルトゲートウェイを WireGuard 越しで VPS に設定しているので SNAT が不要なため、アクセス元の IP アドレスが保持できているというわけです。

まとめ

自宅サーバーの Nginx コンテナを WireGuard を使い VPS 経由で公開することができました。

SSH のポートフォワードよりも当然ですが自由度が高く、使い勝手もよさそうなので、前向きに利用を検討したいと思います。

今回は試さなかったですが、UDP でも同じようにできるはずなので、UDP を使うゲームサーバーを公開するのにも使えそうです。

Copyright © 2017 ttk1