「マイクロサービス」は、この10年でもっとも興味深いアーキテクチャーのスタイルを表す言葉として、使い散らされている最新のアーキテクチャーバズワードでしょう。
マイクロサービスとは?
Martin Fowlerの定義によれば以下のようになります。
要するにマイクロサービスというアーキテクチャーのスタイルは、それぞれが独自のプロセスで動きHTTPリソースAPIなどの軽量なメカニズムでコミュニケーションを取り合う小さなサービスの形をとったひとつのアプリケーションを開発していくというアプローチです。これらのサービスは、ビジネス上の機能を中心に作られ、完全に自動化されたデプロイの仕組みによって、別々にデプロイできます。これらのサービスを集中的に管理する仕組みがあり、それは異なるプログラミング言語で書かれていたり、異なるデータストレージ技術を使っていたりする場合もあります。
さらにシンプルに言えば、マイクロサービスは、コンテキスト境界(bounded context)の中で協力し合い、HTTPのような軽量な通信手段でコミュニケーションし合う小さな自律的に動くサービスあるいは独立したプロセスであると考えられます。
マイクロと言ってもどの程度の小ささなのか?それは何でもそうですが状況によります。マイクロサービスはひとつのアクターからなるべきだと主張する人もいます。Jon Eaves氏のように、最大でも2週間で完了できるものにすべきという人もいます。私は、大まかなルールとしては小さなチーム(または開発者1人)で容易にメンテナンスできるぐらいに小さいもので、何かひとつのことをやるのに焦点を当てており、それをうまく処理できるべきだと思っています。
こういったアーキテクチャーの利点はたくさんあります。例えば、新しい技術に素早く取り掛かりやすく、チームを成長させやすくなります。ある問題に対してそれを解決するのに適した技術を採用しやすいということです(例として、Neo4jをストレージにしてScalaで書くことも、CassandraをバックエンドにしてGoで書くこともできます)。また、複数のマシンを使ったサービス群に分散しているので、システム全体が停止してしまうリスクを限定することができます。比較的小さなサーバ上でスケールさせやすいので、コストを劇的におさえられます。
しかし一方で、このアプローチは別の部分には複雑さをもたらしもします。そのひとつがルーティングです。
統一されたルーティング
複数のコンテキスト境界に分かれた比較的複雑なドメインを考えると、それらは2つ以上のマイクロサービスから成り立っていて、それぞれが特定のドメインの処理を行うことになります。これらをスケールさせていくと、その数はどんどん大きくなります。
こういったサービス群を使っている時、それらを全部追跡していたくはないでしょう。さらに進むと、もしこれらのサービスと通信する必要のあるモバイルアプリケーションを書いたとして、各マイクロサービスへのたくさんのアドレスを全てメンテナンスしなくてはなりませんが、そんなことしたくありません。コンテキスト境界ごとに1つのベースURL(http://account.mystuff.com/api のようなもの)を割り当てるようプログラムでき、ヘッダーを基にどのマイクロサービスを呼び出すか何らかの形で分かればましになるでしょう。
そこで、HAProxyの出番です。
マイクロサービスにHAProxyを使う
その名前が示すように、HAProxyはTCPとHTTPの両方が使える高可用性のプロキシサーバーであり、ロードバランサーです。詳しい情報はドキュメントにあります。
多くの機能がありますが、どのバックエンドにリクエストを送るか決めるのに使われるアクセスリスト(ACL)のコンセンプトが重要です。ヘッダーとURL、さらに他のものについてもACLが使われます。
ひとつのコンタクトポイントを持つようにしたいモバイルアプリケーションの例に戻ると、開発者が送信できるリクエストには、HAProxyがどのエンドポイントにルーティングするかを判断するためのヘッダー(例えば"x-microservice-app-id")を含めることができます。注 : このコンタクトポイントは、SPOF(単一障害点)になるのを防ぐために、ロードバランサーを指す複数のIPアドレスを持つAレコードに紐づけられている必要があります。
これを実現するための例は以下の通りです。
## Mesosphere Marathonのservicerouter.pyにおけるhaproxyの設定がベース
global
daemon
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
maxconn 4096
tune.ssl.default-dh-param 2048
defaults
log global
retries 3
maxconn 2000
timeout connect 5s
timeout client 50s
timeout server 50s
listen stats
bind 127.0.0.1:9090
balance
mode http
stats enable
stats auth admin:admin
frontend microservice_http_in
bind *:80
mode http
frontend microservice_http_appid_in
bind *:81
mode http
acl app__accountCreationService hdr(x-microservice-app-id) -i /accountCreationService
acl app__profileEditingService hdr(x-microservice-app-id) -i /profileEditingService
use_backend accountCreationService_10000 if app__accountCreationService
use_backend profileEditingService_20000 if app__profileEditingService
frontend microservice_https_in
bind *:443 ssl crt /etc/ssl/yourCertificate
mode http
frontend accountCreationService_10000
bind *:10000
mode http
use_backend accountCreationService_10000
frontend profileEditingService_20000
bind *:20000
mode http
use_backend profileEditingService_20000
backend profileEditingService_20000
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server 151_256_250_152_35900 151.256.250.152:35900
# 追加のサーバがあればここに
backend accountCreationService_10000
balance roundrobin
mode http
option forwardfor
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
server 101_206_200_192_31900 101.206.200.192:31900
# 追加のサーバがあればここに
ヘッダー名がクライアント側で管理しなければならないものになります。何らかの命名規則を決めて使えば、ヘッダーの値は算出可能になるので、大量になる可能性のあるヘッダー名をクライアントが管理する必要性をなくすことができます。
このアプローチに従うと、HAProxyの設定をデプロイと合わせて同期する機能が必要になるでしょう。そういったことを行う色々なツールがあります。例えば、Marathonは上の例と同じようなHAProxyの設定を生成するユーティリティですし、KongはAPIゲートウェイです。