sprayで作ったRESTをDockerで動かしてみる

はじめに

Nyle
Nyle Engineering Blog
10 min readJul 29, 2016

--

マイクロサービスアーキテクチャで設計されたシステムを開発をしていると、複数のサーバーをもっと自由に使って試すことができたらいいなと感じることがあります。

実際の運用においては複数サーバーにそれぞれサービスを立ち上げて、相互にやりとりさせることでシステム全体を構築することになります。ところが開発中は開発者のPC一台が実行できる環境で、テスト用に本番と同じ数だけサーバーを用意できることはそう多くないと思います。

そうすると、開発中には問題にならなかったことに複数サーバーにデプロイして初めて気がつくことがあります。そもそもー台だけでは正しくテストできないものもあるでしょう。

そこで、1台のコンピューター上でも複数のサーバーでの動作を模倣させたくなります。そうです、仮想化です。

そこで、今回は仮想化技術の中でもDockerを利用することにして、sbtのプラグインで簡単にDockerイメージを作成し、sprayで作った簡単なRESTサービスをDockerを使って動かしてみることに挑戦してみましょう。

前提

動かす内容は去年「sprayを使って簡単なREST APIを構築する」という記事を書いたので、ここで作ったRESTサービスをそのまま使いましょう。

Dockerは最近Docker for Macが使えるようになったので、これを使うことにします。
そもそもDockerってなに?って方はちょっと古いですが過去にDockerに関する記事も書いているので、そちらを参考にするか最新情報を是非検索してみてください。
過去記事:今日から始めるDocker

Dockerやsprayに関する話は省略するので、そちらが不安な人は去年の記事もぜひ読んでみてくださね。

RESTサービスの作成

まずは全体のファイル構成はこんな感じ

├── project
│ ├── Build.scala
│ └── plugins.sbt
└── src
└── main
└── scala
├── Main.scala
├── MyListener.scala
└── ServiceActor.scala

ちなみに過去記事と違い Build.scala にしてみました。地味に.sbtの時よりトラップが多くて辛かったです。

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.1")

まずDockerイメージの作成ができるプラグインを追加しておきます。ちなみにこの sbt-native-packager は何もDockerイメージを作るためだけのものではありません。ここでは紹介しませんが他にも機能があります。

import sbt._
import Keys._
object BuildSettings {
val buildSettings = Defaults.defaultSettings ++ Seq(
name := "rest_sample",
scalaVersion := "2.11.7",
version := "1.0",
scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8")
)
}
object Dependencies {
val sprayCan = "io.spray" %% "spray-can" % "1.3.2"
val sprayRouting = "io.spray" %% "spray-routing" % "1.3.2"
val sprayJson = "io.spray" %% "spray-json" % "1.3.2"
val akkaActor = "com.typesafe.akka" %% "akka-actor" % "2.3.14"
val json4sNative = "org.json4s" %% "json4s-native" % "3.2.11"
}object Build extends Build {
import BuildSettings._
import Dependencies._

import com.typesafe.sbt.packager.archetypes.JavaAppPackaging
import com.typesafe.sbt.packager.archetypes.AshScriptPlugin // java:8-jdk-alpineはbashじゃなくashなので必要
import com.typesafe.sbt.packager.docker._
import com.typesafe.sbt.packager.docker.DockerPlugin.autoImport._
// Root project to aggregate all apps
lazy val root = Project("root", file("."), settings = buildSettings ++ Seq (
libraryDependencies ++= Seq(
sprayCan,
sprayRouting,
sprayJson,
akkaActor,
json4sNative
)
))
.enablePlugins(JavaAppPackaging, AshScriptPlugin, DockerPlugin)
.settings(
dockerBaseImage := "java:8-jdk-alpine"
)
}
そしてこれが Build.scala です。注目したいのは .enablePlugins でいくつかのプラグインを有効化しているのと、.settingsでdocker向けの追加設定を行っている点です。
必須なのは JavaAppPackaging と DockerPlugin なんですが、alpineを使いたかったので AshScriptPlugin も入れてashに対応させています。
設定には他にもいくつかありますが、ここではalpineへベースイメージの変更のみを行っています。
import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http
object Main extends App{
implicit val system = ActorSystem()
val service = system.actorOf(Props[ServiceActor])val myListener = system.actorOf(Props(new MyListener(service)))IO(Http) ! Http.Bind(myListener, interface = "0.0.0.0", port = 8080)
}
Main.scala はほぼそのままですが、interfaceだけ 0.0.0.0 へと変更してます。Dockerで実行した際に localhost では繋がらなくなってしまうので、hostに関係なく処理をするようにしています。import akka.actor.{Actor, ActorRef}class MyListener(service: ActorRef) extends Actor {
import spray.routing.HttpService._
def receive = runRoute {
path("ping") {
get {
complete("PONG")
}
} ~
pathPrefix("user") {
(pathEnd | path("")) {
get {
ctx => service !(ctx, "test")
}
} ~
path(Segment) { username =>
get {
ctx => service !(ctx, username)
}
}
}
}
}
import akka.actor.Actor
import org.json4s.DefaultFormats
import spray.httpx.{Json4sSupport, SprayJsonSupport}
import spray.json._
import spray.routing.RequestContext
class ServiceActor extends Actor with Json4sSupport{
import MyService.UserData
val json4sFormats = DefaultFormatsdef receive = {
case (ctx: RequestContext, message: String) =>
ctx.complete(UserData(message, 12))
}
}
object MyService {
object MyJsonProtocol extends DefaultJsonProtocol with SprayJsonSupport {
implicit val UserFormat = jsonFormat2(UserData)
}
case class UserData(name: String, age: Int)
}
こちら二つは以前の記事から変化なしです。Dockerイメージの作成では早速Dockerイメージを作ってみましょう。sbt docker:publishLocalを実行します。コンパイルが行われたのち、Dockerfileが生成されてDockerイメージの作成が行われます。出来上がるイメージは rest_sample:1.0 になりました。どうやら name:version となるようです。実行実行するときにはポートフォワードを設定してやる必要があります。REST自体は 8080 をlistenしているので、そこにホスト側の 8070 をつないでやりましょう。docker run -p 8070:8080 rest_sample:1.0これで http://0.0.0.0:8070/ping にアクセスしてみましょう。どうでしょう? PONG が返ってきましたか?最後にどうでしょうか?意外と簡単にDockerで動かすことができたんじゃないでしょうか?実際のアプリケーションを動かすためには、Dockerで動かすことを前提とした設計をする必要があります。むしろそっちの方が大変なのですが・・・ここがうまくいけばマイクロサービスアーキテクチャの旨味をさらに引き出すことができるでしょう。弊社ではまだ本番運用にDockerを持ち込むところまでは至っていないのですが、うまくいけばBlue-Green Deploymentなんかにもつなげることができると思い、現在も様々な検証を続けています。マイクロサービスアーキテクチャやScala、あるいはDockerやBlue-Green Deploymentにに興味があるエンジニアの皆さん。弊社では世界に展開する自社メディアを育てたいエンジニアを積極的に募集中です。少しでも興味があればぜひお気軽にお声がけください。インフラとの垣根なんてもったいないです。興味がある分野にはどんどん挑戦できる環境で、一緒にいろんな事試していきましょう!

--

--