NebulaでMesh VPNネットワークを構築する:Slackが作った数千台のサーバーを安全に接続する方法

Network tutorial - IT technology blog
Network tutorial - IT technology blog

インフラが大きくなるにつれて従来の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 sentHandshake 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.hostsstatic_host_mapに2台目のlighthouseのIPを追加するだけでよい。
  • 証明書の有効期限:デフォルトの証明書には有効期限がない——便利に聞こえるが、証明書が漏洩した場合は非常に危険だ。証明書署名時は必ず-duration 8760h(1年)を設定し、定期的なローテーションのスケジュールを組む。
  • MTUのオーバーヘッド:Nebulaはヘッダーに約60バイトを追加する。アプリがタイムアウトしたりパケットロスが原因不明で発生する場合は、設定にtun: mtu: 1300を追加してみる。
  • ノードの失効:ノードをすぐにネットワークから除外したい場合は、そのノードの証明書をすべてのノードの設定のpki.blocklistに追加してリロードする——Nebulaはその証明書からの接続を即座に拒否する。

WireGuardと比べると、Nebulaは初期セットアップの手間がかかる。しかし、ノードの設定に直接組み込まれたファイアウォールルールは、ノード数が10を超えたときのアクセス制御管理を大幅に楽にしてくれる。別途iptablesは不要で、サーバー間でルールを手動同期する必要もない。インフラが拡大し続けている環境では、従来のハブアンドスポーク型OpenVPNの有力な代替候補だ。

Share: