AWS AppRunnerでzio-httpで実装したAPIを動かしてみる
普段、チームではScalaやAkkaを使ってAPIを開発してk8s上で動かすことが多いのですが、今回はサクッとzio-httpを使ってhello worldを返すAPIを実装してAWS AppRunnerで動かしてみようということで試してみます。
APIの実装
まずはライブラリやプラグインの設定です。
とりあえず、zioやzio-httpは2系を使ってます。
ThisBuild / version := "1.0.0"
ThisBuild / scalaVersion := "3.1.3"
ThisBuild / scalacOptions := Seq(
"-Yretain-trees"
)
val Versions = new {
val zio = "2.0.1"
}
lazy val root = (project in file("."))
.enablePlugins(JavaAppPackaging)
.enablePlugins(DockerPlugin)
.settings(
name := "zio-simple-api",
libraryDependencies ++= Seq(
"dev.zio" %% "zio" % Versions.zio,
"dev.zio" %% "zio-streams" % Versions.zio,
"dev.zio" %% "zio-json" % "0.4.2",
"io.d11" %% "zhttp" % "2.0.0-RC11"
)
)
.settings(
Docker / packageName := "<aws account_id>.dkr.ecr.<region name>.amazonaws.com/<repository name>",
dockerExposedPorts ++= Seq(9000, 9000),
dockerBaseImage := "openjdk:11",
dockerBuildCommand := {
if (sys.props("os.arch") != "amd64") {
dockerExecCommand.value ++ Seq(
"buildx",
"build",
"--platform=linux/amd64",
"--load"
) ++ dockerBuildOptions.value :+ "."
} else dockerBuildCommand.value
}
)
sbt-native-packagerプラグインを使ってDockerイメージを作成します。作成したらローカルからECRにイメージをプッシュしてAppRunnerから使用します。
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("com.lightbend.sbt" % "sbt-javaagent" % "0.1.6")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.11")
addSbtPlugin("com.github.sbt" % "sbt-git" % "2.0.0")
APIサーバーの実装です。
ルートパスにアクセスすると{"message": "hello world!"}
が返ってくる単純なAPIサーバーです。
package com.ryskit.api
import zio.*
import zhttp.http.*
import zhttp.http.{Http, Method, Request, Response}
import zhttp.service.Server
import zio.json.*
@jsonMemberNames(SnakeCase)
case class Greeting(message: String)
object Greeting {
implicit val decoder: JsonDecoder[Greeting] = DeriveJsonDecoder.gen[Greeting]
implicit val encoder: JsonEncoder[Greeting] = DeriveJsonEncoder.gen[Greeting]
}
object Main extends ZIOAppDefault {
val port = 9000
val app: Http[Any, Nothing, Request, Response] = Http.collect[Request] { case Method.GET -> !! =>
val greeting = Greeting("hello world!")
Response.json(greeting.toJson)
}
private val program = for {
_ <- Console.printLine(s"server is running at http://localhost:$port")
_ <- Server.start(port, app)
} yield ()
override def run: ZIO[Environment with ZIOAppArgs with Scope, Any, Any] = program
}
これでAPIの準備は完了です。
ECRを作成
ご自身のAWSアカウントの作成や設定は既に済んでいる前提で進めます。
AWS CLIを利用してCloudFormationを実行しAmazon ECRを作成します。
実行コマンド
aws cloudformation create-stack \
--stack-name zio-simple-api-ecr \
--template-body file://01_ecr.yml \
--parameters ParameterKey=EnvType,ParameterValue=dev
ファイル内容
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECR
Parameters:
EnvType:
Description: Environment type.
Type: String
AllowedValues: [dev, stg, prod]
ConstraintDescription: must specify dev, stg or prod.
Resources:
ECRRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub "zio-simple-api-${EnvType}"
ImageTagMutability: IMMUTABLE
Outputs:
EcrRepositoryUri:
Value: !GetAtt ECRRepository.RepositoryUri
Export:
Name: zio-simple-api-ecr-repository-uri
IAMを作成
App RunnerがECRのイメージにアクセスするために使用するロールを作成します。
実行コマンド
aws cloudformation create-stack \
--stack-name zio-simple-api-iam \
--template-body file://02_iam.yml \
--parameters ParameterKey=EnvType,ParameterValue=dev \
--capabilities CAPABILITY_NAMED_IAM
ファイル内容
AWSTemplateFormatVersion: "2010-09-09"
Description: Create IAM Role
Parameters:
EnvType:
Description: Environment type.
Type: String
AllowedValues: [dev, stg, prod]
ConstraintDescription: must specify dev, stg or prod.
Resources:
AppRunnerAccessRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'zio-simple-api-${EnvType}-apprunner-access-role'
Description: zio simple api access role for appruner
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- build.apprunner.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess
Outputs:
RoleArn:
Value: !GetAtt AppRunnerAccessRole.Arn
Export:
Name: zio-simple-api-access-role-arn
Dockerイメージを作成する
ECRのURI設定
sbt-native-packagerプラグインを利用してDockerイメージを作成します。
build.sbtのsettings内で定義していた Docker / packageName
に作成したECRのリポジトリURIを設定してください。
Docker / packageName := "<作成したECRのリポジトリURI>",
sbt-native-packagerでDockerイメージを作成する
プロジェクトルートで以下のコマンドを実行します。
sbt docker:publishLocal
実行するとローカルにイメージが作成されます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/zio-simple-api-dev 1.0.0 123456789012 20 seconds ago 675MB
作成されたイメージに付けられているタグはbuild.sbtで記述している version
が使われているため変更したい場合はここを変えると良いです。
DockerイメージをECRにプッシュする
ECRにログインする
以下のコマンドを実行してECRにログインします。
aws ecr get-login-password | docker login --username AWS --password-stdin <aws account_id>.dkr.ecr.<region name>.amazonaws.com/<repository name>
ECRにイメージをプッシュする
先ほど作成したイメージをECRにプッシュする場合は以下のコマンドを実行します。
docker push <aws account_id>.dkr.ecr.<region name>.amazonaws.com/<repository name>:<tag>
これでECRにイメージが登録されていればOK!
Amazon App Runenrを作成する
ここまでできればあとは簡単!Amazon App Runnerを作成するだけです。
以下のようにCloudformationテンプレートを用意して実行します。
実行が完了したかどうか確認するには管理コンソールからCloudFormationのStacksを確認するか以下のコマンドを実行して、zio-simple-api-apprunner
というスタック名があれば実行が完了しています。
aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE
実行コマンド
aws cloudformation create-stack \
--stack-name zio-simple-api-apprunner \
--template-body file://03_apprunner.yml \
--parameters ParameterKey=EnvType,ParameterValue=dev
ファイル内容
AWSTemplateFormatVersion: "2010-09-09"
Description: Create App Runner
Parameters:
EnvType:
Description: Environment type.
Type: String
AllowedValues: [dev, stg, prod]
ConstraintDescription: must specify dev, stg or prod.
Resources:
AppRunnerService:
Type: AWS::AppRunner::Service
Properties:
HealthCheckConfiguration:
HealthyThreshold: 1
Interval: 5
Path: "/"
Protocol: TCP
Timeout: 2
UnhealthyThreshold: 5
InstanceConfiguration:
Cpu: '1 vCPU'
Memory: '2 GB'
ServiceName: !Sub 'zio-simple-api-${EnvType}'
SourceConfiguration:
AuthenticationConfiguration:
AccessRoleArn:
Fn::ImportValue: zio-simple-api-access-role-arn
ImageRepository:
ImageConfiguration:
Port: '9000'
ImageIdentifier:
Fn::Join:
- ''
- - Fn::ImportValue: zio-simple-api-ecr-repository-uri
- ':1.0.0'
ImageRepositoryType: 'ECR'
AWS App Runnerにアクセスしてみよう
AWS App Runnerが作成されたらServiceUrlが払い出されているので、そのURLにアクセスしてアプリケーションが正常に動いているか確認してみます。
以下のコマンドを実行するとServiceUrlを確認できます。
aws apprunner list-services
{
"ServiceSummaryList": [
{
"ServiceName": "zio-simple-api-dev",
"ServiceId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"ServiceArn": "arn:aws:apprunner:ap-northeast-1:000000000000:service/zio-simple-api-dev/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"ServiceUrl": "XXXXXXXXXX.ap-northeast-1.awsapprunner.com",
"CreatedAt": "2022-12-17T02:26:10+09:00",
"UpdatedAt": "2022-12-17T02:26:10+09:00",
"Status": "RUNNING"
}
]
}
ServiceUrlを使って以下のようなcURLコマンドを書いてみましょう。
curl -X GET https://XXXXXXXXXX.ap-northeast-1.awsapprunner.com
{"message":"hello world!"}
{"message":"hello world!"}
が出力されていればOKです!
後片付け
AWSリソースが残っていると料金が発生するので削除しておきましょう。
aws cloudformation delete-stack --stack-name zio-simple-api-apprunner
aws cloudformation delete-stack --stack-name zio-simple-api-iam
ECRはイメージが残っているとCloudFormationのスタックの削除に失敗するため、AWS管理コンソールやAWS CLIで登録されているイメージを削除しておきましょう。
イメージの削除作業が完了したら以下を実行してください。
aws cloudformation delete-stack --stack-name zio-simple-api-ecr
最後に
sbt-native-packagerでDockerイメージをビルドしてApp Runnerを使ってデプロイしてしまえばサクッとアプリケーションを動かせるのは便利ですね!
Githubのリポジトリも作ってあるので興味がある方はAWSのアカウント情報を少し書き換えて試してみてください。
Discussion