従来型VPNルーティングが大規模環境で破綻する理由
開発者のワークステーション、エッジクラスタ、マルチクラウド上のVMを相互接続する際、これまでは妥協を強いられるのが一般的でした。OpenVPNのような従来のハブ&スポーク型システムでは、すべてのパケットが中央のゲートウェイを経由します。月額20ドル程度の単一クラウドVPSに800Mbpsのトラフィックを流そうとすれば、CPU使用率は瞬時に100%に張り付き、レイテンシは15msから140msへと急増し、ファイル転送は完全に滞ってしまいます。
この状況を一変させたのがTailscaleです。軽量な協調サーバーを介してポイントツーポイントのWireGuardトンネルを確立することで、クライアントマシンはNATの障壁を越えて直接通信できるようになります。トラフィックは常に最短の物理ルートを通過します。しかし、HIPAAやSOC 2、あるいは厳格なゼロトラスト要件に縛られている組織にとって、Tailscaleがホストする協調サーバーに依存することには重大な制約が伴います。ルーティングのメタデータ、ノードの公開鍵、デバイスのマッピング情報がサードパーティのインフラ上に残ってしまうためです。
エアギャップ環境のテストベッドを運用している場合や、ネットワークのメタデータを自社の境界外に一切出さない保証が求められる場合、外部の協調サーバーを利用することはできません。Headscaleは、コントロールプレーンを自前で保持できるようにすることで、この課題を解決します。
アーキテクチャ:コントロールプレーンとデータプレーン
制御フローとデータフローの違いを理解しておくと、トラブルシューティングをスムーズに進めることができます。
- データプレーン(WireGuard): 実際の通信トラフィックは、エンドポイント間においてUDPによるPeer-to-Peer(P2P)で流れます。これらのパケットが協調サーバーを経由することはありません。対称型CGNATの背後にあるなど、直接のNATホールパンチングに失敗した場合のみ、フォールバックとして暗号化されたDERP(Designated Encrypted Relay for Packets)リレーを介してルーティングされます。
- コントロールプレーン(Headscale): Headscaleは、Go言語で記述されたTailscale協調サーバーの軽量なオープンソース実装です。公開鍵の配布、キャリアグレードNATプール
100.64.0.0/10からの内部IPアドレスの割り当て、アクセスコントロールリスト(ACL)の配信などを担います。
HeadscaleはTailscaleネイティブのプロトコルを採用しているため、Linux、macOS、iOS、Windows、Android向けの標準クライアントパッケージがそのまま動作します。洗練されたクライアントのUI/UXを維持しながら、ネットワークトポロジの管理権限をSaaSベンダーに渡さずに済むのが大きな利点です。
実践:DockerとNginxによるHeadscaleのデプロイ
Tailscaleクライアントがノードマップを取得し、WireGuardの鍵を交換するには、有効なHTTPSエンドポイントが必要です。ここでは、Let’s EncryptでTLS終端を行うNginxのリバースプロキシの背後で、コンテナとしてHeadscaleを起動します。
1. ディレクトリと設定ファイルの準備
サーバー上にプロジェクトのディレクトリツリーを作成し、デフォルトの設定テンプレートを取得します。
mkdir -p /opt/headscale/config /opt/headscale/data
cd /opt/headscale
wget -q https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml -O config/config.yaml
config/config.yamlを開き、公開ドメイン、リッスンポート、IPプールを設定します。
server_url: https://headscale.example.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090
ip_prefixes:
- 100.64.0.0/10
- fd7a:115c:a1e0::/48
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite
log:
format: text
level: info
2. Docker Composeによるデプロイ
/opt/headscale/配下にdocker-compose.ymlを作成します。
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
restart: unless-stopped
volumes:
- ./config:/etc/headscale
- ./data:/var/lib/headscale
ports:
- "127.0.0.1:8080:8080"
command: headscale serve
コンテナを起動します。
docker compose up -d
3. TLS対応Nginxリバースプロキシのセットアップ
Tailscaleクライアントは協調サーバーを常時ポーリングし、アクティブなWebSocket接続を維持します。Nginxの設定ではUpgradeヘッダーを中継し、プロキシバッファリングを無効化してください。
server {
listen 80;
server_name headscale.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name headscale.example.com;
ssl_certificate /etc/letsencrypt/live/headscale.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/headscale.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
ユーザーの作成とノードの登録
Headscaleでは、「ユーザー」と呼ばれる名前空間でマシンをグループ化します。まずは運用インフラ用の初期ユーザーを作成しましょう。
docker exec -it headscale headscale users create ops-team
方法A:事前認証キー(Pre-Auth Keys)による自動参加
無人サーバー、CI/CDノード、クラウドインスタンスの場合、Web画面での手動認証よりも自動登録が適しています。30日間有効な再利用可能キーを生成します。
docker exec -it headscale headscale preauthkeys create --user ops-team --reusable --expiration 720h
コマンドを実行すると、48文字のトークン(例: 9e2c6b4a8f103d...)が出力されます。このキーをクライアントのCLIに渡します。
sudo tailscale up \
--login-server https://headscale.example.com \
--authkey 9e2c6b4a8f103d81b9ecf0246a81d09e \
--accept-routes
方法B:ノートPCやワークステーション向け対話型登録
個人のノートPCをオンボーディングする場合は、認証キーなしで接続を開始します。
sudo tailscale up --login-server https://headscale.example.com
ターミナルに登録リンク(例: https://headscale.example.com/register/mkey:abc123def456)が表示されます。キーのパラメータを抽出し、Headscaleホスト側でデバイスを承認します。
docker exec -it headscale headscale nodes register --user ops-team --key mkey:abc123def456
ノードに内部IPアドレス(例: 100.64.0.2)が割り当てられたことを確認します。
docker exec -it headscale headscale nodes list
本番運用から得られた知見
ベアメタルノードやAWSインスタンスにまたがってHeadscaleを運用すると、事前に考慮しておくべき現実的なエッジケースがいくつか見えてきます。
1. 高負荷時におけるSQLiteの安全なバックアップ
SQLiteは40MB未満のメモリ消費量で300〜500程度のアクティブピアを容易に処理できます。ただし、単純なファイルコピーを行うと、鍵交換中に不完全な書き込みやページの破損が発生するリスクがあります。毎日のcronジョブ内でSQLiteのオンラインバックアップツールを使用してください。
sqlite3 /opt/headscale/data/db.sqlite ".backup '/var/backups/headscale-$(date +%F_%H%M).sqlite'"
2. レガシーサブネット向けのサブネットルート公開
プリンター、レガシーストレージ機器、ベアメタルのデータベースなどでは、クライアントバイナリを直接実行できない場合があります。その場合は、特定のゲートウェイマシンからサブネットをアドバタイズさせます。
sudo tailscale up --login-server https://headscale.example.com --advertise-routes=192.168.10.0/24
サーバー側でルートを確認して有効化するまで、Headscaleはパケットを転送しません。
# 利用可能なルートを一覧表示し、ルートID(例: ID 1)を確認
docker exec -it headscale headscale routes list
# ルートID 1を有効化
docker exec -it headscale headscale routes enable -r 1
3. DERPリレーへのフォールバックを防止する
WireGuardの直接ハンドシェイクはUDPポート41641に依存しています。クラウドのセキュリティグループやローカルのファイアウォールで受信UDPがブロックされていると、ピアは通知なしにDERPリレーサーバーへフォールバックします。その結果、レイテンシは8msから90ms超へと急増し、スループットは大幅に低下します。ホストのファイアウォールでUDP 41641を必ず開放してください。2つのメッシュエンドポイント間で簡易チェックを実行できます。
tailscale ping 100.64.0.3
direct 198.51.100.22:41641ではなくpong from ... via DERP(fra)と表示された場合は、直ちにインバウンドUDPファイアウォールルールを確認してください。
インフラストラクチャの次のステップ
Headscaleをセルフホストすることで、メッシュネットワーキングの高速性、標準のTailscaleクライアントバイナリ、そしてルーティングテーブルに対する完全な制御権を自組織のものにできます。ライセンスプランの制限や予期せぬレート制限、外部へのトラッキングも一切ありません。クラウドインスタンス上にテストノードを立ち上げ、認証キーを生成し、手元のハードウェアでダイレクトピア通信の低遅延を体感してみてください。

