インフラが大きくなるにつれて従来のVPNは破綻し始める
複数のクラウドプロバイダーにまたがる数十台のサーバー——バージニアのAWS、ニュルンベルクのHetzner、シンガポールのVPS数台——を管理していると、ハブアンドスポーク型のOpenVPNやWireGuardではもう対応できないことに気づく。すべてのトラフィックが中央サーバーを経由しなければならない。レイテンシーは増大する。その中央サーバーが落ちれば、ネットワーク全体が止まる。
Slackはまさにこの問題に数千ノードの規模で直面した。彼らは自ら構築し、Nebulaとしてオープンソース化した——メッシュVPNオーバーレイで、各ノードがlighthouse(トラフィックをリレーせずに経路を示すだけのノード)による認証を経て、互いにピアツーピアで直接接続する。単一障害点はなくなる。ボトルネックもなくなる。
3つの異なるデータセンターにある3台のVPSと1台の開発用ラップトップをNebulaで接続してみた。30分のセットアップですべてが動作した。同じリージョン内の2ノード間のレイテンシーは、リレー経由のWireGuardと比べて約15ms低かった。
Nebulaの動作の仕組み
各ノードは自分で選んだ範囲(通常は192.168.100.0/24)の仮想IPを受け取る。ノード間のトラフィックはNoise Protocol Frameworkで暗号化される——WireGuardと同じ基盤だが、アーキテクチャはまったく異なる。4つの点が違いを生む:
- Lighthouse:パブリックIPを持つノードで、他のノードが互いを発見するのを助ける(STUNサーバーのように機能する)。トラフィックはリレーせず、ディスカバリーのみを担当。
- 真のピアツーピア:ディスカバリー後、2つのノードはUDPホールパンチングで直接接続する——両方がNATの背後にあっても。
- 証明書ベースの認証:各ノードは自分のCAが署名した証明書を持つ。有効な証明書がなければネットワークに入れない、それだけのことだ。
- コンフィグに組み込まれたファイアウォール:オーバーレイ層でトラフィックを制御し、別途iptablesは不要。
Nebulaのインストール
要件
- パブリックIPを持つサーバーが最低1台(lighthouseとして使用)
- Linux/macOS/Windows対応
- lighthouseのファイアウォールでUDPポート4242を開放
バイナリのダウンロード
各ノード(lighthouseを含む)で、GitHubリリースからバイナリをダウンロードする:
# Linux x86_64の場合
wget https://github.com/slackhq/nebula/releases/latest/download/nebula-linux-amd64.tar.gz
tar -xzf nebula-linux-amd64.tar.gz
sudo mv nebula nebula-cert /usr/local/bin/
CAと各ノードの証明書を作成する
このステップは管理マシンで一度だけ実行し、その後scpで各ノードに証明書を配布する。重要:ca.keyはこのマシンの外に出してはならない。
# CAを作成する
nebula-cert ca -name "MyInfra CA"
# 生成されるファイル: ca.crt と ca.key — ca.keyは厳重に秘密保持すること
# lighthouseの証明書を作成する(仮想IP: 192.168.100.1)
nebula-cert sign -name "lighthouse" \
-ip "192.168.100.1/24" \
-ca-crt ca.crt -ca-key ca.key
# 生成されるファイル: lighthouse.crt, lighthouse.key
# アプリサーバーの証明書を作成する(仮想IP: 192.168.100.10)
nebula-cert sign -name "app-server" \
-ip "192.168.100.10/24" \
-groups "servers" \
-ca-crt ca.crt -ca-key ca.key
# 開発用ラップトップの証明書を作成する(仮想IP: 192.168.100.50)
nebula-cert sign -name "dev-laptop" \
-ip "192.168.100.50/24" \
-groups "developers" \
-ca-crt ca.crt -ca-key ca.key
オーバーレイIPのサブネット計算が必要なときはtoolcraft.app/ja/tools/developer/ip-subnet-calculatorをよく使っている——CIDRを入力するとネットワーク範囲、ブロードキャスト、最大ホスト数がすぐに分かる。手計算よりずっと楽だ。
詳細な設定
Lighthouseの設定
lighthouseサーバー上に/etc/nebula/config.yamlを作成する:
pki:
ca: /etc/nebula/ca.crt
cert: /etc/nebula/lighthouse.crt
key: /etc/nebula/lighthouse.key
lighthouse:
am_lighthouse: true
listen:
host: 0.0.0.0
port: 4242
punchy:
punch: true
logging:
level: info
firewall:
outbound:
- port: any
proto: any
host: any
inbound:
- port: any
proto: icmp
host: any
通常ノードの設定(app-server、dev-laptop)
pki:
ca: /etc/nebula/ca.crt
cert: /etc/nebula/app-server.crt
key: /etc/nebula/app-server.key
lighthouse:
am_lighthouse: false
interval: 60
hosts:
- "192.168.100.1" # lighthouseの仮想IP
static_host_map:
"192.168.100.1": ["203.0.113.10:4242"] # lighthouseの実際のパブリックIP
listen:
host: 0.0.0.0
port: 4242
punchy:
punch: true
respond: true
logging:
level: info
firewall:
outbound:
- port: any
proto: any
host: any
inbound:
- port: any
proto: icmp
host: any
# developersグループからのSSHを許可
- port: 22
proto: tcp
groups:
- developers
# サーバー間のアプリトラフィックを許可
- port: 8080
proto: tcp
groups:
- servers
証明書のデプロイと動作確認
各ノードに証明書をコピーし、systemdに登録する前にテストする:
# lighthouseにコピーする
scp ca.crt lighthouse.crt lighthouse.key root@<lighthouse-ip>:/etc/nebula/
# app-serverにコピーする
scp ca.crt app-server.crt app-server.key root@<app-server-ip>:/etc/nebula/
# フォアグラウンドで起動してログをリアルタイムで確認する
sudo nebula -config /etc/nebula/config.yaml
ログにHandshake message sentとHandshake message receivedが表示されたら、2つのノードのハンドシェイクが成功している。その後でsystemdに切り替える:
cat > /etc/systemd/system/nebula.service << 'EOF'
[Unit]
Description=Nebula VPN
After=network.target
[Service]
ExecStart=/usr/local/bin/nebula -config /etc/nebula/config.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now nebula
接続確認とモニタリング
オーバーレイネットワーク経由でPingする
すべてのノードでNebulaが動作したら、仮想IPで疎通確認する:
# app-serverからNebulaオーバーレイ経由でdev-laptopにpingする
ping 192.168.100.50
# 現在のノードの証明書情報を表示する
nebula-cert print -path /etc/nebula/app-server.crt
Prometheusメトリクスでピア状態を確認する
NebulaはPrometheusフォーマットでメトリクスを公開できる。設定に追加する:
stats:
type: prometheus
listen: 127.0.0.1:8080
path: /metrics
namespace: nebula
subsystem: stats
interval: 10s
# アクティブなトンネル数、ハンドシェイクの成功/失敗を確認する
curl -s http://127.0.0.1:8080/metrics | grep -E "(tunnel|handshake)"
ファイアウォールルールのデバッグ
# デバッグログを有効にしてパケットの許可/拒否を確認する
# 設定を変更: logging.level: debug
# 再起動後に監視する
journalctl -u nebula -f | grep -E "(ALLOW|DENY|firewall)"
実際のスループットをテストする
ネットワークが安定した後、2ノード間の帯域幅を計測する:
# 受信ノード(192.168.100.10)で実行する
iperf3 -s -B 192.168.100.10
# 送信ノードで実行する — 4並列ストリーム、30秒間テスト
iperf3 -c 192.168.100.10 -t 30 -P 4
本番運用で押さえておくべきポイント
最初の数週間の本番稼働でいろいろと躓いた。同じ苦労をしなくて済むよう、ここに書き残しておく:
- Lighthouseの冗長化:異なるデータセンターに最低2台のlighthouseを用意する。各ノードの設定の
lighthouse.hostsとstatic_host_mapに2台目のlighthouseのIPを追加するだけでよい。 - 証明書の有効期限:デフォルトの証明書には有効期限がない——便利に聞こえるが、証明書が漏洩した場合は非常に危険だ。証明書署名時は必ず
-duration 8760h(1年)を設定し、定期的なローテーションのスケジュールを組む。 - MTUのオーバーヘッド:Nebulaはヘッダーに約60バイトを追加する。アプリがタイムアウトしたりパケットロスが原因不明で発生する場合は、設定に
tun: mtu: 1300を追加してみる。 - ノードの失効:ノードをすぐにネットワークから除外したい場合は、そのノードの証明書をすべてのノードの設定の
pki.blocklistに追加してリロードする——Nebulaはその証明書からの接続を即座に拒否する。
WireGuardと比べると、Nebulaは初期セットアップの手間がかかる。しかし、ノードの設定に直接組み込まれたファイアウォールルールは、ノード数が10を超えたときのアクセス制御管理を大幅に楽にしてくれる。別途iptablesは不要で、サーバー間でルールを手動同期する必要もない。インフラが拡大し続けている環境では、従来のハブアンドスポーク型OpenVPNの有力な代替候補だ。

