ScalaよりもGAE色が強くなってしまった、Scala advent calendarなのに...^^;
はじめに:Google App Engine
元祖PaaSのGAE、忘れられた存在、、、かと思いきや、最近またよく耳にするようになってきました。
東京リージョンでの利用開始ニュースや主にGolangでの採用事例紹介などなど。
翻ってScalaエンジニア的にGAE/Javaってどうだろう?と考えると、バージョンがJava7だったりjettyベースだったりしてちょっと魅力に欠けるかなと個人的には思ってしまいます。
ただし、ドキュメントをよくよく読んでみると、GAEには2種類あり、これまでのStandard Environmentに加えベータではあるもののflexible environmentというのがあり(Standardはつい最近東京リージョンでも利用可能になりましたがベータはUSかヨーロッパのみとなります)こちらはJava8とあります。
さらにドキュメントのAbout Java Runtime Environmentsのところを見ると2種類あることがわかります。
Standard同様jetty版があるのはいいとして、 単なるJava8の実行環境だけというのもあります!
あら、もしかしてakka-http動くんじゃないの?と思ってやってみた、というのがこの記事となります。
稼働させるための条件
上記のリンクを読むと、GAE flex環境でアプリケーションを稼働させるには以下の3つあればいける模様。
- app.yaml
- 8080ポートでlistenするmainプログラムを含んだjar(各種ライブラリを含んだfat-jar)
- Dockerfile
準備
事前に以下2点を行っておく必要があります。
- GAE上でプロジェクトを作っておく
-
Cloud SDKを開発PCにインストールしておく
- GAEにデプロイするのに使用する
gcloud
コマンドを含む
- GAEにデプロイするのに使用する
※あと今回のscalaコードではslack認証をしているので、サンプルコードを動かすにはSlack上でアプリケーションを登録し、client_idおよびclient_secretを取得しておく必要があります。
ソースコード
まずはじめにディレクトリ構成は以下。
.
├── Dockerfile
├── app.yaml
├── build.sbt
└── src
└── main
├── resources
└── scala
└── my
└── Main.scala
app.yaml
とりあえず必要最低限だけ。
runtime: custom
env: flex
Dockerfile
自分で作るfat-jarの名前はmygae.jarとします。
FROM gcr.io/google_appengine/openjdk8
VOLUME /tmp
ADD mygae.jar app.jar
CMD [ "java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
build.sbt
scalaは最新の2.12.0を、akka-httpも最新の10.0.0を。
なおfat-jarを作るためにsbt-assemblyプラグインも別途入れております。
scalaVersion := "2.12.0"
libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.0.0"
assemblyJarName in assembly := "mygae.jar"
mainClass in assembly := Some("my.Main")
Main.scala
Slack認証する部分をサンプルコードとしてみました。
slauth
というエンドポイント部分はakka-httpのサーバーとしてのプログラム例として、またそこからSlackのoauth APIを呼び出す部分はakka-httpのクライアント例として見ることが出来ます。
本来は返ってきたjson文字列をcase classにunmarshalするようにすべきですがそこはサボって単に画面にjson文字列をベタッと表示するだけのシンプルなものにしています。
package my
import akka.actor.ActorSystem
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.{Http, HttpExt}
import akka.stream.ActorMaterializer
import akka.util.ByteString
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.util.{Failure, Success}
object Main extends App {
implicit val actorSystem = ActorSystem()
implicit val ec = actorSystem.dispatcher
implicit val actorMaterializer = ActorMaterializer()
val http: HttpExt = Http(actorSystem)
object Controller {
val route: Route =
path("slauth") {
get {
parameters('code) { code =>
onComplete(
http.singleRequest(HttpRequest(uri = s"https://slack.com/api/oauth.access?client_id=クライアントID&client_secret=クライアントシークレット&code=$code")).flatMap {
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
entity.dataBytes.runFold(ByteString(""))(_ ++ _).map(_.utf8String)
case resp @ HttpResponse(httpCode, _, _, _) =>
sys.error(s"fail: $httpCode")
}
) {
case Success(result) => complete(result)
case Failure(_) => complete(StatusCodes.InternalServerError)
}
}
}
}
}
val bindingFuture = Http().bindAndHandle(Controller.route, "0.0.0.0", 8080)
sys.addShutdownHook {
val awaitable = for {
binding <- bindingFuture
_ <- binding.unbind()
_ <- actorSystem.terminate()
} yield ()
Await.ready(awaitable, 10 seconds)
}
}
なお、 bindAndHandle
メソッドの第二引数部分は "0.0.0.0"
とします。ここは localhost
とか 10.0.0.1
とかだと動かないので注意が必要です。
デプロイ
- コンソール上で
sbt assembly
実行 - 出来た
mygae.jar
をtargetディレクトリからapp.yaml
やDockerfile
が配置してあるプロジェクト直下のディレクトリに移動 - コンソール上で
gcloud app deploy
初回は少し時間がかかりますが、2回目以降は3-4分くらいでデプロイ出来ます。
実行
ブラウザから https://slack.com/oauth/authorize?client_id=....&scope=...&redirect_uri=...
のURLを入力してSlackの認証画面を表示し「Authorize」ボタンを押下すれば、ブラウザ上にjson文字列が表示されるはずです!
終わりに
scalaを稼働させる新たな基盤候補が見つかってよかったです。
追記
ログ出力は以下のAPI使ってやるのかな、と思っていたのですが
http://qiita.com/mtoyoshi/items/d11bcfa6f7ed51e5a813
こちらの方式のほうが簡単ですね。
http://qiita.com/chidakiyo/items/1452ab1ea83279a8e485