読者です 読者をやめる 読者になる 読者になる

LinuxのARPとL2スイッチのお話

この記事は、はてなエンジニアアドベントカレンダー2016の12月19日の記事です。

developer.hatenastaff.com

昨日はid:taketo957くんの 10ms以下のレスポンスタイムを支える継続的負荷テスト - taketo957の日記 でした!

Webオペレーションエンジニアのid:masayoshiです。 2016年に入社後、基盤チームとして仮想化、ネットワーク周りを中心に見ています。

さて、この記事ではLinuxARPの挙動とその挙動から起こった問題を紹介しようと思います。

長々と記事を読みたくない人向けに結論をまとめるとLinuxARPTCP通信で使われ続ける限りキャッシュが飛ばないという挙動になるので、複数のL2スイッチにまたがったセグメントや非対称ルーティングをしている場合は気をつけましょうという話が書かれています。

今回紹介する事例自体は別に新規性もなく、私が入社前から社内でも知られていて対策がされている内容なのですが、ネットワークに詳しくないインフラエンジニアやアプリケーションエンジニアはあまりこういう事例や、動作が説明されている記事がなく、検索してたどり着くのが大変そうな気配がしたので、自分の勉強ついでに書いてみることにしました。

はじめに

はてなでは、ロードバランサーとしてLVS(Linux Virtual Server) + keepalivedを利用しています。 LVSではNAT,DR,TUN形式が利用できますが、リアルサーバ(振り分け先のサーバ)のセグメントを選ばないかつLVSに負荷がかかりにくいTUN形式を用いています。 TUN形式はIP in IPトンネリングでパケットをカプセル化して送ることで、送信元IPアドレスをリアルサーバに渡すことが出来ます。 そのため、リアルサーバからの戻りパケットがLVSを経由することなく、送信元ホストに直接パケットを送ることが出来ます。(直接返せるのはDR方式と同じですね) EC2などとは違い、リソースマネージメントがめんどくさいDC環境ではネットワークを意識しなくて良いのは非常にありがたいことです。 ここらへんのお話は先月OSCTokyoFallでの発表資料も参照していただければと思います。

speakerdeck.com

今回の記事はLVS/TUN形式で送信元ホストとLVS、リアルサーバが同じセグメントに存在し、複数のL2スイッチで構築されている環境というかなり限定された状況下で、戻りパケットがブロードキャストされるという問題です。 正確に言えばLVSは直接関係ないのですが、今回のような状況が起こるのはLVSのようなロードバランサなどを利用している場合で起きやすいと感じた(実際に発生した)のでその事例を例に説明したいと思います。

発生した問題

f:id:masayoshi:20161219103137j:plain 図1-1: 想定している正常な通信

上記の図1-1のように、送信元ホストであるホストAがVIP宛にパケットを送信し(①)、LVSがリアルサーバA,Bに振り分け(②)、リアルサーバA,BはホストAに直接返信する(③)と環境です。

ただ、実際には通信開始から5分程度経つと、下記の図1-2のように、リアルサーバBの戻りパケットがその他のホストBにも届いてしまうようになります。

f:id:masayoshi:20161219103359j:plain 図1-2: 5分後の想定しいない通信

ホストBでtcpdumpをした際に全く関係ない通信であるはずのホストAの通信が見えてしまったことで発覚しました。また、ホストB以外でも関係ないホストでホストAの通信が見えており、ブロードキャストされているようでした。

今回発生した問題はあるホスティングサービス上で構築した際に発覚したので、L2スイッチの詳細なつなぎ方などはわからないため、図は簡略表示をしています。 なぜブロードキャストになったのかを理解するにはL2スイッチの挙動を理解しなければいけないので、まず先にその話をします。

L2スイッチの挙動について

L2スイッチ(普通のLANハブでもよい)では、MACアドレステーブルというものを持っています。これはMACアドレスと物理ポート(L4のポートではない)の対応表であり、対象のMACアドレス宛てのパケットをどの物理ポートから出せばいいのか調べる際に利用します。 MACアドレステーブルに対象のMACアドレスがない場合は送出先ポートがわからないため、フラッディング(受信したポート以外にすべて送る)します。また、当然ですがブロードキャストアドレス(FF:FF:FF:FF:FF:FF)は常にフラッディングします。

このMACアドレステーブルは受信したポートとパケットの送信元MACアドレスからひも付けを行います。先ほどの図1の通信では図2-1,2-2,2-3のようにMACアドレスと、ポート番号を覚えていきます。はじめはフラッディングしていたL2スイッチも図2-3の時点でフラッディングせずに通信が可能になります。

f:id:masayoshi:20161219103611j:plain 図2-1: ホストA->LVSの通信時

f:id:masayoshi:20161219103631j:plain 図2-2: LVS->サーバAの通信時

f:id:masayoshi:20161219103652j:plain 図2-3: サーバA->ホストAの通信時

以降の通信はMACアドレステーブルを参照し、対象のポートのみに通信していきますが、MACアドレステーブルにはエージングタイム(IEEE802.1Dでは300秒とされている)が設けられており、エージングタイム以降は再学習することになっています。細かい仕様は機器毎に違ったりしますが、大体300秒程たった後に再学習出来ないとMACアドレステーブルが更新されないため、対象のMACアドレスへの通信はフラッディングになります。

MACアドレステーブルと非対称ルーティング

これまでの説明で、通信開始から5分程度でブロードキャストになるというのがMACアドレステーブルのエージングタイム(300秒)が切れたタイミングであることから、MACアドレステーブルが更新されなかったことが原因ということが予想できるのですが、なぜそうなるのか見ていきましょう。

f:id:masayoshi:20161219175714j:plain 図3-1: ARP Request時にMACアドレステーブルにエントリを追加

図2にL2スイッチβを追加したのが図3-1です。先程は省略しましたが、一番最初のホストAからの通信はLVS/TUNのMACアドレスを調べるためにARP Requestから通信が始まります。このとき宛先MACアドレスはFF:FF:FF:FF:FF:FFでブロードキャストを使います。

ブロードキャストなので当然L2スイッチβにもARPパケットが届きます。このとき送信元MACアドレス(ホストAのMACアドレス)と受信ポートからMACアドレステーブルが作成されます。これで最低でも5分程度はホストA宛のパケットはフラッディングではなく、対象のポートのみに送られます(図3-2)。

f:id:masayoshi:20161219104041j:plain 図3-2: MACアドレステーブルにエントリがある時の挙動

図3-2の状態ですが、重要なのはホストAからの送信パケット(①)です。ホストAはVIP宛のパケットを送ります。VIPのMACアドレスLVSMACアドレスなのでL2スイッチαはMACアドレステーブルに従ってLVSのつながっているポートにのみパケットを送ります。LVSはサーバBにパケットを送りますが、このパケットの送信元MACアドレスLVSMACアドレスであり、ホストAのMACアドレスでありません(当然ですが)。

つまり最初のARPによるブロードキャスト以外はL2スイッチβにホストAからのパケットは来ないことになります。サーバBからホストA宛のパケットはMACアドレステーブルに従い、L2スイッチαにのみ送られ、その後、ホストAに届きます。ただしそのACKパケットなどもLVSに行ってから送られるのでL2スイッチβはホストAのMACアドレスをブロードキャスト以外で知ることはできません。

なので、そのままエージングタイムが過ぎてしまうと、MACアドレステーブルからエントリが消えてフラッディングになってしまいます(図3-3)。 LVS/TUNのような行きと帰りのパケットが違うルートを通るような非対称ルーティングな通信を行うときには、こういうことが発生しやすいので特に注意する必要があります。

f:id:masayoshi:20161219112355j:plain 図3-3: MACアドレステーブルからエントリが消えた時の挙動

さて、原因っぽいのがわかったのはいいのですが、MACアドレステーブルが消えるまでにホストAがブロードキャストしてくれれば再学習ができるのでなんの問題もありません。サーバ側でそもそもARPテーブルのキャッシュがきれて更新するためにARP Requestが飛ぶので問題ないような気がします。

しかし、LinuxではARPテーブルがTCP通信で使用され続けていると、ARPテーブルのキャッシュは基本的に切れないという挙動になっているようなので、ある程度の頻度でTCP通信が定期的に発生しているとARP Requestが飛ばず、MACアドレステーブルが更新できないという状況になります。

LinuxARPの挙動

カーネルを読み込みが甘く、自分でも追いきれていない部分もありますので、下記内容には誤り、情報が古いなどあるかも知れません。

ARPテーブルのキャッシュに関する状態遷移の簡易図が図4です。

f:id:masayoshi:20161219112417j:plain 図4: ARPの状態遷移の簡易図

実際にはNUD_INCOMPLETE,NUD_FAILEDなど、他にも状態がありますが簡略化しています。ARP Requestが成功するとエントリが登録されNUD_REACHABLEになります。この状態では通信の際に普通にARPテーブルから対象のMACアドレスを得ることが出来ます。一定時間経つとNUD_STALEになります。NUD_STALEのエントリは参照されるとNUD_DELAYに遷移します。NUD_DELAYは一定時間内にTCP通信の場合は対象のMACアドレスからのACKが返ってきた場合ARP Requestは送らずNUD_REACHABLEに遷移します。つまり、ACKが返ってきたということはIPアドレスMACアドレスのひも付けは正しく、再解決する必要がないとカーネルが判断します。 なので、TCPで通信し続けている限りその対象のIPアドレスに対してARP Requestを投げることはしないという動作になります。

ちなみにUDPなどではsendmsgにMSG_CONFIRMフラグをつけることによって対象がまだ生きていることを通知してNUD_REACHABLEにすることが出来るらしいです。

NUD_PROBEに遷移するとNUD_DELAYと同じくTCP ACKを受け取るか、ARP Requestをユニキャストで送り、成功した場合はNUD_REACHABLEに遷移します。 tcpdumpで覗いているとユニキャストのARP Requestが来ることがありますが、そのパケットはNUD_PROBE状態から送られています。

そのユニキャストでの解決に失敗し、送信キューがある場合はARP Requestをブロードキャストで実行します。送信キューがない場合や応答が一定時間内に返ってこない場合はARPテーブルからエントリを削除します。 疑問点としてはNUD_PROBEが行うユニキャストARPは送信キューに入ってなくても行うかどうかですね。送信キューがなくても解決する場合は応答ある限りARPテーブルからエントリは消えないし、ARP Requestのブロードキャストが発生しないことになりそうです。 なお、NUD_REACHABLEなどの情報はip neighbor listなどで確認が可能です

▶ ip -s neighbor list
10.13.0.254 dev wlp4s0 lladdr 2c:6b:f5:3a:48:35 ref 1 used 78630/0/224 probes 4 REACHABLE

対策

対策としては送信元ホスト(ホストAなど)がブロードキャストをしてくれればよいということになります。

簡単なのは送信元ホストでarpingなどをcronでまわしたりするのが良いかなと思います。

はてなでは入れ替えの激しい送信元ホストやリアルサーバに仕込むのではなく、LVSから/proc/net/ip_vs_connをパースして送信元IPアドレスを偽装したICMP echo Requestパケットを送信元ホストに送信し、ICMP echo Replyを行う際にARP Requestを誘発させてブロードキャストを行う方法をとっています。この方法ではLVSにcronを仕込むだけで良いので、サーバの引っ越しや設定変更で書きなおし等があまり発生しません。(LVSを構築し直すときは別ですが頻繁には行わないです。)

まとめ

実際におきた事例の紹介とL2スイッチの基本的な挙動、LinuxARPの挙動について紹介しました。

今回の事例ではサーバからみるといきなりブロードキャストし始めたようにみえるので、L2スイッチの挙動を知らないとサーバ側の調査ばかりしてなかなか答えにたどり着かないみたいなことになりがちです。 ネットワークの問題かな?と思ったら丁寧にパケットの気持ちになってどうやって書き換えられていくのか、書き換える情報はどこから得ているのか、どこに送られていくのかを追ってみるのが大切だと改めて実感させられる事例でした。

また、Linuxではブロードキャストしないようにかなり気を使って実装されているなぁという印象を受けました。 なんかARPテーブルに登録しておいて時間がたったらもう1回解決するんでしょ?ぐらいにしか考えていなかったので勉強になりました。

このエントリで少しでもネットワークに興味を持ってくれる方が増えてくれると嬉しいです。

明日のアドベントカレンダーid:hakobe932さんです!

株式会社はてなに入社しました

ブログをはじめましたid:masayoshiです。 

2016年4月1日に株式会社はてなに新卒枠で入社しました。職種はWebオペレーションエンジニア職です。なんかサーバとかネットワークをいじる職種です。詳しく知りたい人は以下の本が全体的な雰囲気をつかめてオススメです。 

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)

 

 

同期が優秀な人しか居なくて、インターン生だったり、既にアルバイトとして働いている方だったり、アメリカから半年前入社に成功している猫だったりで、「このままだと殺される」と感じたので、3月の下旬からアルバイトとして入って、ほとんど研修ですが、実質2週間ぐらい仕事をしていた感じです。

何か長くなったので、上の方に目標でも書いておきます。

  • 試用期間中にクビにならないこと
  • Webサービスを支えられる技術を身につけること
  • 会社を支えられるようになること

"会社を支えられるようになる"というのは、別に技術とかそういう話だけではなくて、社内で困っている人の相談に乗るとか、そういうのも含めてです。ココらへん目標とかは別のエントリにしっかり書きたいと思います。

 

以下は見たい人用

  • はてなの印象
  • 2週間でやったこと
  • 新卒力

はてなの印象

まぁ一日目で雰囲気ってなんだという気もしますが、↑に書いたようにアルバイト期間も含めての印象ってことで。

一言でいうと「技術を大切にしている会社だな」と思いました。

会話してて、アプリケーションエンジニアやWebオペレーションエンジニアは当然として、その他の方たちもエンジニアっぽく感じることが多いです。

「何か人には簡単に真似できないような技術ですごい事するすごい人」という雑な上に個人的な”エンジニア”の定義だと、一般的なエンジニアだけではなく、営業、人事、総務なども含んで"エンジニア"かなっと思った。

そういう意味で全員”エンジニア”な会社だなーというのが現在の印象です。

2週間でやったこと

  • 研修
  • 技術的なアウトプットの準備
  • 会社用PCのセットアップ
研修

バイト期間中に研修をやっていました(今は社員用の研修をやってます)。はてな研修用教科書に沿ってPerlとかJavaScriptとか頑張るやつです。(同期は既にインターンやアルバイト期間に終了している..)

github.com

私は研究室でアルティメット雑なC言語Pythonのクソコードを時々書いてただけなので、Webアプリケーションや他人に見られるコードの書き方を全く知らない状態からのスタートです。

とにかくすごいと感じたのがレビューで、アプリケーションエンジニアなのかWebオペレーションエンジニアなのかミニ四駆レーサーなのかわからない社員の方が、自分の仕事をこなしつつ、私のクソコードを丁寧にレビューし、初心者丸出しの質問に的確に答えつつ、定時か定時1時間以内に仕事を終わらせ、ミニ四駆やアニメ映画を見にいっていることです。

あんな感じに他人のコードをさっと見てレビューしたり、サンプルコードとかパッと提示できるあたりにプロを感じた。

Webオペレーションエンジニアはアプリケーションエンジニアに比べたらコーディング量は多くないものの、コーディングはするしミドルウェアソースコードを読むことも多いので、プログラミング修行の必要性を感じられてよい。

その社員の影響ということは全く無いですが、私もミニ四駆を買いました!

技術的なアウトプットの準備

はてなはエンジニアが技術的なアウトプットすることを非常に推していて、私もアウトプットしたいなと思ってたので、簡単な内容からちょくちょくアウトプットしていこうかと思います。

インフラ系エンジニアの人はご存知の方も多いかもしれませんが、はてなのWebオペレーションエンジニアにはアウトプット魔神のような社員がいて、毎週「いつ記事を書くのか。今でしょ」的なプッシュをされるので早めにやっていきたいです。

会社用PCのセットアップ

はてなでは、業務で使用するPCを個人で選択できます。大体MacBookが多いです。

私は、家のノートPCはMacBookとX220を使用していますが、先日大学の色々が終わったので、OSXを"El Capitan"にアップデートしたら色々疲弊したのと、有線LANが変換コネクタなど無しで使いたかったので、X260にしました。

最初は業務用なので「DebianとかUbuntuでいいかな?」と思っていました。

安定していたほうがいいし、情報量も多いほうがいいと考えたからです。

しかし、CPUが新しいので、Linuxカーネルがほぼ最新(4.4以降)でないと動かない関係で

  • DebianとかUbuntuにしても、結局unstableな感じになってしまう
  • 隣の社員はFreeBSDで、CTOはGentooを推してくる

といった感じなので、脳みそがユルユルになり、ArchLinuxをいれることにしました。

ArchLinuxを入れた理由は、普段使っているので慣れていることと、最新を追っている割には情報量が多いことです。

例えばUbuntuは情報量が多いですが、あくまでstableの情報が多く、testやunstableの情報量はさほど多くないです。

それに比べ、ArchLinuxはローリングリリースなので、ArchLinuxの情報は全て最新に近い情報が手に入ります。(X260の情報も既にあります。Lenovo Thinkpad X260 - ArchWiki)

業務用のPCなので、ディスクの暗号化とかをする必要があり、dm-cryptをCUIで操作しディスク暗号化をしたのは始めてだったので勉強になりました。(そのうち記事にしようかと思います)

新卒力について

ここからは意識が低いお話

上で書いたように、同期はアルバイトやインターン生だったので、私が一番フレッシュ感あふれて新卒力で殴っていけるのではないか?と考え、フレッシュ感を推してキャラ付けし、新しい会社に馴染んでいこうという計画を3月31日に思いついた。

結果としては、「全然フレッシュ感が無い」と言われてしまった。

研修でも振り返りは大事ということだったので、何がいけなかったのか振り返ってみる。

  • 始業3分前に出社
  • 「2016年には一般家庭でもvmstatを叩いている」
  • 「フレッシュ感という状態を持たない新卒は、スケールアウトが容易」
  • 自己紹介スライドを作らないうえに、発表内容もその場で考える

初めに3分前出社。4月1日の私は「学生気分が抜けきれてなくて新卒感ある」という主張をしていたが、どう考えても緊張しながら絶対に遅刻しないように始業30分前にはいる方が新卒力が高い。だが、私が出社した1分後に出社してきた1年前からアルバイトしている入社同期氏のほうが新卒っぽいと言われていたので、出社時刻が決定的新卒力の差ではないことがわかった。

2つ目の発言はランチのときの発言。今見直すと何いってんのこいつ?感しかない。確か風が吹けば桶屋が儲かる理論で「一般家庭にもNASがある時代」「NASがあるとvmstatを叩く」みたいな流れで誤った推論をしまくった結果出た発言で、MySQLに詳しい社員とFreeBSD使いの社員がなにいってんのこいつ?みたいな顔してた。でも詳細はよくわからないが、隣の席で「人間ドックではなく人間Docker」みたいなよくわからなそうな話も出てたし許容範囲な発言の気がする。少なくても新卒力を高める感じはしない。

3つ目は新入社員歓迎会のときの2分前出社同期氏との会話で出た発言。全くコンテキストを覚えてないから分からないが、私は素面で同期は酒が入っていたので、よった勢いという言い訳もできない。

4つ目は3分ぐらいの自己紹介があるので準備しといてね的なことを言われ、中途採用の方や他の同期がスライドを用意している中、全く用意していなかったので、その場で考えて発表してしまった。2分前出社同期氏もスライドを用意していなかったが、発表時にスマホを片手にあたかも原稿があるかのように見せて発表していた。

全体として、落ち着きがありすぎる(緊張感がない)ということが新卒力、フレッシュ感のなさに繋がっていそうというご意見を頂いた。

これは"はてなという会社の文化"に自然に入れたからということな気がしないでもないので、結果的に良かったということにしておこう。

 

長々と書きましたが、これから色々頑張ります!