Scala on Cloudflare
最近はCloudflare Containersというのもあるし、ネイティブコンパイルしたScalaをデプロイするとかだと軽量でおもろいであろう
構成要素
Scala CLI
Scala Nativeへのブリッジをやってくれる
Cloudflare Container
Cloudflare Containerとは?
Cloudflareのエッジ上でDockerコンテナが起動し、HTTPをサーブできる仕組み
Earthリージョンにデプロイされ、どこからでも利用可能
必要なもの
いつもの wrangler.jsonc
最初にリクエストを受け取る javascript code
Dockerfile
試す
$ pnpm create cloudflare@latest --template=cloudflare/templates/containers-template
$ pnpm wrangler deploy
おもしろい点: 勝手にレジストリのログインとpushをやってくれる
https://scrapbox.io/files/68790caaec794a7400d8ffad.png
CLIにはhttps://cf-containers-experiment.windymelt.workers.devを案内されたので行くと
https://scrapbox.io/files/68790d04cbbfe8e879b877ce.png
どのような仕組み?
まずリクエストはいつも通りWorkerが受けている
テンプレで勝手に作られた
WorkerにはContainersがアタッチされていて、その情報を起動時にバインディング経由で受け取っている
実態のデータはDurableObjectで管理されている
DOには型が用意されている(@cloudflare/containersのContainerをextendしたclassを用意すればよい)
listenしてるポート
どのくらいidleになったら眠りにつくか
環境変数
を渡せる
Workerからは、コンテナは単なる「HTTPを喋るなんか」として扱われる
fetchできる
Containerを得るには、getContainerなどのメソッドをバインディングに対して呼び出す
getRandom: ロードバランシング。N個のインスタンスのうち、1つに接続する
getContainer: キーをもとにコンテナにつなぐ
複数回のリクエストで別々のコンテナインスタンスに泣き別れにならないための仕組みっぽい
特にキーを指定しなければデフォルトでcf-singleton-containerを指定したことになるようだ
Workerがfetchした結果をそのまま返せば、実質的にContainerでリクエストをハンドルできる
コンテナはというと
CLOUDFLARE_DEPLOYMENT_IDという環境変数がもらえる。これはインスタンスのIDとして使える
Dockerfileさえ作ればよさそうなことがわかったので・・・
Scala Nativeでコンテナを作ることにする
Scala
Scalable Programming Language
JVMを土台に生まれた言語
非同期処理や関数型プログラミングに強みあり
RustにGCひっついたと思ってもらえればよさそう
Scala Native
プログラミング言語Scalaをネイティブコンパイルするプロジェクト
Scala風の言語が動くのではなく、フルセットのScalaがそのままネイティブコンパイルされる
LLVMをバックに動く
高速起動・省メモリ・省サイズなの売り
今回のビルドツールはScala CLI
コマンド1つでJVM・JS・Nativeにパッケージング可能
Denoライクな使い勝手
スクリプト自体に依存パッケージを直接書くと解決してくれる
スクリプト単体で処理系のバージョン固定が可能
ポータブル
code:scala
//> using scala 3.6
//> using platform native
//> using nativeVersion 0.4.17
//> using dep org.http4s::http4s-ember-server::0.23.30
//> using dep org.http4s::http4s-dsl::0.23.30
//> using dep com.armanbilge::epollcat::0.1.7
import cats.effect.*
import cats.effect.std.Env
import com.comcast.ip4s.*
import epollcat.EpollApp
import org.http4s.HttpRoutes
import org.http4s.dsl.io.*
import org.http4s.ember.server.*
import org.http4s.implicits.*
import scala.concurrent.duration.Duration
object Main extends EpollApp {
def getDeploymentId: IO[OptionString] = EnvIO.get("CLOUDFLARE_DEPLOYMENT_ID") val helloWorldService = HttpRoutes.ofIO { case GET -> Root / "hello" / name =>
for {
depId <- getDeploymentId
resp <- Ok(s"Hello, $name from Scala Native Container (${depId})!")
} yield resp
}
EmberServerBuilder
.withHost(ipv4"0.0.0.0")
.withPort(port"8080")
.withHttpApp(helloWorldService.orNotFound)
.withShutdownTimeout(Duration(1, "second"))
.build
.use(_ => IO.println("Server started") >> IO.never)
.onCancel(IO.println("Cancelling..."))
.as(ExitCode.Success)
}
これをコンテナにする
Dockerfileを用意する
code:Dockerfile
FROM virtuslab/scala-cli:1.8.4 AS builder
RUN apt install libssl-dev && apt update
RUN mkdir /app
WORKDIR /app
COPY server.scala server.scala
RUN --mount=type=cache,target=~/.ivy2 --mount=type=cache,target=~/.cache/coursier scala-cli --power package -o /app/server server.scala
FROM debian:bookworm-slim
RUN apt update && apt install -y openssl
RUN mkdir /app
COPY --from=builder /app/server /app/server
EXPOSE 8080
# Avoiding PID 1
コンテナの準備ができたのでさっきのテンプレートのDockerfileとすげ替えておく
@windymelt: Yeah (Scala NativeコンテナがCloudflare Containersで動いた) https://pbs.twimg.com/media/GwE97NbWQAEdAWI.png
用途
ファイルシステムが使える(エフェメラルだが)
QRコード生成
分散FSとかのエッジを動かしたりしたら面白いのでは
sidecar
ログとか
API Proxy (envoyみたいな)
バッチ処理のオフロード
他にはSSG?レンダラとか