人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

FastContainerアーキテクチャ構想

追記:この記事へのコメントとして、この記事以上に内容の趣旨を端的かつ完璧に表しているコメントがありましたので、まずはそれを紹介しつつ、引き続き呼んで頂けると幸いです。

FaaS的だけど「アプリ側の構成も基盤側に合わせて変えるべき」路線なFaaSに対し「アプリは従来のままでもちゃんと動くよう基盤側が頑張るべき」という基盤側の矜持を感じる by takahashim

FastContainerアーキテクチャ構想 - 人間とウェブの未来

FaaS的だけど「アプリ側の構成も基盤側に合わせて変えるべき」路線なFaaSに対し「アプリは従来のままでもちゃんと動くよう基盤側が頑張るべき」という基盤側の矜持を感じる

2016/11/13 18:25
b.hatena.ne.jp

素晴らしいまとめの一言です。それがまさに本構想に至った意図でございます。僕もこんな趣旨をかけるようになりたいです。上記の的確なコメントのような認識で読んで頂けるとより内容がわかりやすいかもしれません。ということで、以下本文に続きます。

Webサーバの動的コンテンツを処理する方式としてFastCGIがあります。FastCGIの動きをざっくりとまとめると、

  1. Webサーバがリクエストうける
  2. 動的コンテンツ実行時にFastCGIデーモンが動いているか確認
  3. 動いていればそのまま処理を依頼してコンテンツ実行、動いていなければデーモンを起動させて実行
  4. 実行後はFastCGIデーモンは暫く起動しっぱなしで連続的なリクエストを処理する
  5. 一定時間処理がこなければデーモンが落ちる

というような動きをします。これによって、最初の一回はデーモンプロセス起動に時間がかかっても、起動後の連続的なアクセスはprefork的に処理できるため、都度プロセスの生成・破棄が生じるCGI方式よりも性能が良くなります。また、一定時間たつと、プロセスが停止するため、使っていないFastCGIプロセスがたまることもなくエコです。

その考え方をWebアプリを提供するコンテナが沢山収容されているシステムの設計に適用してみましょう。それを「FastContainerアーキテクチャ」と呼ぶことにします。共有ストレージにマウントした複数のホストOS上に、Webアプリを提供するコンテナが沢山起動しているようなシステムを、自分達で作り、運用・管理しようとしている側の設計の工夫であることを想定しています。その場合に、FastCGIならぬFastContainerのアーキテクチャを適用した場合に、どういう設計になるか、具体的にはどういう実装になりそうか、さらに、システム運用面で得られるメリットを考察してみます。

FastContainerアーキテクチャの動き

前提として、インターネット上からシステムに収容されているWebアプリコンテナのドメインにアクセスがあると、ちゃんと対応したWebアプリからレスポンスを正常に返せているように見える事が条件です。そのようなWebアプリが高集積に収容されているシステムを想定しています。

その場合、コンテナを使っているので、サービス的に見たときにGAE(Google App Engine)ぽい、というかPaaSやIaaSのような機能を提供できるようにすることを考えると、ある種似ているのは当然として、運用・マシンコストを最適化するために、システム側でどういうアーキテクチャをとるかは別なので、今回の記事はそこの提案と一般化になります。

PaaSやIaaSを作る際にGAEのような動きができるのはサービスとして理想的でありますが、それを実現するアーキテクチャとシステム運用が複雑だと問題なので、それをどうすれば我々でも解決し作れるかと考えた時の一つのアプローチをFastContainerアーキテクチャとしてまとめてみたいと思います。

IaaSやPaaSのシステムを設計する際に、いかに可用性と性能のバランスをとりつつ、運用を楽にするかが肝になります。そのために、FastContainerアーキテクチャを、Webアプリコンテナを収容するシステムに適用する場合の基本的な仕様は、

  • Webアプリのプロセス含めコンテナで高速に起動できるコンテナを準備する
    • とはいえよくあるapacheやnginxや各種アプリサーバで充分
    • JVMのような最初の起動が特に遅いミドルウェアは合わない
  • コンテナに関する情報はCMDB(構成管理DB)などに保存しておく
    • CMDBにはコンテナを起動するための各種パラメータや収容ホストのアドレス、Listenポート等を保存
  • 複数コンテナにリクエストを振り分けるプロキシをフロントに用意しておく
  • 既にHTTPリクエスト先のコンテナが起動していれば、そのままコンテナにプロキシする
  • 起動していなければ、リクエストを契機にreactiveにコンテナを起動してからプロキシする
    • 最初の1リクエストだけ起動するので少し遅くなる
  • 連続的なリクエストはコンテナを起動しっぱなしでさばきつつ、一定時間リクエストがない、あるいは、一定の時間が経てばコンテナを落とす
  • 外からアクセスしている分にはちゃんとWebアプリを提供できている(ように見える)
    • 原理的にはFastCGIのように動かせるWebアプリは動くはず
    • FastCGIのようにアプリを実行して良いなら、Webアプリコンテナもリクエストがないと時は死んでいて良いはず

のようになります。実際にこれを、より具体的に連携イメージ図に落とし込むと、例えば以下のようになるでしょう。

f:id:matsumoto_r:20161112215249p:plain

この場合、インターネットからWebアプリを見に来たクライアントからリクエストを受けたフロントのWebプロキシが、CMDBからリクエスト先のコンテナの収容ホストとListenポートを取得した上で、そこから収容ホストで待ち構えているLocal Web Proxyにアクセスします。リクエストを受けたLocal Web Proxyは、ホスト上で対象コンテナのポートやUnix Domain SocketがListenしているかを極めて高速な処理によって確認を行い、Listen済みであればコンテナが起動しているとしてそのままプロキシ、Listenしていない場合は、CMDBからコンテナ起動に関するパラメータを受け取って、コンテナを起動してから、該当のコンテナにプロキシを行ってレスポンスを返す、という処理になるでしょう。これによって、内部的にはコンテナがなかったりする場合もあるのですが、外から見ると、ちゃんと即座に起動してWebアプリのレスポンスを返せているように見えます。

そして、同様に、連続的にリクエストがつづけばそのまましばらく処理を行い、リクエストが長い間なければ、コンテナを停止してプロセスからいなくなるようにします。

システム運用側のメリット

このような、FastContainerのようなアーキテクチャをとるメリットを考えると、以下のようになるでしょう。

  • リクエストのない不要なコンテナは停止するためホストOSのメモリは節約される
  • ホスト間の移行時もCMDBのデータベース上の収容ホスト情報を変えるだけで、リクエストを契機に別ホストにreactiveにコンテナが作られる
  • 移行前のコンテナはアクセスが0になるので、いずれ停止するので放っておけば良い
  • CMDBに複数収容ホスト情報が追加されれば勝手にホストOSにリクエストがきてreactiveにコンテナが作られ負荷分散される
  • CMDB上の収容ホスト情報を、サーバの負荷に合わせて変更するような仕組みを作れば、動的にホストをコロコロ最適な状態を目指して移動する
  • コンテナなので最初の起動もそれほど遅くならない(はず)
  • コンテナが自然と生成・破棄を繰り返し、動的平衡性が保たれたなめらかなシステムに成りうる

つまり、ストレージとCMDBのみが状態を持ち、その状態によってコンテナがreactiveでstatelessにホストを自由に駆け巡ることができるようになります。また、ホストを追加するのも容易ですし、メンテナンス時に別ホストにコンテナの向け先を変えるだけで、動的にホストを超えて入れ替わっていく、起動しっぱなしのものであっても実質リクエストがきていなければ勝手に落ちる上にリクエストが来ない限りは起動しないので、そのあたりの様々な設計も簡単になるでしょう。

コンテナの作成を制御するためにAPIやジョブなどを用意せずとも、CMDBの値のみを変更するだけでコンテナの生成・破棄・複製・移動・負荷分散が実現でき、かつ、リクエストのないコンテナは停止するのでホストOSのリソースも最適化される上に、システム設計も非常にシンプルになるように思います。

もちろん状況に応じて、DBのキャッシュなどを用意する必要はあるでしょう。

実装例

上記のような例では、例えば、clientからのリクエストを待ち受けるWeb Proxyや、ホストOS上に存在するLocal Web Proxyにngx_mrubyのようなRubyでプログラマブルにリクエストデータからプロキシをコントロールできるソフトウェアを使いつつ、ngx_mrubyからのコンテナの扱いに、これまたRubyでプログラマブルにコンテナを制御できるコンテナエンジンのhaconiwaを使うことによって、比較的容易に実装できるでしょう。

clientのリクエストを最初に受けたWeb Proxy(ngx_mruby)が、CMDBを経由してコンテナの収容ホストアドレス情報とListenポート情報を引き出し、X-Container-Port: 8080のようなヘッダをHTTPリクエストに追加した上でホストOSまで到達した後に、ホストOS上で動作するLocal Web Proxyのngx_mrubyがコンテナのListen状態を確認します。Listenしていれば、既にコンテナが起動しているとみなして、そのままコンテナにプロキシしてレスポンスを返します。一方で、Listenしていない、つまり、コンテナが起動していなければ、CMDBにコンテナ構成情報を再度聞きに行ってからhaconiwa用のRubyのDSLを作り、そのDSLをhaconiwaに渡して実行すれば、haconiwaはそのDSLに従ってコンテナを起動させる(コンテナ領域なければ作り、あれば領域を起動)ことができます。その起動も最初の一回のみで、しばらくは連続的にレスポンスを返しますし、最初の起動もコンテナであるため、実運用上は問題無いレベルとして扱えるのではないかと思います。コンテナを落とす時間をどれぐらいにするのか、といった所にも依存してきますが。

こういったソフトウェアを使えば、比較的簡単にコードでこのような、所謂FastContainerの動きを実現できそうです。haconiwaについては、以下の記事で検証していますので、是非御覧ください。

hb.matsumoto-r.jp

以下の記事ではポート番号を元にかなり柔軟に動的設定をかける例を示しました。

hb.matsumoto-r.jp

まとめ

このように、ストレージでデータを共有する複数ホスト上に大量にWebコンテナが動いている状況で、いかにホストOSのリソースを最適化しながらも、状態を持つ・持たないでコンポーネントを分離し、コンテナがホスト間を自由、かつ、即座に行き来できるようなアーキテクチャを考えた時に、このFastContainer構想を応用すれば非常にシンプルな設計で実現できる気がしております。

FastCGIだけでなく、xinetdといった古くからあるUnixのソフトウェアを色々見ていると、昨今の技術背景においてもうまく応用すれば面白いアーキテクチャが生まれそうだと思わせられるような技術が沢山あり、再度、昔からある汎用的な技術に目を向けるのは重要であると実感しておるところです。

ということで、最近考えていたFastContainerアーキテクチャ構想についてざっとまとめてみました。