🍪

AWS AppRunnerでzio-httpで実装したAPIを動かしてみる

2022/12/19に公開

普段、チームではScalaやAkkaを使ってAPIを開発してk8s上で動かすことが多いのですが、今回はサクッとzio-httpを使ってhello worldを返すAPIを実装してAWS AppRunnerで動かしてみようということで試してみます。

APIの実装

まずはライブラリやプラグインの設定です。

とりあえず、zioやzio-httpは2系を使ってます。

build.sbt
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から使用します。

plugins.sbt
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サーバーです。

Main.scala
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

ファイル内容

01_ecr.yml
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のイメージにアクセスするために使用するロールを作成します。

https://docs.aws.amazon.com/ja_jp/apprunner/latest/dg/security_iam_service-with-iam.html

実行コマンド

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

ファイル内容

02_iam.yml
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イメージを作成します。

https://github.com/sbt/sbt-native-packager

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にログインします。

https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login-password.html

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

ファイル内容

03_apprunner.yml
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のアカウント情報を少し書き換えて試してみてください。

https://github.com/ryskit/zio-simple-api

参考: https://zenn.dev/110416/articles/5beb6e34b8b4fd

Discussion