物理サーバのセットアップをon-the-fly ISO patchingで自動化した話

メルカリSREの@kazです。 今日は、メルカリの所有する物理サーバのセットアップと設定配布の自動化をどのように実現したかをお話します。

メルカリのデータセンタ

本題に入る前に、メルカリが現在構築している自社データセンタ(東京DC)についてご紹介します。

メルカリでは現在、基幹システムのMicroservice化を進めています。各Microservicesの開発においては、そのサービスの特性などを鑑みて、チームの裁量で柔軟にインフラを選択することが可能です。その結果として、メルカリはさくらインターネット、GCP、AWSなど複数のクラウドにまたがって運用される状況となりました。

こうした中、SREチームが問題視したのは「各クラウドデータセンタ間の物理的距離」です。メルカリでは、コストダウン・低災害リスクの観点から、主にさくらインターネットの石狩リージョンを利用しています。一方で、GCPやAWSにおいては主に東京リージョンを利用しており、そのリージョン間の物理的距離はおよそ1,000kmにもなります。ネットワーク的な距離にすると、RTTでおよそ17msです。これは、仮にHTTPSで通信するならば、SSLハンドシェイクを終えるまでにおよそ250msもかかる距離です。

この「距離」は、ユーザ体験に直接的な影響を与えるものであり、到底看過できるものではありません。メルカリSREでは、この課題をソフトウェアで解決するべく、choconを開発・運用しています。choconは、外部サーバとのコネクションを集約・維持することによってハンドシェイクにかかる時間を減らすシンプルなプロキシサーバです。このchoconは東京DCとさくらインターネット石狩リージョンの双方で運用され、石狩・東京間の通信をプロキシすることで遅延を軽減しています。東京DCは、このように石狩と東京をつなぐ拠点としての性格を持っています。

また、こうした「距離」は信頼性にも大きな影響を与えます。東京DCが運用開始される以前、石狩・東京間の通信はもともとインターネットを経由していました。これによってさくらインターネットの上流ISP障害の影響を受けて通信が不安定になるなどの事例があったほか、2017年8月のGoogleによるBGP誤経路広告トラブルでは、この影響を大きく受け1時間半ものあいだサービスを停止せざるを得ない状況に陥りました。

そこで、東京DCと各クラウド事業者のデータセンタ間でインターネットを経由しない接続を行い、東京DCを各クラウドの「結節点」として機能させることによって、インターネットに起因する障害を回避し、各Microservicesに一貫性のある接続を提供することができるようになりました。

さらに、東京DCでSRE Managedなアプリケーションサーバ、データベースサーバを提供し、各Microservicesチームのインフラに関する選択肢の1つとする構想もあります。

物理サーバのセットアップ

先に述べたように、メルカリのMicroservice化が進行するにつれて東京DCの重要性はますます高くなり、東京DCで新たに運用されるコンポーネントが増えてきました。こうした背景で、新規に物理サーバを購入してセットアップ(OSインストール)を行ったり、既存のサーバを別用途に転用するために再セットアップする事例も増えてきています。

東京DCでのサーバセットアップはもともと、すべてSREが手作業で行っていました。検証段階など、セットアップを行うこと自体がまれな段階においてはこれでも十分でしたが、本格的に運用するにあたっては、このセットアップ作業が大きなToilとなりつつありました。具体的には、以下のような課題が挙げられます。

  • 搭載されたハードウェアに適した設定を記述したkickstartファイルをマシン毎に用意する必要があるが、導入時期などの要因によってさまざまなハードウェアが導入されているため、セットアップ対象のサーバのハードウェア構成を毎回調べ、kickstartファイルを書く必要がある。
  • マシンに対応したkickstartファイルは、対象のマシンへ手作業で配布する必要がある。セットアップ対象のマシンが多くなると、配布対象を誤る可能性が大きくなる。
  • kickstartファイルの配布は、手動でISOイメージに埋め込み、これを対象のマシンにマウントすることで行っていたが、この作業は工数が多く複雑であり、時間がかかる。

今回、これらのToilを取り除くべく、ワンアクションでセットアップを開始でき、かつマシンに適した設定を自動で配布するための仕組みを作りました。

全体構成

はじめに、今回作った仕組みの全体像を以下に示します。図では、管理サーバとファイルサーバが別になっていますが、実際には1台の同じサーバ上で、1つのプロセスが管理サーバ機能とファイルサーバ機能を提供しています。

先に述べた課題は、以下の通りに解決しています。それぞれ、これ以降で詳しく説明していきます。

  • kickstartファイルの用意
    • → netboxでマシンの情報を一元管理し、kickstartファイルを自動生成
  • 手作業で行っていた設定の配布
    • → ネットワークブートとRedfish APIを活用して自動化
  • kickstartファイルのイメージへの埋め込み
    • → on-the-fly ISO patchingで自動化

kickstartファイルの自動生成

手放しでセットアップを行うため、RHEL系Linuxではしばしばkickstartファイルが用いられます。東京DCのようにさまざまなハードウェアが導入されている環境においては、セットアップされようとしているマシンがどういったハードウェア構成なのかを判断し、適切なkickstartファイルを記述する必要があります。

今回、メルカリでは新たにnetboxを導入し、ここですべてのマシンの情報を管理することにしました。netboxはもともとDigitalOceanのインフラエンジニアによって開発されたOSSで、データセンタ内に存在するあらゆるリソースの情報をWeb GUIやAPIから閲覧・編集できるようになっています。

このnetboxにはContext Dataという機能があります。これはサーバに紐付けられている様々なコンテクスト(ロールであったり、ネットワーク上の位置であったり、etc……)から、そのサーバのための設定を生成することができる機能です。メルカリでは、あらゆるコンテクストについて予め設定を記述しておき、各マシンにそのコンテクストを紐つけておくことで、様々な設定情報を一度に取得できるようにしています。このデータをもとに、マシン毎に異なるkickstartファイルを生成しています。

netboxが導入される以前、マシンのロールやハードウェア構成、ネットワークの情報などは、すべてGoogle Spreadsheetを用いて手作業で管理されていました。しかし、これでは様々な自動化における障壁となるため、今回これら全てをnetboxへ移行しました。最初のデータ入力は膨大な量で、さすがに骨の折れる作業でしたが、ここはマンパワーでなんとかしてしまいました。以降は、新しいハードウェアが導入されるたびに都度入力するだけで済むので、さほど課題になる部分ではないと思っています。

また、Custom Scriptsという機能もオススメです。netboxは各操作がわりとプリミティブなのですが、このCustom Scriptを使うと、たとえば退役したマシンからIPアドレスを自動で解放させるような作業が一括で行えるようになります。このような複雑な操作を正しく行うためには、APIを外部から叩くような方法もあるかとは思いますが、netbox単体でこれが実現できるのが強みです。東京DCでの初回のデータ入力時にも、このCustom Scriptsを活用しました。

ネットワークブート

次に、手動でのISOマウントから脱却し、ネットワーク越しにマシンをブートする何らかの仕組みを導入し、セットアッププロセス全体の自動化を目指します。このネットワークブートにはいくつか選択肢があります。以下の3つについて検討し、最終的にはIPMIの仮想ディスクマウントを利用することにしました。

PXEブート

おそらくネットワークブートというと、最も採用されているケースが多いのがこのPXEブートでしょう。PXEブートでは、接続してきたサーバによって別のイメージを配布する仕組みが存在するため、あらかじめ設定を埋め込んだイメージをいくつか用意しておけば、マシン毎に設定を自動で配布することが可能です。

PXEブートでは、ブートローダを配布するTFTPサーバ、イメージを配布するファイルサーバ(HTTP、FTP、NFSなど)、さらにそれらサーバの情報を配布するためのDHCPサーバが必要になります。このように、必要となるコンポーネントが比較的多く、運用コストが少々心配になります。

東京DCでは、基本的にDHCPサーバを使わないような構成になっています。そのため、初回のセットアップのためだけにDHCPサーバを導入し、ネットワーク構成を変更する必要があるという点がネックでした。

HTTPブート

最近ではUEFIの機能として、HTTPを使ってネットワークブートする機能が提供されています。

このHTTPブートでは、PXEブートと比較すると、DHCPサーバとTFTPサーバが不要となるため、比較的シンプルな構成でネットワークブートを利用できます。ただし、シンプルであるが故に、マシンによって別の設定を配布するためには別の仕組みを用意して工夫する必要があります。幸いにも、kickstartファイルはネットワーク越しに取得してセットアップを開始することにも対応しているため、ここで接続元によって異なるファイルを配布することができます。ここは相性が良いと言えるでしょう。

また、これはDHCPサーバを捨てたために仕方のない事ですが、PXEブートのようにマシンを起動するだけでセットアップが始まるような形ではありません。予めUEFIがネットワークに接続できるように設定しておく必要があるほか、イメージのURLも指定してかねばなりません。ここを完全に自動化するには、また一工夫が必要になります。

まだ新しい機能であるため採用された例も少なく、ノウハウが少ない点が懸念ではあります。

これは非常に有力な選択肢ではありましたが、あらかじめUEFIにネットワーク設定が必要な点がネックであったり、検証段階で満足に動作させることができなかったため見送りとしました。

IPMIによる仮想ディスクマウント

「手作業でISOイメージをマウントしていた」といっても、さすがに物理メディアを挿入していたわけではありません。メルカリの所有するサーバのIPMIには、ネットワーク越しにISOイメージを取得し、仮想ディスクとしてマウントする機能があり、これを利用していました。

これはそのままHTTPブートに近いネットワークブートとして利用できます。また、IPMIはマシン自体(またはUEFI)のネットワーク設定とは別にローカルネットワーク接続されており、東京DCではこの設定はマシンの設置時に完了していることになっています。そのため、HTTPブートでのセットアップ時に必要であったUEFIのネットワーク設定が不要で、イメージのURLだけを与えれば良いです。

もう一点、マシンによって別の設定を配布する仕組みが必要である点はHTTPブートと同様です。また、残念ながらkickstartファイルをネットワーク越しに配布することは難しい状況でした。なぜならIPMIのネットワーク設定はマシン自体(UEFI)のネットワーク設定とは別であり、そもそもこれは配布したいkickstartファイルの中に含まれるものですので、この段階では利用できないためです。

一つ課題が残りましたが、ここはソフトウェアで解決するとして、最終的にはこの方式を利用することにしました。

Redfishによるセットアップの開始

HTTPブート、IPMIによる仮想ディスクマウントに共通するデメリットとして挙げていた「PXEブートのように電源を入れたら勝手にセットアップが始まらない」という点もなんとかしたいです。

メルカリの物理サーバでは、搭載されているIPMIがRedfish APIに対応していたため、これを使って解決することにしました。Redfishは、RESTでサーバの設定変更から電源操作までできてしまう便利なAPIです。物理サーバ周りの自動化の強い味方になるでしょう。

管理用サーバから、Redfish APIを用いてマウントするイメージのURLを設定し、電源ONまで行うツールを作成し、ほぼ一回のアクションでセットアップが完結するようにしました。

マシンへの設定配布

生成したkickstartファイルは、どのようにして対応するマシンに配布すればよいでしょうか。

先に述べたように、PXEブートには接続元をMACアドレスやIPアドレスで判定して、異なるファイルを配布する仕組みがあるのですが、HTTPブートやIPMIによる仮想ディスクマウントでは、シンプルが故にそんな気の利いた機能は無いのでした。ここは頑張る部分です。

とは言ってもPXEブートと同じことをするだけ、欲しい機能を作ってしまうだけです。つまり、kickstartファイルに要求が来た際に、接続元によって違うファイルを返すようなHTTPサーバ実装をすれば良いです。東京DCでは、IPMIの持つネットワークインタフェースのIPアドレスをマシン毎に固有にしています。そのため、このIPアドレスを識別子として、netboxからハードウェア情報を取得し、kickstartファイルをその場で作成してHTTPで配信すれば良いでしょう。HTTPブートでは、これで設定の自動配布が完成となります。

しかし、今回採用したIPMIによる仮想ディスクマウントでは、kickstartファイルを単純にHTTPで配信するだけでは不十分です。なぜなら、あくまでもIPMIによる仮想ディスクマウントはHTTPでISOイメージそのものを取得してマウントする機能だからです。つまり、kickstartファイルは配信するISOイメージに埋め込まれていなければならないのです……!

on-the-fly ISO patching

ISOイメージにファイルを埋め込むには、一旦展開して、ファイルを追加して再度固め直す必要があります。これには時間がかかりますし、単純に面倒です。また、OSインストールを行うイメージはそこそこ大きいので、各マシン毎にISOイメージを作っていると、大量のストレージ領域も必要になってしまい、うれしくありません。

そこで、今回はベースとなるISOイメージのISO9660ファイルシステムを直接書き換えることで、ISOイメージにファイルを追加することを試みました。正直、これはかなり奇抜なアプローチだろうと思います。on-the-fly ISO patchingと名付けました。

ISOイメージの構造はこうなっていて……という説明を始めると日付が変わっても帰れなくなりそうですので、詳しいことは省略します。ISOイメージというのはISO9660というファイルシステムで、そのルールに従ってファイルが格納されているので、これを力技💪で書き換えていくだけです。注意が必要なのは、最近のISOイメージは素のISO9660であることはほぼなく、大抵はRockRidgeと呼ばれる拡張が入っています。これを考慮して書き換えを行わないと不正なISOイメージが出来上がってしまいます。一番たいへんなのは、間違いなくISO9660や、特にRockRidgeの仕様書を探すことでしょう。ISO9660/RockRidgeのイメージ作成機能があるOSSのソースコード(たとえばcdrkitのgenisoimageなど)を読むのが一番の近道かもしれません。

これらはGoで実装しています。工夫点として実際のファイルを書き換えずに、オンメモリでファイルの一部分だけを書き換えたように見せかけて読み出せるReadWriterを実装しています。ちょうどOverlayFSのReadWriter版のようなイメージです。これは、実際にディスクに書かないことでpatchingのパフォーマンスの向上、また省ストレージ化に寄与しています。

これによって、高速で省ストレージなISOイメージ編集システムが完成しました。このon-the-fly ISO patching機能部分だけを切り出して公開したものがgithub.com/kaz/patchworkです。

ISOイメージを一旦展開してファイルを追加して再度固め直す場合と、on-the-fly ISO patchingでファイルを追加する場合の所要時間の比較をしてみました。検証に使ったコードでは、今回の要件に沿って「HTTPリクエストを受けてからISOイメージを作り始める」3つの方法を比較しています。

existing1は一番愚直なやり方で、リクエストを受けてからベースのISOイメージを展開、書き換え、再作成しています。existing2はもうちょっと頭を使ったやり方で、あらかじめベースイメージを展開するまではやっておいて、再作成時にもディスクに書かないように工夫しています。ただし、このHTTPサーバが同時に複数のサーバからイメージを要求することを考慮できていないため、本番で使うにはまだ課題が残されているでしょう。最後のproposedは、今回のon-the-fly ISO patchingによるファイル書き換えです。

以下、結果となります。いずれも、TTFB(リクエストを開始してから最初の1バイトを取得するまでの時間)を計測しています。

[kaz@default ~]$ curl localhost:8080/existing1 -s -o /dev/null -w '%{time_starttransfer}s\n'
39.808936s
[kaz@default ~]$ curl localhost:8080/existing2 -s -o /dev/null -w '%{time_starttransfer}s\n'
0.158605s
[kaz@default ~]$ curl localhost:8080/proposed -s -o /dev/null -w '%{time_starttransfer}s\n'
0.023341s

おわりに

これによって、管理サーバからコマンド1つで物理サーバのセットアップが完了するようになり、東京DCをより活用しやすくなりました。Future Workとして、たとえばこのシステムにWeb GUIを追加し、開発者がすぐにサーバをセットアップして使うことができるようなシステムとすれば、東京DCをプライベートベアメタルクラウドに発展させていける可能性もあるかと考えています。