流行りなのでmicroservicesという用語を使ってみましたが、意図としては(microservicesも含む)マルチサーバー構成によるクラウド・システムを構成するサービス群がターゲットです。
この実行基盤としてFinagle、Karaf、Dockerの組合せが有力ではないかということで、試しにサンプルプロジェクトを作ってみました。このサンプルプロジェクトは、新しいクラウド・システム内サービスを作る時の雛形を想定しています。
このサンプルプロジェクトを実際に作ってみることで、Karafがどのぐらいの手間で使えるのか、ScalaやFinagleとの相性はどうなのか、といったことを試してみるのが目的です。この手のプロジェクト構築ではライブラリ間の依存関係の解決が難しいのですが、Finagleをリンクしても大丈夫な設定を発見できたので、この問題はクリアできていると思います。
以下ではクラウド・システム内サービスをサービスの粒度によらずmicroserviceと呼ぶことにします。
Apache Karaf
Apache Karafは軽量OSGiコンテナです。
Karafを導入する目的は大きく以下の3つです。
- microserviceの環境設定共有
- dependency hell対策
- CamelによるEIP(Enterprise Integration Patterns)
環境設定共有
第一の目的はクラウド・システムを構成するmicroservice群の環境設定を共通化することです。
microserviceをmainメソッドを作るなどして自前でデーモン化すると各種設定もすべて自前で行わなければならなくなります。
たとえば、SLF4J+fluentedなどによるロギング、JMX(Java Management Extensions)による運用監視、JNDI(Java Naming and Directory Interface)によるディレクトリ管理といった各種設定をサービス毎に設定する必要があります。
これは(1)設定そのものが大変な手間、(2)クラウド・システム内の共通設定情報の共有の手間、(3)設定漏れの危険、(4)ハードコーディングによるカスタマイザビリティの欠如、といった問題があります。
microserviceをコンテナ内で動作させることで、各種設定はコンテナに対する設定として共通化する上記の問題を解消することができます。
この目的で導入するコンテナとして、軽量OSGiコンテナであるKarafがよいのではないか考えています。
dependency hell対策
Java VM上での開発における未解決問題の一つにdependency hellがあります。
この問題はJava 9でModule機能として解決される見込みですが、当面の対策としてはOSGiコンテナを使用するのが現実解となっています。
最近はちょっとしたライブラリをリンクしても、その裏でApache Commons、Spring、JBossといった巨大なライブラリがついてくることが多いので、思ったより切実な問題です。
Twitter Finagle
Twitter Finagleはmicroservices指向のRPCシステムです。
FinagleはFutureモナドによる非同期実行、耐故障性の抽象化を行っており、Monadic Programmingとの相性がよいのもFunctional Reactive Programming指向のmicroservicesの通信基盤として魅力です。
準備
Karafを使うためのベースとしてKarafの実行環境をDockerイメージ化しました。
Karaf Docker Image
Dockerイメージを作成するプロジェクトは以下になります。
Dockerfileだけの簡単なプロジェクトです。
実際にmicroserviceをインストールして使うことを想定しているので、余分な設定は行わずプレインな簡単なものにしています。
Docker Hub
このDockerイメージをDocker Hubに登録しました。
以下のようにして利用できます。
$ docker pull asami/karaf-docker
サンプル・プロジェクト
サンプル・プロジェクトは以下のGitHubプロジェクトとして作成しました。
サンプル・サーバー
Finagleによるサンプル・サーバーは以下のものです。
package sample import com.twitter.finagle.{Http, Service, ListeningServer} import com.twitter.util.{Await, Future} import java.net.InetSocketAddress import org.jboss.netty.handler.codec.http._ object Server { val service = new Service[HttpRequest, HttpResponse] { def apply(req: HttpRequest): Future[HttpResponse] = Future.value(new DefaultHttpResponse( req.getProtocolVersion, HttpResponseStatus.OK)) } def start(): ListeningServer = { Http.serve(":8080", service) } def stop(server: ListeningServer) { server.close() } def main(args: Array[String]) { val server = start() Await.ready(server) } }
ビルド
ビルドは以下の手順で行います。
- Karaf KAR
- Dockerfile
- Dockerイメージ
Karaf KAR
Karafに配備するKARファイルの作成にはkarafタスクを使用します。
$ sbt karaf
karafタスクはbuild.sbtで定義しています。
Dockerfile
KARファイルを配備したKaraf実行環境のDockerイメージを作成するためのDockerfileの作成にはdockerタスクを使用します。
$ sbt docker
dockerタスクはbuild.sbtで定義しています。
Dockerイメージ
Dockerイメージの作成は以下になります。
$ docker build -t sample-finagle-karaf-docker .
dockerタスクを実行するとDockerfileが作成されるので、このDockerfileでDockerイメージをビルドします。この時、前述したDockerイメージasami/karaf-dockerを内部的に使用します。
実行
作成したDockerイメージsample-finagle-karaf-dockerを実行してみましょう。
$ docker run -t -i --rm -p 1099:1099 -p 8101:8101 -p 44444:44444 -p 8080:8080 sample-finagle-karaf-docker
ポートは1099, 8101, 44444, 8080の4ポートを使用します。
ポート1099, 8101, 44444はKarafが使用するポートです。
ポート | 用途 |
---|---|
1099 | JMX RMI registry |
8101 | SSHコンソール |
44444 | JMX RMI server |
ポート8080はサンプルのFinagleサービスが使用するポートです。
確認
Dockeイメージを実行するとKarafコンテナ内で自動的にサンプルのFinagleサービスが起動されます。
curlコマンドでサンプルのFinagleサービスにアクセスすると以下の結果が得られます。無事動作していることが確認できました。
$ curl http://192.168.59.103:8080 -v * Rebuilt URL to: http://192.168.59.103:8080/ * Hostname was NOT found in DNS cache * Trying 192.168.59.103... * Connected to 192.168.59.103 (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.37.1 > Host: 192.168.59.103:8080 > Accept: */* > < HTTP/1.1 200 OK < Content-Length: 0 < * Connection #0 to host 192.168.59.103 left intact
感想
現時点だとSBTからKaraf向けのKARファイルの作成のための設定が大変なので、まだまだKarafは気軽には使えないような感じです。
ただAPPREL CLOUD級の規模になってくると設定の大変さよりも運用管理上のメリットの方が大きくなるのでそろそろ導入を考えてもよさそうです。
諸元
- Scala 2.10.5
- Finagle 6.25.0
- Karaf 4.0.0.M2
- Docker 1.3.2
- sbt 0.13.7