プライベートクラウドグループの中西(@whywaita)です。JOBの制度を用いてインターンに参加して頂いた川原さんからの寄稿記事です。こんにちは、インターン生の川原です。Driverに関するバグについて検証と修正方法について紹介します。以下の図のようにあるVMからハイパーバイザノード外のホストに対して帯域を多く消費するようなベンチマークを行なったところ、ハイパーバイザノードで起動しているVMの通信量が以下のグラフの様になりました。上半分がそれぞれのVMが送信した通信量、下半分が受信した通信量です。Mbps程度であることがわかります。対して、送信元VMと同じネットワークに接続しているVMの受信量はベンチマークを実施しているVMの送信量とほぼ同値です。このことから、あるVMがハイパーバイザノード外部に送信したパケットを同じブロードキャストドメインに接続しているVMが受信していると考えられました。14:00ごろにVMが3台増えるのと同時にベンチマークの送信量が悪化していることから、パケットを受信するVMが増えることで実効帯域が低下する問題があることがわかります。発生している現象をまとめるとあるVMがハイパーバイザノード外のホストに送信したパケットを、同じハイパーバイザノードで実行されている、かつ同じネットワークに接続しているVMが受信するとなります。この現象によって、一つのハイパーバイザノードで同じネットワークに接続しているVMが増加するほど実効帯域が低下します。いう形でチケットになっています。はじめはパフォーマンスの問題として起票されていましたが、他のVMの通信が見えるためセキュリティの問題としても認識されています。同じハイパーバイザノードに同じネットワークに接続するVMが起動することは大規模環境では起きづらいため重要な問題として認識されておらず、長い期間放置されていました。今回の問題が発生した環境は以下のとおりです。vSwitchのテーブル設計の不備によって外部ホストのMACアドレスが学習されず外部ホストへ通信が全て内部のブリッジでフラッディングされていたことによって発生していました。フラッディングされていた原因は、内部VMから外部ホストへの通信はNORMALアクションによって実現されていますが、外部ホストから内部VMへの通信はoutputアクションによって実現されていたためです。詳細について、自分がたどった思考の順に記述していきます。現象を確認した際、あるVMが送信したパケットを他の全てのVMが受信していることから、一つのパケットが複製されていることがわかりました。ネットワークにおいてパケットを複製する処理というのは数少なく、パケットがフラッディングされているかQEMUのドライバ周りのバグではないかと予想しました。Ethernetでは宛先のアドレスとポート番号を学習することでパケットを宛先に送り届けます。受信したパケットの宛先アドレスを学習できていない場合、パケットを破棄するわけにはいかないので受信したポート以外のすべてのポートにパケットを送信するフラッディングという処理を行います。vSwitchでフラッディングしている部分を探しました。継続的に増加していたためベンチマークのパケットがこのフローを通っていると考えました。vSwitchがパケットをL2スイッチングしてくれます。一般的なL2スイッチのように宛先のMACアドレスが学習されていない場合、フラッディングします。いうコマンドで学習されたMACアドレスを確認したところ、宛先として設定される外部ホストのMACアドレスが学習されていませんでした。このことから、外部ホストのMACアドレスが学習されていない状態でNORMALアクションが呼び出されていることが他のVMにパケットが送られてしまう原因であるとわかりました。次に、なぜ外部ホストのMACアドレスが学習されなかったのかを調査しました。vSwitchのNORMALアクションは一般的なL2スイッチと同様に、あるMACアドレスが送信元として設定されたパケットをあるポートから受信した際にその組を学習します。今回の場合、外部ホストのMACアドレスを学習していないため、外部ホストが送信元として設定されたパケットである外部ホストから内部のVMへのパケットが通る経路を調査する必要があります。あることを思い出しため、それを利用しました。内部VMから外部ホストへのパケットは以下のようになります。94)というふうになっていることを確認できます。floodingと記述されている通り、MACアドレスを学習していない状態でNORMALアクションが実施され、フラッディングしていることを確認できます。次に外部ホストから内部VMへの通信は以下のように処理されます。82)という処理を行っていることがわかります。outputアクションとはOpenFlowの標準命令で、あるポートに対してパケットを送信するという命令です。送受信の処理を図にまとめると以下の通りになります。VMから外部ホストへの通信はNORMALでL2スイッチングされていますが、外部ホストからVMへの通信はoutputアクションでL2スイッチングされており、外部ホストのMACアドレスが学習されていないことがわかります。まとめると、内部VMから外部ホストへの通信はNORMALアクションによって実現されているが、外部ホストから内部ホストへの通信はoutputアクションによって実現されていたため外部ホストのMACアドレスが学習されず、外部ホストへ通信が全てbr-intでフラッディングされていたためとなります。対策としては送信と受信のどちらもoutputアクションかNORMALアクションのどちらかで処理するように統一する必要があります。前述のLaunchpadにコメントされている修正(https://review.opendev.org/#/c/666991/)はoutputアクションで統一するアプローチをとっています。今回の環境で実験したところフラッディングは部分的にしか抑制されませんでした。outputアクションで統一する場合、動的に変化する可能性のある外部ホストのMACアドレスを学習するにはOpenFlowの制約によってコントローラという外部プロセスが必要になります。Driverにはコントローラが存在しないためこのアプローチで解決するのは難しいと私は考えています。そこで、NORMALアクションで統一するアプローチで以下のようなパッチを作成しました。先程の図を更新すると以下のようになります。rules実験を行ったところ、実際にパケットのフラッディングを以下のように防げていることが確認できました。17:20頃にパッチを適用し、漏洩していたパケットが止まるのと同時にベンチマークのパフォーマンスも4倍近く向上していることがわかります。先程は外部ホストから内部VMへの通信でoutputアクションが使われていましたが、フローのシミュレーションでも以下のようにNORMALアクションでL2スイッチングするようになったことを確認できます。82)というふうになります。内部VMから外部ホストへの通信は以下のようになります。push_vlan(vid=1370,pcp=0),2ここでは、以下のようにMACアドレスが学習されたことでフラッディングしなくなったことを確認できます。Driverにおける「他のVMのパケットを受信する」というバグについて紹介しました。原因はパケットの送受信で二つの異なった手法で仮想ネットワークを構築していたため、外部ホストのMACアドレス学習が行われずVMから外部ホストへの通信がフラッディングされていたためでした。そのため、送受信双方を一つの手法でL2スイッチングするように修正することで問題が解決したことを確認しました。このバグは同じハイパーバイザノードに同じネットワークのVMが起動しないと発生しないことに加えて、広帯域の通信を行わない限り気づくことすら難しいです。このような高負荷環境でのみ顕在化するバグを修正できたことはインターンシップならではの体験でした。メンターの皆さんを始め、インターンシップ中にお世話になった皆さま本当にありがとうございました。今後は、この修正案をOpenStackに報告しようと考えています。修正案があるためマージされるかはわかりませんがOSSへの貢献だと思ってがんばります。NeutronOpenStackインターンエンジニアプライベートクラウドwhywaita2019年新卒入社。現在はメディア事業部プライベートクラウドグループにて、プライベートクラウドの開発などを行っています。