エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

コンテナベースシステムのデザインパターンに関する論文紹介

エンジニアリンググループ AI・機械学習チームの笹川です。 今回は、コンテナベースシステムのデザインパターンに関する論文 Design patterns for container-based distributed systems について紹介します。

なお、この記事は、社内勉強会であるM3 tech talkで紹介した内容をまとめたものです。 M3 tech talkは、エムスリー赤坂オフィスで隔週で開催されている5-20分程度のLTを数件行う勉強会で、そのトークテーマの振れ幅は最近の数回でも筋トレ、型システム、3Dプリンタ、量子コンピュータなどこのブログの数段上で、個人的にも毎回楽しみにしています。 M3 tech talkは、外部からの参加・登壇も歓迎しています。興味のある方はぜひ以下からお問い合わせください。

jobs.m3.com

論文の概要

今回紹介する論文は、HotCloud '16 というUsenixカンファレンスの中でも特にクラウドコンピューティングに関する話題を扱ったワークショップで発表された論文です。 内容としては、近年、爆発的に利用が広がっているコンテナ技術を用いた分散環境のシステムの設計について、 OOP (object-oriented programming) の文脈でおなじみのデザインパターンの考え方を適用することで、 典型的なユースケースに対して設計の「型」を提供しようという提案がなされています。

個人的に、OOPの重要性は、開発者の間でfalkloreであったであろう頻出する実装パターンに名前をつけて、開発者間での共通認識を持てたことであると考えています。 例えば、開発者が "facade" という文字列を見た瞬間既存のコードを読む際の脳の準備ができて理解が早まったりすることは、皆さんにも経験があることと思います。 また、デザインパターンは、典型的な問題に対する解法であり、正しく用いることで、再利用性や、開発効率が向上することも重要なメリットです。 このようなことから、分散システムのデザインパターンを考えることは、ただでさえ御し難い分散システムの諸問題の一部について、 エレガントに解く方法を与えてくれることを期待できると思います。

以下では7つのデザインパターンをノード数と、ノードの中で動くコンテナ数で分類して紹介します。

1. Single-container management pattern

コンテナに詰め込んだアプリケーションをコンテナの外側からマネージするシステムに対し、 共通のインターフェイスを提供してやると色々便利やで!ということが述べられています。 コンテナからマネージメントシステムへ内部のメトリクスや、設定の状態を通知する"upward"インターフェイス、 反対に、マネージメントシステムがコンテナへ停止、実行などと司令を出す"downward"インターフェイスを備えてやることで、 複数のコンテナをコンポーネントとした分散システムの設計、開発、運用が簡単になるメリットがあります。

シングルノードのパターン

次に1つのノード (ここではコンテナが動くホストの意味) で動くデザインパターンを紹介します。

2. Sidecar pattern

f:id:hsasakawa:20190320142819p:plain:w300
sidecar pattern (論文より引用)

最初はもっとも有名なコンテナデザインパターンであるsidecar patternです。 このパターンでは、メインの機能を実装したコンテナに対して、サブのコンテナがその機能を拡張したり強化する役割を持つパターンです。 具体例として、メインとsidecarコンテナは同じボリュームをマウントし、メインコンテナはそのボリュームにログを吐き出し、 sidecarはそのログを適切な場所に流すというパターンがあります。

上記の例で、メインのコンテナがログを配信する部分までを担当することもできるのですが、本文中ではsidecarパターンを用いるメリットとしていくつか紹介されています。個人的には以下の2点が特に良い部分だなと感じました。

  1. 開発を分担することができる
  2. 障害がコンテナ内部に閉じる

1は、共通ライブラリ的に社内で整備するsidecarコンテナを用意することで、開発効率を向上させることなどが期待できます。 2に関しては、例えばログの配信機能にメモリリークがあった場合、sidecarパターンを利用していないとアプリごと落ちてしまうようなケースも考えられますが、 sidecarを用いることで障害を限定的な箇所に留めることができます。

3. Ambassador pattern

f:id:hsasakawa:20190320143142p:plain:w300
ambassador pattern (論文より引用)

次に紹介するのは、メインコンテナが外部と通信する際に、サブコンテナがそれをサポートするambassador patternです。 このパターンでは、メインコンテナはlocalhostに対して通信を実行するように実装し、通信先、負荷分散などの処理はambassadorコンテナに任せてしまう形にします。 こうすることで、メインコンテナの実装がシンプルになるだけでなく、テストと本番でメインコンテナの動作が同一になるため、テストの再現性が上がります。 また、sidecarパターンと共通する特徴ですが、ambassadorは様々なコンテナと組み合わせて利用できることも利点です。

4. Adapter pattern

シングルノードのパターンの最後は adapter pattern です。 このパターンでは、上述のコンテナマネジメントシステムへ、メインコンテナ内部の状態を知らせるような"upward"の通信を行う際などで、 サブコンテナが通信の形式を揃える (adapt) する役割を持ちます。 メインコンテナの実装言語や、frameworkが異なる場合などで、同じメトリクスでも異なるフォーマットで出力される場合にその差分を吸収して マネジメントシステム側で統一的に取り扱うことができるようになります。 このパターンの良いところは、既存のアプリケーションに追加する場合でも、メインのアプリケーションに手を入れずにインターフェイスを修正できることです。 反対に、メインのコンテナはアダプタの存在を知る必要がなく、(ある程度) 自由な形式で出力してよくなるため、実装がシンプルになることも利点と言えます。

*1

複数ノードのパターン

次に1つのノードで動くデザインパターンを紹介します。

5. Leader election pattern

複数ノードのパターンの最初は、 leader election pattern です。 このパターンでは、分散システムの辛い問題の筆頭にあげられるリーダ選出問題に対するパターンです。 リーダ選出は、一般にとても難しい問題として有名で、それを解くこれまた有名なpaxosというアルゴリズムがありますが、 このpaxosを有名にしている最大の要因は、このアルゴリズムの難解さが、それを専門に研究する研究者がアルゴリズムの説明を間違えることを懸念するレベルということです (なお、非同期を仮定するシステムでは100%合意に至ることのできるアルゴリズムはないと証明されています。はー、つれー。)。

上記のような難しいアルゴリズムを開発者が個々に実装することは、地獄に至るもっとも簡単な近道として知られています ( 社会通念上の共通認識 個人の感想)。 よってこのような場合、ライブラリの利用を検討するわけですが、 リーダ選出のためだけに、アプリ側がライブラリの言語に縛られるのも受け入れがたい場合が多いと思います。 そこで、リーダ選出のアルゴリズムを実装したコンテナ集合を別途用意し、http APIを介して通信しあうことで選出を行うことで、 アプリの本質的な実装に影響を与えず、複雑な機能の追加が可能になります。

6. Work queue pattern

f:id:hsasakawa:20190320143349p:plain:w300
work queue pattern (論文より引用)

Work queueパターンでは、リクエストされるジョブを貯めるqueue部分と、溜まったジョブをワーカに割り当てて、実行させる部分を切り出します。 このパターンを用いる際には、開発者は、個々のジョブを実行するための実装のみ (図の薄い色の部分) を開発すればよく、work queueの仕組み自体は特定のジョブとは独立しているため、実装言語や、適用先によらず再利用可能となります。

7. Scatter/gather pattern

f:id:hsasakawa:20190320143622p:plain:w300
scatter/gather pattern (論文より引用)

最も広く使われている分散処理の実装はHadoopをはじめとするJavaで実装されたMapReduceであると思いますが、それを抽象化したデザインパターンが scatter/gather pattern です。 このパターンでは、巨大なジョブの処理をルートコンテナに対してリクエストすると、ルートはそれを小さな部分問題に分割して、ワーカ的に動作する子のコンテナにばら撒きます。 ばら撒かれたサブタスクは、子のコンテナで処理され、結果をルートに返します。 この、「ルートが子にタスクをばら撒く (scatter)」機能と、「子がサブタスクを処理し、結果をルートが集める (gather)」機能をフレームワークとして切り出してコンテナ化することで、 これを利用する開発者は、ルートがタスクに分割する部分と、処理結果のマージ部分、子ノードでの処理部分をそれぞれフレームワークのインターフェイスに合わせて実装するだけでよくなり、 scatter/gatherのフレームワーク側は再利用可能となります。

まとめ

今回はコンテナベースシステムのデザインパターンに関しての論文である Design patterns for container-based distributed systems の内容を簡単に紹介しました。 これまで見てきたパターンについてはどれも、ある程度複雑だったり、責務が異なる処理を切り出して、別コンテナでの処理としてやることで、 本体の実装のシンプルさを保ちつつ、障害の局所化や、リソース割り当て、各種マネジメントシステムとの統合などが可能になるというメリットを享受できることがわかりました。

エムスリー社内のプロダクトにおいてもコンテナ技術は多数使われていますが、上記で紹介したデザインパターンを適用することでより良い設計にしていけるのではないかと思っています。 一方で、OOPにおいても典型的な、デザインパターンを使いたいだけ病に陥らないようにするためにも、個々のパターンについて、主要な実装を参考に深く学んで行きたいと考えています。

We're hiring!

エムスリーでは、コンテナ技術を使ってプロダクトをいい感じに設計したいエンジニアを募集しています。 我こそは!という方はぜひ以下からご応募ください。

m3.recruitment.jp

*1:2019/4/29 スペルミス修正: adopter → adapter