@hayasshi_です。Scalaのプロダクトを開発しています。
Heroku、気軽にWebアプリケーションを公開できて便利ですよね。
先日公開した「チャットワークのWebhookの署名検証を各言語で実装してみた」の記事を書くにあたって、検証用のScalaアプリを Heroku Container Registry をつかってデプロイしました。
もともとDockerでHerokuにデプロイできれば、Scala以外のアプリのときでもデプロイの手順差異が少ないと思って試してみたのですが、公式ドキュメント通りに進めたところ少し嵌ってしまったので、備忘録を兼ねて記録しておきたいと思います。
なおHerokuのContainer Registryを利用したデプロイに関するドキュメントは下記で、基本的には記載通りの手順で問題ありませんでした。
ScalaにおけるDockerイメージ作成用のツールの兼ね合いもあり、Pushing an image(s)
の部分はPushing an existing image
の手順で進めました。
アプリケーションを用意する
まずはデプロイ対象のアプリケーションが必要です。
今回はサンプルとして、akka-http
をつかってping-pong
サーバーを作ってみたいと思います。
通常のScalaプロジェクトを作成して、下記のファイルを用意しました。
object PingPongServer extends App { implicit val system: ActorSystem = ActorSystem() implicit val materializer: ActorMaterializer = ActorMaterializer() val route = pathEndOrSingleSlash { post { extractRequest { req => val result = req.entity.dataBytes.map(_.utf8String).runWith(Sink.head) onSuccess(result) { body => println(s"Client input is '$body'") val res = body.toLowerCase match { case "ping" => "pong" case _ => body } complete(HttpEntity(ContentTypes.`text/plain(UTF-8)`, res)) } } } } val host = "0.0.0.0" val port = sys.env.getOrElse("PORT", "8080").toInt // $PORT is set by Heroku Http().bindAndHandle(route, host, port) sys.addShutdownHook({ import scala.concurrent.duration._ Await.ready(system.terminate(), 30.seconds) }) }
Dockerイメージを作成する
ChatWorkのScalaプロダクトではDockerイメージの作成は、sbt-native-packager
を利用しています。
今回も同様にsbt-native-packager
を利用しました。
plugins.sbt
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.1")
build.sbt
import com.typesafe.sbt.packager.docker.ExecCmd lazy val root = (project in file(".")) .settings( inThisBuild(List( organization := "com.chatwork.cwhayashi", scalaVersion := "2.12.3", version := "0.1.0-SNAPSHOT" )), name := "ping-pong", libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.0.10", libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.4.19", // Docker settings defaultLinuxInstallLocation in Docker := "/opt/application", executableScriptName := "app", dockerBaseImage := "openjdk:8u131-jdk-alpine", dockerUpdateLatest := true, mainClass in (Compile, bashScriptDefines) := Some("com.chatwork.cwhayashi.PingPongServer"), packageName in Docker := name.value, // Expose is NOT supported by Heroku // dockerExposedPorts := Seq(8080) // Run the app. CMD is required to run on Heroku dockerCommands := dockerCommands.value.filter { case ExecCmd("CMD", _*) => false case _ => true }.map { case ExecCmd("ENTRYPOINT", args @ _*) => ExecCmd("CMD", args: _*) case other => other } ) .enablePlugins(AshScriptPlugin)
いくつかポイントがあります。
- Heroku Container Registry に Push するイメージのDockerfileでは
CMD
を利用するsbt-native-packager
ではENTRYPOINT
を使う形でDockerfileが出力されるので、変換するコードを入れています
- Dockerfileで
EXPOSE
は使えない- 記述があっても無視されます
- 利用するコンテナポートはHerokuが環境変数
PORT
に設定して渡される- このサンプルアプリでは、プログラムコード内で環境変数
PORT
を取得して Listen するようにしています
- このサンプルアプリでは、プログラムコード内で環境変数
特に一番目のポイントはドキュメントにも記載がなかったので、注意が必要です。
sbt-native-packager
は特別な設定をしなければENTRYPOINT
でDockerfileを作成するのでうまく起動せず、ドキュメントに書いてあるHerokuサンプルのDockerfileをみて気付くことができました。
下記のコマンドを実行し、ScalaアプリのDockerイメージを作成します。
$ cd /path/to/sbt-project
$ sbt docker:publishLocal
...
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ping-pong 0.1.0-SNAPSHOT 6d789fb48461 42 seconds ago 121MB ping-pong latest 6d789fb48461 42 seconds ago 121MB
Herokuアプリを作成する
続いてHerokuのアプリ領域を作成します。
こちらはドキュメントに記載の通り、CLIツールをつかって簡単に作成できます。
$ heroku login # Input Email and Password Logged in as ... $ heroku apps:create <app-name> Creating ⬢ <app-name>... done https://<app-name>.herokuapp.com/ | https://git.heroku.com/<app-name>.git
HerokuにイメージをPushする
すでに存在するDockerイメージをPushする場合は、ドキュメントのPushing an existing image
の手順を行う必要があります。
基本的にdocker
コマンドをそのまま利用します。
# Heroku Container Registryへのログイン $ heroku container:login # Heroku Container Registry用にタグ付け $ docker tag ping-pong registry.heroku.com/<app-name>/web # Heroku Container RegistryへPush $ docker push registry.heroku.com/<app-name>/web
Pushが完了した時点でアプリケーションはデプロイされ実行されます。
2018-06-27追記
確認したところ、明示的にreleaseのためのコマンドを実行する必要になった模様です。
Container Registry & Runtime (Docker Deploys) | Heroku Dev Center
heroku container:release web -a <app-name>
実行することでpushされたイメージからアプリが起動し、下記のように接続できるようになりました。
$ curl -X POST -d "test" https://<app-name>.herokuapp.com/ test $ curl -X POST -d "ping" https://<app-name>.herokuapp.com/ pong $ curl -X POST -d "🍣🍺" https://<app-name>.herokuapp.com/ 🍣🍺
まとめ
無事にScalaアプリのDockerイメージをHerokuで実行することができました。
途中の嵌りポイントは、Heroku Container Registryを利用する上で共通と思われますのでご注意ください。
Herokuをつかって、チャットワークのWebhookやOAuthアプリケーションをぜひ作ってみてください!