はじめに
私は自宅サーバーで動かしてるサービスをインターネットに公開する際、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 設定の AllowedIPs
を 0.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 を使うゲームサーバーを公開するのにも使えそうです。