HEARTBEATS

こんにちは、滝澤です。前回の記事『OctoDNSとGitLab CI/CDを利用した複数DNSプロバイダー構成の運用』に引き続き、社内事例を紹介します。

弊社ハートビーツではMSP(Managed Service Provider)サービスの可用性向上のために、社内基盤をマルチクラウド構成で運用しています。 複数のクラウド拠点のネットワークおよび事務所のネットワークとの間をWireGuardというVPNトンネルのソフトウェアで接続しています。 今回はこのWireGuardの利用事例を紹介します。

行っていることをまとめると次のようになります。

  • マルチクラウド構成(Azure, AWS, GCP)の各拠点と事務所のネットワーク間をWireGuardによるVPNで接続している。クラウド拠点間のレイテンシーはVPNルーター間で2〜4ミリ秒、分散システムのノード間で2〜6ミリ秒である。
  • ピア(対向ノード)毎にWireGuardのインターフェースを作成し、ルーティング制御にはルーティングデーモンのbirdを用いてBGPで行っている。
  • VPN自動構成ツールを開発して、仮想マシン(インスタンス)起動時にVPNルーター間の接続に必要な鍵生成と鍵交換と設定ファイルの生成を行い、自動でVPN接続するようにしている。

WireGuardとは

WireGuardはVPNトンネルのソフトウェアです。公式サイトは次の場所です。

トップページに書いてある主要な特徴は次のものです。

  • Simple & Easy-to-use
  • Cryptographically Sound
  • Minimal Attack Surface
  • High Performance
  • Well Defined & Thoroughly Considered

WireGuardについての説明は次のサイトにあるプレゼンテーション資料に詳しく書かれています。

さらに詳細を知りたい方は、公式サイトで公開されているホワイトペーパーを読んでみることをお勧めします。

以降、弊社の事例を紹介します。

背景

弊社がラックを借りているデータセンターが閉鎖することになったのがきっかけです。

2019年4月から調査と計画を始め、新しい社内システム基盤をマルチクラウド構成でクラウド上に構築し、その基盤に現在運用しているサーバーを移設する方針を決めました。 このとき、分散データベースと分散ストレージを利用すること計画していました(分散ストレージは現在保留中)。 そのため、クラウド拠点間VPNの高スループットと低レイテンシーは最重要項目の一つでしたので、次に述べるようなVPNの事前調査および検証を行いました。 その結果として、拠点間VPNにWireGuardを採用するに至りました。

2019年10月から設計・構築に着手し、2020年2月末にデータセンターからの移設がすべて完了しました。

WireGuardによる拠点間VPNそのものは2020年1月下旬から運用を開始しており、現時点(2020年5月下旬)で4ヶ月運用していることになります。

事前検証

以下の内容は2019年6月に実施した検証の雑なまとめを転記したものです。 運用に手間をかけたくないため、マネージドVPNサービスを利用することも検討していました。

仮想マシンでWireGuardによるVPNを利用する場合とマネージドVPNサービスを利用する場合の性能を比較を行った。

AWS東京リージョンとAzure西日本リージョン間のVPNでの計測を行った。

結果

  • マネージドVPNサービスを利用する場合は、AWS VPNとAzure VPN Gateway共に公称の最大スループットは1.25Gbpsであるが、約0.7Gbpsしかスループットが出なかった。IPsec VPNによるレイテンシーは5ms程度増加している。
  • WireGuardによるVPNを利用する場合は、Azure側の仮想マシンのタイプ(Standard F4s_v2)によるネットワーク帯域幅の制限(1.7Gbps)の影響を受けたが、約1.6Gbpsのスループットが出た。レイテンシーは2ms程度増加している。IPsecに比べて増加幅は小さい。

なお、スループットの測定にはiperf3を利用しています(コマンド: iperf3 -c IPADDR -4 -i 1 --parallel 4)。 レイテンシーは、対向ノードのパブリックIPアドレスへのpingとトンネル内IPアドレスへのpingの応答時間の差として雑に計測しました(コマンド: ping -c10 -s1272 IPADDR)。

このとき、AWS東京リージョンとAzure西日本リージョンという距離が離れている拠点で検証を行ったのは、ディザスタリカバリーも考慮したためです。 最終的には、3つのクラウド事業者の計5カ所のゾーンすべてが利用できなくなることはないだろうと判断して、東京近郊のリージョンだけで構成することになりました。pingの応答時間を計測すると、拠点内のゾーン間が1〜2ミリ秒、拠点間が2〜4ミリ秒になるため、ネットワーク的距離はそれなりに離れていると考えられます。

拠点間VPNの概要

弊社が運用している構成は次のようになります。

構成図

クラウドの拠点として次の3拠点を利用しています。拠点については後述します。

  • Azure 東日本リージョン
  • AWS 東京リージョン
  • GCP 東京リージョン

この図のVPN Routerで、WireGuardとルーティングデーモンのbirdを動かしています。

利用している分散システムとしては、MySQLサーバーとConsulサーバーがあるため、この構成図に含めてます。 MySQLサーバーはMySQL 8.0のグループレプリケーションを運用上の制約をいくつか付けた上でマルチプライマリーモードで動かしています。

VPNルーターの仮想マシン

VPNルーターのOSとしてLinuxディストリビューションのCentOS 8.1.1911を利用しています。

PackerとAnsibleを利用して、WireGuardとbirdと後述する自動構成ツールをインストールして仮想マシンイメージを作成して、Terraformで仮想マシンを作成・起動させています。ソフトウェアのアップデートは packer buildterraform apply で完了するようにして、できるだけ運用の手間がかからないようにしています。

障害が発生した場合は、仮想マシンの停止と起動で対応します。WireGuard自体はカーネルモジュールとして動作するため、原因究明は簡単ではないということもありますし、クラウド基盤による障害もあるので起動し直した方が対応が早いということもあります。

各拠点の構成

各拠点の構成について紹介します。

Azure 東日本リージョン

Azure東日本リージョンをプライマリー拠点として利用しています。社内システムのサービスはこの拠点で動かすことになります。

Azure構成図

VPNルーターを冗長化するため、Azure東日本リージョンの2個のアベイラビリティーゾーンを選び、それぞれのゾーンにVPNルーターと分散システムの各ノードを配置しました。

Virtual NetworkからVPNルーターへのアクセスを冗長化するために、内部ロードバランサーとしてAzure Standard Load Balancerを利用しています。 Azure Standard Load BalancerにはHA ports(高可用性ポート)という機能があり、これを使うことにより、他拠点向けのすべてのアウトバウンドのトラフィックを冗長化できます。 また、Floating IP(一般的にDirect Server Return (DSR) と呼ばれるもの)という機能もあり、これを利用することにより、他拠点からのインバウンドのパケットはロードバランサーを経由せずに戻るため、レイテンシーを低減できます。

Azure Load Balancerの詳細は以下のサイトをご覧ください。

AWS 東京リージョン

AWS東京リージョンをセカンダリー拠点として利用しています。監視システムや踏み台サーバーなど高可用性が要求される社内システムの場合は、アクティブ/アクティブ構成あるいはアクティブ/パッシブ構成として、プライマリー拠点に加えて、こちらの拠点でも動かします。

AWS構成図

AWS東京リージョンの2個のアベイラビリティーゾーンを選び、それぞれのゾーンにVPNルーターと分散システムの各ノードを配置しました。

AWSにはAzure Standard Load Balancerのようなすべてのトラフィックを負荷分散するマネージドのロードバランサーがありません(構築開始の2019年時点)。そのため、他拠点向けのすべてのアウトバウンドのトラフィックを冗長化することができません。

VPNルーターの障害発生時の影響範囲を緩和させるために、ゾーン内のサブネットのルートテーブルに対して、他拠点向けのルーティングを同じゾーンのVPNルーターに向けるようにしました。VPNルーターに障害が発生すると同じゾーンに配置した分散システムのノードも利用不可になります。しかし、別のゾーンに配置した分散システムのノードは正常に動作し続けます。分散システムのノードはVPNルーターと運命を共有することになります。

GCP 東京リージョン

GCP東京リージョンを調停ノード用の拠点として利用します。この拠点では社内システムのサービスは動かしません。

GCP構成図

GCP東京リージョンの1個のゾーンを選び、そのゾーンにVPNルーターと分散システムのノードを配置しました。

分散システムのクオーラム(定足数)の判定に利用するためのノードをこの拠点に配置します。 AzureやAWSのリージョンやゾーンにおいて何らかの障害によりネットワーク分割が発生した場合に、この拠点のノードにアクセスできるノード側がクオーラムを満たし、マジョリティ(過半数)を取得して、サービスを継続できることになります。本システムではクラウド事業者のリージョン規模での障害が発生してもサービスを継続できるようにすることを想定しています。

ネットワーク分割

また、この拠点が一時的に利用不可になっても問題ないため、VPNルーターの冗長化は行いません。

WireGuardによるレイテンシー

簡易的な測定として、pingでレイテンシーを計測してみました(コマンド: ping -c10 -s1272 IPADDR)。

クラウド拠点間(トンネルなし)のVPNルーター間では2〜4ミリ秒でした。WireGuardによるトンネルを経由した場合でも同じく2〜4ミリ秒でした。

レイテンシーが2〜4ミリ秒のネットワーク間の場合は、ネットワークの揺らぎの方が大きく、WireGuardによる明確なレイテンシーの増加はわからないという結論でした。

クラウド拠点間のレイテンシー

簡易的な測定として、pingでレイテンシーを計測してみました(コマンド: ping -c10 -s1272 IPADDR)。

クラウド拠点間(WireGuardによるトンネル経由)では、VPNルーター間で2〜4ミリ秒、分散システムのノード間で2〜6ミリ秒になります。

後者の分散システムのノード間のレイテンシーが大きくなる理由は、必ずしもゾーンを考慮した最短ルートを経由しているわけでは無いためです。ノードとは別のゾーンのVPNルーターを経由してしまい、ゾーン間の1〜2ミリ秒のレイテンシーが加わってしまうことがあります。

この点はもう少しゾーンを考慮した細かいルーティング情報を付与して、改善できるのではないかと考えています。

WireGuardの設定

ここからはWireGuardの設定について紹介します。

WireGuardはLinuxのカーネルモジュールとして組み込まれ、ネットワークインターフェースとして実装されています。

コマンドにより動的にネットワークインターフェースを生成・設定することもできますし、設定ファイルからネットワークインターフェースを生成することもできます。今回は設定ファイルからネットワークインターフェースを生成するようにしています。

次の例は公式サイトに掲載されている例です。

[Interface]
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
ListenPort = 51820

[Peer]
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
AllowedIPs = 10.192.122.3/32, 10.192.124.1/24

[Peer]
PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
AllowedIPs = 10.192.122.4/32, 192.168.0.0/16

[Peer]
PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
AllowedIPs = 10.10.10.230/32

Interfaceセクションには自身のネットワークインターフェースの情報を、Peerセクションにはピア(対抗ノード)の情報を設定します。

WireGuardは公開鍵ペアを利用して認証を行っています。PrivateKeyには自身のプライベート鍵を、PublicKeyにはピアの公開鍵を設定します。これはSSHの公開鍵認証を参考にしているそうです。

AllowedIPsにはピアの拠点内のネットワークアドレスを設定します。

WireGuardのネットワークインターフェースには複数のピアに対してトンネルを構成することができます。上記の例では3つのピアを設定しています。 WireGuardでは1つのインターフェースに複数のピアを設定すると、それぞれのピアに対してルーティングを行うことができます。 宛先がAllowedIPsに記述されたネットワークに含まれるピアに対してパケットを送信します。送信元がAllowedIPsに記述されたネットワークに含まれるピアからのパケットの受け取りを許可します。これをWireGuardではCryptkey Routingと呼んでいます。

冗長化

ここで再び構成図を見てみましょう。

構成図

事務所とAzureとAWSの拠点ではVPNルーターを冗長化のためにそれぞれ2台配置しています。

試しに、1つのWireGuardのネットワークインターフェースに同じ拠点の2台のVPNルーターをピアとして設定してみます。しかし、AllowedIPsは同じネットワークアドレスになるため、冗長化できることを期待したいところですが、実際にはトンネルの冗長化としては機能しません。

そのため、経路冗長化を行うために、ピア毎にWireGuardのネットワークインターフェースを作成し、ルーティングプロトコルで経路制御を行う必要があります。 今回のケースではルーティングデーモンのbirdを利用してBGPで経路制御を行っています。ちなみに、今回は拠点間のVPNルーター間はeBGPで、拠点内のVPNルーター間はiBGPで制御しています。

実際の設定例の一部を書き換えた例を紹介します。

# wg20.conf
# azure-vpngw01
[Interface]
PrivateKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ListenPort = 51820
Table = off
Address = 172.31.20.1/24

# office-vpngw02
[Peer]
PublicKey = YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
PresharedKey = ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
Endpoint = 192.0.2.2:51820
AllowedIPs = 172.31.20.0/24, 10.0.0.0/16, 172.16.0.0/16, 192.168.0.0/16
PersistentKeepalive = 60

ルーティングをBGPで制御するため、「Table = off」を設定します。

WireGuardのネットワークインターフェース毎にピアを一つだけ設定します。

AllowedIPsにはトンネルのネットワークアドレスとピアの拠点のネットワークアドレスを記述します。他のトンネルの利用不可時の迂回経路として、このトンネルが利用される場合は、さらに他の拠点のネットワークアドレスも記述します。

VPNの自動構成

ピア毎に上述のようなWireGuardのネットワークインターフェースの設定を記述する必要があります。しかし、弊社のケースでは全部で21個のトンネルがあり、42個のネットワークインターフェースの設定を行う必要があり、手動で設定するのは非常に煩雑で手間がかかります。そのため、弊社では自動構成するツールを開発しました。

自動構成ツールではVPNルーターの仮想マシンの起動時に次のことを行います。

  • VPNルーターのネットワーク情報の取得: サービスディスカバリーを利用
  • WireGuardの公開鍵ペアの生成
  • ピア毎にトンネルの生成
    • WireGuardの公開鍵の交換
    • WireGuardの事前共有鍵の生成と共有
    • ピアのWireGuardのインターフェースの生成と有効化
    • 自身のWireGuardのインターフェースの生成と有効化
  • birdの設定ファイルの生成と有効化

Packer + Ansbileで上記の自動構成ツールを自動起動するようにした仮想マシンイメージをAzure用、AWS用、GCP用に作成し、Terraformにより仮想マシンを起動するとVPNが自動構成される仕組みになっています。 実際にはもう少し細かい作業があるのですが、おおよその流れはこのようになっています。

知見の紹介

以上が弊社のWireGuardによるVPNの事例紹介となります。

以降は、検証・構築中に貯まった知見のいくつかを紹介します。 当たり前のようなことも書いていますが、思い出すために利用していただければ思います。

カーネルパラメータ

net.ipv4.ip_forward

パケットを転送するので1にします。

net.ipv4.conf.default.rp_filter, net.ipv4.conf.all.rp_filter

RPF(Reverse Path Forwarding)を無効にするため、0にします。 経路冗長化を行うため、行きと帰りの経路が異なることは普通に生じるため、RPFによりパケットが破棄されることを防ぎます。

net.ipv4.ip_local_reserved_ports

WireGuardで利用するポートを予約しておきます。

net.core.rmem_default, net.core.rmem_max

WireGuardはUDPを使うため、大きめに設定しておきます。

Linux側のファイアウォール

ステートフルファイアウォールによるパケット破棄を避けるため、firewalldを停止しています。 経路冗長化を行うため、行きと帰りの経路が異なることは普通に生じるためです。

アクセス制御はクラウド側のファイアウォールの機能(AzureのNetwork Security Group, AWSのSecurity Group, GCPのFirewall)で行います。

ルーティング

拠点のネットワーク全体へのルーティング

VPNルーターが持っている静的ルーティングの情報がBGPで伝搬するため、VPNルーターに拠点内のネットワーク全体に対する静的ルーティングを設定します。

AzureのVNet、AWSのVPC、GCPのVPCのネットワークアドレス宛のネクストホップをVPNルーターが配置されているサブネットのゲートウェイのIPアドレスに指定します。このIPアドレスは次のようにしてDHCPで払い出されるデフォルトゲートウェイとしても把握できます。

$ ip route show default
default via 10.1.0.1 dev eth0 proto dhcp metric 100 

これはipコマンドで設定してもよいですし、birdで静的ルーティングを設定してもよいです。

bird.confの例は次の通りです。ネットワークアドレスが10.1.0.0/16で、ネクストホップが10.1.0.1です。

protocol static {
  ipv4;
  route 10.1.0.0/16 via 10.1.0.1;
}

GCPのサブネットのゲートウェイ

GCPのサブネットのデフォルトゲートウェイはICMPの応答がありません。そのため、上記の静的ルーティングを設定しても無効化されます。 これを回避するために、次のようにipコマンドでonlinkを指定してルーティングを設定します。

$ sudo ip route add 10.1.0.0/16 via 10.1.0.1 dev eth0 proto static onlink

拠点内のVPNルーター間の通信

拠点内のVPNルーター間の通信もトンネル経由で行います。 クラウドのネットワークの場合は、トンネルを経由しないと、クラウドのネットワークのルーティングに制御が移り、BGPでルーティングを制御できなくなるためです。

クラウド側の設定

ファイアウォール

クラウド側のファイアウォール(AzureのNetwork Security Group、AWSのSecurity Group、GCPのFirewall)に対して他拠点のIPパケットに対する送受信の許可ルールを追加します。

仮想マシンの転送許可設定

クラウド側のデフォルトの設定により、仮想マシンは自身のIPアドレス宛のパケット以外は受け取らないようになっているため、受け取る設定を行います。

Azureの場合は、仮想マシンのNetwork Interfaceの設定において「IP forwarding」を有効にします。

AWSの場合は、EC2インスタンスの設定において「Networking」の「Source/Dest. Check」を無効にします。

GCPの場合は、VMインスタンスの設定において「IP forwarding」を有効にします。

ネットワーク帯域幅の制限

利用する仮想マシンのタイプによって、ネットワーク帯域幅の制限が異なる点に注意してください。

VPNの可用性

MySQL 8.0のグループレプリケーションのメンバーステータスの監視を行っていて、メンバーステータスがUNREACHABLEになるアラートが時々発生しています。通常は数分経つと自動的に復帰します(自動復帰する設定を入れています)。ネットワークが復帰してもステータスが復帰しないこともありますが、その話は別の機会に。

メンバーステータスがUNREACHABLEになる原因を調べると、VPNルーター間の経路上でネットワーク断が発生していることがあります。VPNルーターのネットワーク周りで問題が発生していることもありますし、ゾーンのネットワークで問題が発生していることもあります。

マルチクラウド構成のソフトウェアによる拠点間VPNは、このような数分間のネットワーク断が毎月数回発生することを許容して運用する必要があります。

まとめ

  • マルチクラウド構成(Azure, AWS, GCP)の各拠点と事務所のネットワーク間をWireGuardによるVPNで接続している。クラウド拠点間のレイテンシーはVPNルーター間で2〜4ミリ秒、分散システムのノード間で2〜6ミリ秒である。
  • ピア(対向ノード)毎にWireGuardのネットワークインターフェースを作成し、ルーティング制御にはルーティングデーモンのbirdを用いてBGPで行っている。
  • VPN自動構成ツールを開発して、仮想マシン起動時にVPNルーター間の接続に必要な鍵生成と鍵交換と設定ファイルの生成を行い、自動でVPN接続するようにしている。

参考サイト

株式会社ハートビーツの技術情報やイベント情報などをお届けする公式ブログです。