Http4s で書いた Scala のサーバーを cloud run にデプロイする
Http4s で書いた Scala のサーバーを cloud run にデプロイする最小構成の例
サーバーを sbt-native-packager プラグインで Docker イメージにしてリリースします.
http4s は Scala の Typelevel エコシステムのウェブサーバーを書くためのライブラリです.
Typelevel エコシステムのライブラリははやくから Scala 3 対応が進んでいます. もちろん http4s も Scala 3 で書くことができます.
build.sbt にプロジェクトを定義してライブラリを追加します.
json ハンドリングをしないので core
,dsl
,ember-server
モジュールだけでOk です.
val V = new {
val scala213 = "2.13.8"
val http4s = "1.0.0-M36"
val CE = "3.3.12"
}
lazy val server = project.in(file("server"))
.enablePlugins(JavaAppPackaging)
.enablePlugins(DockerPlugin)
.settings(
run / fork := true,
scalaVersion := V.scala213,
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-core" % V.http4s,
"org.http4s" %% "http4s-dsl" % V.http4s,
"org.http4s" %% "http4s-ember-server" % V.http4s,
"org.typelevel" %% "cats-effect" % V.CE
)
)
.settings(
Docker / packageName := "<region>.gcr.io/<project-name>/path/to/image",
// Docker / packageName に gcr のレポジトリを含め代わりに
// Docker /packageName := imageName
// dockerRepository := <region>.gcr.io/<project-name>
// としてもいい.
dockerExposedPorts ++= Seq(8080,8080),
dockerUpdateLatest := true, // latest tag を更新する場合はこれを true に設定する. デフォルトは false.
dockerBuildCommand := {
if (sys.props("os.arch") != "amd64") {
dockerExecCommand.value ++ Seq("buildx", "build", "--platform=linux/amd64", "--load") ++ dockerBuildOptions.value :+ "."
} else dockerBuildCommand.value
}
)
以下の部分は、arm の端末で amd64 向けにイメージをビルドするためのワークアラウンド.
arm 向けにビルドすると cloud run でサーバーを動かすことができないので docker buildx
コマンドを使ってクロスビルドする必要があります.
if (sys.props("os.arch") != "amd64") {
dockerExecCommand.value ++ Seq("buildx", "build", "--platform=linux/amd64", "--load") ++ dockerBuildOptions.value :+ "."
} else dockerBuildCommand.value
}
Docker イメージをビルドするために sbt-native-packager プラグインを追加します.
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.11")
サーバーを以下のように実装します.
// Blaze は古いので Ember を使う
import org.http4s.ember.server.EmberServerBuilder
// IO の runtime の管理や main 関数を省略するために IOApp を使う
// (Scala の App trait の cats effect 版のようなもの)
import cats.effect.IOApp
import org.http4s.dsl._
import com.comcast.ip4s._
// dsl.io._ をインポートすると、akka のようなルーティング DSL が使える.
import org.http4s.dsl.io._
import cats.effect.{ExitCode, IO}
import org.http4s.HttpRoutes
object Main extends IOApp {
val pingPong = HttpRoutes.of[IO] {
case GET -> Root / "ping" =>
Ok("pong")
}
def run(args: List[String]): IO[ExitCode] =
EmberServerBuilder
.default[IO]
.withHost(ipv4"0.0.0.0")
.withPort(port"8080")
.withHttpApp(pingPong.orNotFound)
.build
.useForever
.as(ExitCode.Success)
}
イメージをビルドします. 以下のコマンドで "<region>.gcr.io/<project-name>/path/to/image:0.1.0-SNAPSHOT
という名前の Docker イメージがローカルに生成されます. この名前は build.sbt の Docker / packageName
で変えられます.
sbt docker:publishLocal
以下のコマンドで docker push <region>.gcr.io/...
で GCP の Container Registry にイメージを push できるようにします.
gcloud auth login
gcloud auth configure-docker
gcr にアプリケーションのイメージを push します.
docker push <region>.gcr.io/<project-name>/path/to/image:0.1.0-SNAPSHOT
あとは GCP のコンソールにログインして cloud run の画面を開き、push したイメージを選択してリリースすればサーバーを公開できます.
http4s はさまざまなライブラリと組み合わせることができます. 例えば circe を使って json を受け取って返すサーバーを作ったり、tapir を使ってルーティング定義から openapi を自動生成したりすることができます. シンプルな API を提供しているので Play Framework を使うほどでもないちょっとした API サーバーを立てるケースにも活躍します.
Discussion