Miistin's Tech Blog

株式会社ミースチンの技術ブログ

AWS AppRunnerからECSのタスクを呼び出す

概要

前回に引き続きAWS関連の投稿になります。

miistin.hatenablog.com

前回はAppRunner (+Java + Micronaut) + CloudWatchの連携をしましたが、今回はECSのFargateタスクの連携を行います。

そんなわけで

今回の内容は以下にpushしてます。

github.com

今回も、Java側の内容は特には触れません。
ただMicronautはサーバーもバッチ処理も同じ感じで作れるので便利ですね。

ECS 側の設定

今回は、ECSのFargateを使ってのタスク処理を想定してるので、その設定を行います。

ソースコード

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "ecsTaskExecutionRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        },
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role_policy_attachment" "ecs_task_logging" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = aws_iam_policy.cloudwatch_logs_policy.arn
}

resource "aws_ecs_cluster" "my_batch_cluster" {
  name = "my-batch-cluster"
}

resource "aws_ecs_task_definition" "my_batch_task" {
  family                   = "my-batch-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn
  task_role_arn            = aws_iam_role.ecs_task_execution_role.arn

  container_definitions = jsonencode([
    {
      name      = "my-batch-container"
      image     = "${aws_ecr_repository.apprun-ecr-repository.repository_url}:latest"
      essential = true
      command = ["cli", "hello"]

      logConfiguration = {
        logDriver = "awslogs"
        options = {
          awslogs-group         = aws_cloudwatch_log_group.ecs_log_group.name
          awslogs-region        = data.aws_region.current.name
          awslogs-stream-prefix = "ecs"
        }
      }
    }
  ])
}

今回、DockerのイメージはAppRunnerと同じものを指定しており、コマンドを command = ["cli", "hello"] みたいにオーバーライドすることでタスク処理として対応するようにしてます。 もちろんそれぞれでDockerのイメージを作成するので問題ないです(むしろそっちが普通のやり方でしょうね)

AppRunnerから呼び出すための設定

AppRunnerからECSのタスクを呼び出すにはそれ相応の権限が必要なので、それも追加します。

ソースコード

# ECSタスクを実行するためのIAMポリシー
resource "aws_iam_policy" "ecs_run_task_policy" {
  name        = "AppRunnerECSRunTaskAccess"
  description = "Allows AppRunner to run ECS tasks"
  policy      = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "ecs:RunTask",
          "ecs:DescribeTasks",
          "ecs:ListTasks",
          "ecs:StopTask",
          "ecs:DescribeTaskDefinition"
        ]
        Resource = "arn:aws:ecs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:task-definition/*" // 必要に応じてリソースARNを修正
      }
    ]
  })
}

# AppRunnerがECSタスク実行ロールをパスするためのIAMポリシー
resource "aws_iam_policy" "pass_role_policy" {
  name        = "AppRunnerPassRolePolicy"
  description = "Allows AppRunner to pass the ECS task execution role"
  policy      = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = "iam:PassRole"
        Resource = aws_iam_role.ecs_task_execution_role.arn
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "pass_role_policy_attachment" {
  role       = aws_iam_role.apprunner_role.name
  policy_arn = aws_iam_policy.pass_role_policy.arn
}

resource "aws_iam_role_policy_attachment" "apprunner_ecs_run_task_policy_attachment" {
  role       = aws_iam_role.apprunner_role.name
  policy_arn = aws_iam_policy.ecs_run_task_policy.arn
}

こちら補足すると、こちらの ecs_run_task_policy はAppRunnerがECSのタスクを実行するために必要となる権限セットになります。
一方で、 pass_role_policy は、実行するタスクに特定のロールを付与するために必要な権限です。

あとはデプロイしてAppRunnerの特定のエンドポイントを呼び出せば、ECSのタスクが実行されるようになります。

補足:ECSのジョブ終了に関して

ECSのタスクは、基本的にはDockerが終了のシグナルを遅れば終了するはずなのですが、そのシグナルが何故かずっと送られないという自体がこの作成中に発生しました。 (Micronautを使ってるからなのかな)

その場合は、明示的にexitをコード上から呼び出してあげることでちゃんと終了するようです。

aws.amazon.com

AppRunner + CloudWatch Loggingの設定をTerraformで行う

概要

前回のブログの続きです。

miistin.hatenablog.com

前回はAppRunnerのデプロイを行うところの設定までだったので、今回は CloudWatch Loggingの設定をTerraformで行うようにしました。

ちなみに

AppRunnerでは、stdoutされたものは自動的にアプリケーションログとしてCloudWatch Logsに出力されるので、この設定は不要っちゃ不要です。

Application Log

ただ、デフォルトのアプリケーションログは、デプロイしてバージョンが変わるたびに新たなログストリームが作成されるので、ちょっと面倒です。 今回の設定をすることで、デプロイをしても同じログストリームに出力させることができるようになります。

そんなわけで、設定

github.com

前回の差分

Javaの部分は今回のブログの趣旨から外れるので割愛します(どれも似たようなものですし)。

AppRunnerのロール設定

CloudWatch Loggingへアクセスできるように、AppRunnerに付与するロールを以下のように設定します。

ソースコード

resource "aws_iam_role" "apprunner_role" {
  name               = "AppRunnerECRAccess"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect    = "Allow"
        Principal = {
          Service = ["build.apprunner.amazonaws.com", "tasks.apprunner.amazonaws.com"]
        }
        Action    = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_policy" "cloudwatch_logs_policy" {
  name        = "AppRunnerCloudWatchLogsAccess"
  description = "Allows AppRunner to write logs to CloudWatch"
  policy      = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
          "logs:DescribeLogGroups"
        ]
        Resource = "arn:aws:logs:*:*:*"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "apprunner_ecr_access_policy" {
  role       = aws_iam_role.apprunner_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess"
}

resource "aws_iam_role_policy_attachment" "apprunner_cloudwatch_logs_policy_attachment" {
  role       = aws_iam_role.apprunner_role.name
  policy_arn = aws_iam_policy.cloudwatch_logs_policy.arn
}

cloudwatch_logs_policy あたりがそれですね。

AppRunnerのインスタンスロールの設定

AWSでは、AWS内のサービスを利用する代表的な方法として以下が用意されています。

  • 環境変数AWSの認証情報を設定
    • AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY を設定することで利用できるようになります
    • いずれも機密情報なので取り扱い注意のものになります。使う場合はAWS Secrets Manager とかで管理する必要がありますね
  • IAMロールから自動取得
    • インスタンスにIAMロールを付与している場合、そこに設定された権限の中で自由にAPIを呼び出せるようになります

今回は IAMロール を設定することで、自動でCloudWatchにアクセスできるようにします。

ソースコード

// AppRunenr
resource "aws_apprunner_service" "sample" {
  service_name = "sample"

  source_configuration {
    image_repository {
      image_repository_type = "ECR"
      image_configuration {
        port             = "8080"
      }
      image_identifier = "${aws_ecr_repository.apprun-ecr-repository.repository_url}:latest"
    }
    auto_deployments_enabled = true
    authentication_configuration {
      access_role_arn = aws_iam_role.apprunner_role.arn
    }
  }

  instance_configuration {
    instance_role_arn = aws_iam_role.apprunner_role.arn
  }

  depends_on = [null_resource.deploy]
}

結論をいうと、この instance_configuration.instance_role_arn を設定することで、インスタンスに権限を与えることができます。

CloudWatchのロググループとログストリームの作成

ソースコード

フレームワークによっては自動で作ってくれたりもするようですが、terraformでも簡単に作れるので作っておきます。

resource "aws_cloudwatch_log_group" "apprunner_log_group" {
  name = "apperunner"
  retention_in_days = 7
}

resource "aws_cloudwatch_log_stream" "apprunner_log_stream" {
  name           = "api"
  log_group_name = aws_cloudwatch_log_group.apprunner_log_group.name
}

AWS AppRunner のデプロイをTerraformで行う

概要

Terraformを用いて、AWS ECRへのコンテナイメージのビルドとAppRunnerのデプロイを自動化します。
今回は以下の環境で行います。

  • Java 21
  • Micronaut
    • 標準でdockerビルドに対応してるのでこれを採用

AppRunnerについて

aws.amazon.com

AppRunnerとは、Webアプリケーションを簡単にデプロイし、オートスケーリングやロードバランシングや暗号化など、Webアプリケーション運用に必要な諸々のことを自動で行ってくれるサーバーレスサービスです。

他のクラウドの類似サービスは以下になります。

  • GCP: Croud Run
  • Azule : Azure Container Apps

正直、この手のサービスだと先発の Cloud Run が一番高機能なんですが、AppRunnerも既に商用のWebアプリケーションを作るのに充分な機能を提供しています。
(ただ、まだWebアプリケーションに特化しており、Cloud Run のようにバッチ処理を行うような使い方は不向きです)

この手のサービスの最大の特徴は、Dockerイメージをpushすることで自動でデプロイができる点でしょうか。
デプロイもローリングアップデートで徐々にトラフィックを変えてくれるという形をとるので、push後は何も気にすることなく鼻をほじってたらデプロイが完了します。

また、AppRunnerはソースコードをpushすることでもデプロイする「コードビルド」にも対応しています。

とはいえDockerイメージの方が汎用性が高いので、今回はDockerイメージをpushしてデプロイする手順を踏みます。

というわけで、実装

実装内容は以下に公開しています。

github.com

サーバー側はMicronautのプロジェクトを作成した時にできる内容をちょろっと変更しただけのもので、メインはterraformの設定になります。

以下のようにするだけでAppRunnerがデプロイされます。
(要 terraform インストール、 aws cli プロファイルの設定)

$ cd ./terraform/apprunner/dev
$ terraform apply

一度実行して環境が構築されたあとは、次からはソースコードに差分がある時だけpushされ、差分がないと何もしないということをterraform側が自動でやってくれます。

やってること

ECRとAppRunnerのデプロイ設定

DockerのイメージをpushするためのECRの設定を行っています。

resource "aws_ecr_repository" "apprun-ecr-repository" {
  name = "my-ecr-repository"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

name は外だしした方がよさそうですけど、とりあえず決め打ちです。
次にAppRunnerの設定は以下で行っています。

# ロールの設定
resource "aws_iam_role" "apprunner_role" {
  name               = "AppRunnerECRAccess"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect    = "Allow"
        Principal = {
          Service = "build.apprunner.amazonaws.com"
        }
        Action    = "sts:AssumeRole"
      }
    ]
  })
}

# ロールのポリシー設定
resource "aws_iam_role_policy_attachment" "apprunner_ecr_access_policy" {
  role       = aws_iam_role.apprunner_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess"
}

# AppRunnerの設定
resource "aws_apprunner_service" "sample" {
  service_name = "sample"

  source_configuration {
    image_repository {
      image_repository_type = "ECR"
      image_configuration {
        port             = "8080"
      }
      image_identifier = "${aws_ecr_repository.apprun-ecr-repository.repository_url}:latest"
    }
    auto_deployments_enabled = true
    authentication_configuration {
      access_role_arn = aws_iam_role.apprunner_role.arn
    }
  }

  depends_on = [null_resource.deploy]
}

特にそんなに言うこともないですが、その後ECRへのpushを行うタスク ( null_resource.deploy )のあとに動くようにするよう、 depends_on の設定をしています。

DockerのビルドとECRへのpush

次に、以下でDockerのビルドを行っています。
今回は Micronaut を利用しているため、標準で入っている dockerBuild の gradleコマンドを用いてビルドしています。

data "archive_file" "deploy" {
  type        = "zip"
  output_path = "${path.module}/../../../build/hello.zip"
  source_dir  = "${path.module}/../../../src"
  # .dockerignore 相当の指定を行う。
  excludes = setunion(
    fileset("${path.module}/../../..", "test/**/*"),
  )
}

resource "null_resource" "deploy" {
  provisioner "local-exec" {
    command = <<BASH
      cd ../../../
      echo "Create Docker Image"
      ./gradlew dockerBuild

      # 最新のトークンを取得
      LOGIN_PASSWORD=$(aws ecr get-login-password --region $AWS_REGION)
      if [ $? -ne 0 ]; then
        echo "Failed to get ECR login password"
        exit 1
      fi

      echo "Deploy Docker Image to ECR"
      # ECRにログイン
      echo $LOGIN_PASSWORD | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
      docker tag ${var.imagename}:latest $REPOSITORY_URL:latest
      docker push $REPOSITORY_URL":latest"
    BASH

    environment = {
      AWS_ACCOUNT_ID = data.aws_caller_identity.current.account_id
      AWS_REGION = data.aws_region.current.name
      REPOSITORY_URL = aws_ecr_repository.apprun-ecr-repository.repository_url
    }
  }

  triggers = {
    sha256 = "${data.archive_file.deploy.output_sha}"
  }
}

archive_file でzipファイルを作ってますが、これは単にコードの差分をsha256で検知するために利用してます。こちらの記事:Terraformでコンテナイメージのビルドからデプロイまでを行う(AWS編)を参考にさせてもらいました。

(docker_registry_imageを使う方がスマートだなと思ったんですが、ビルドも gradleを使ってるし素直にコマンドでpushまでするようにしてます)

環境の設定

terraformは未だに環境ごとに設定を分けるベストプラクティスがよくわかってないのですが(おすすめとかあれば教えてほしいです)、ぼくは基本的に基本的な処理を全部 common ディレクトリに入れ、環境ごとのディレクトリでそのモジュールを呼び出すという形で実現してます。
(AWSの場合は環境ごとにディレクトリを分けたりはせずに、実行の際都度プロファイルを入力する感じで運用する方がいいのかもしれないですが)

というわけで、 terraform/apprunner/dev というディレクトリを作り、そこで環境設定を行っています。

locals {
  profile = "admin"
}

provider "aws" {
  region = "ap-northeast-1" 
  profile = local.profile
}

# 処理の内容を呼び出す
module "common" {
  source = "../common"
}

Cloud Run + Go で Webサーバーを作成する

Cloud Run + Go で Webサーバーを作成する

弊社では、バックエンド側のシステム作成の際には主に Google Cloud Platform (GCP) を利用しています。
その中でも簡単な設定でAPIサーバーを作ることができる Google App Engine (GAE) をよく使っているのですが、Cloud Run でも同様にオートスケーリングされるサーバーを簡単に作ることができるので、その方法をまとめます。

Cloud Run とは

詳細は公式サイトを見るのが一番かと思います。

cloud.google.com

すごくざっくりいうと、以下の特徴があります。

  • Docker とかのコンテナを実行できるサービスです
  • サービス (サーバーみたいにしばらく常駐する) と ジョブ (実行したらすぐ終了する) を選ぶことができます
  • オートスケーリング (負荷に応じて自動で冗長化して増強したり減らしたりする) に対応しています
  • どれくらいのメモリやCPUを使うかなどを指定することができます
  • 料金はCPU/メモリの利用 (秒単位) とリクエスト数 (100万単位)

こちらは 実行される Docker イメージを用意すればいいだけで、リクエストの負荷などもあまり気にする必要がないので楽に運用することができます。
また、AWS や Azure にも、それぞれ AWS App Runner や Azure Container Apps のように同じ思想のサービスが存在しているそうですが、この分野では Cloud Run がフロントランナーだそうで、一番機能が高いそうです(他を使ったことがないのでどれくらい違うのか知らないのですが…)。

GCP内の類似サービス

GCPには Cloud Run の他に、 Google Kubaernetes Engine (GKE) や Google App Engine (GAE) も存在します。
どういう部分に違いがあるのかもざっくりと記載します。

Google Kubaernetes Engine (GKE)

  • 同じように Docker コンテナを管理・デプロイし、オートスケールができる特徴があります
  • GKE は複数のコンテナを組み合わせて大規模なシステムを構築・管理できることに最大の強みがありますが、 Cloud Run はそれよりもシンプルです
    • とはいえ、Cloud Run でもマイクロサービス設計で複数のコンテナを組み合わせて大規模なシステムを作ることはできます
  • GKE は、かなり細かいチューニングやカスタマイズができますが、その分管理が複雑になります。 Cloud Runはインフラ部分はすべて任せられるためシンプルですが、柔軟性には限界があります

Google App Engine (GAE)

  • 同様にフルマネージド型のサービスのため、スケーリングや証明書などインフラ面はすべて任せられる点は GAE も Cloud Run も同じです
  • GAE はコードと設定ファイルさえ用意すれば簡単にデプロイすることが出来、Cloud Run より楽にサーバー環境を作ることができます
  • GAEのサービス分類は PaaS (Platform as a service) であり、使用できる言語にも制限があったりと Cloud Run より自由度が低いです

Cloud Run は GAE と GKE の間(GAE寄り) に位置付けられてる感じでしょうか。
GAEの楽さを兼ね備えつつ、コンテナを使うことで自由度の高さも持ち合わせています。

簡単な オリジンサーバーを作成する

何か簡単なサンプルをということで、CDNのオリジンサーバーを Cloud Run で作ってみようと思います。
(CDNの設定は置いておいて、ひとまずはオリジンサーバー部分の作成だけ)

構造はこんな感じですね

Cloud RunでOrigin サーバーを作成する - Miistin&#x27;s Tech Blog

GCS と Cloud Run は Cloud Storage FUSE を利用して同期させ、CDN側からリクエストがあればそのリソースを返却することを想定します。
実際に運用するならオリジンサーバーを守るためにアクセス制限なども設ける必要がありますが、今回は Cloud Run の実装部分だけフォーカスします。

構成

こんな感じになります。

<Project Root>
├ src
│ └ main.go
├ Dockerfile
├ go.mod
├ go.sum
└ run.sh

Dockerfile では go のイメージから gcsfuse をインストールしたイメージを作成します。
Cloud Run のデフォルトポートは 8080 なので、それも指定しています。

Dockerfile

FROM golang:1.20.13

WORKDIR /go/src/app

RUN apt-get -qqy update && apt-get install -qqy curl apt-transport-https lsb-release gnupg sudo && \
        export GCSFUSE_REPO="gcsfuse-$(lsb_release -c -s)" && \
        echo "deb https://packages.cloud.google.com/apt $GCSFUSE_REPO main" > /etc/apt/sources.list.d/gcsfuse.list && \
        curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
        apt-get update && apt-get install -y gcsfuse

COPY ./go.mod /go/src/app
COPY ./go.sum /go/src/app
COPY ./src /go/src/app/src
COPY run.sh /go/src/app
RUN chmod +x /go/src/app/run.sh

ENV MOUNT_DIR /go/src/app/gcs

RUN go mod tidy
EXPOSE 8080

CMD ["/go/src/app/run.sh"]

run.sh

run.sh は以下の感じです。 バックグラウンド上で gcsfuse を実行した後、サーバーを立ち上げています。

#!/bin/bash

mkdir $MOUNT_DIR
nohup gcsfuse --implicit-dirs --foreground --debug_gcs --debug_fuse [バケット] $MOUNT_DIR &

go run src/main.go

src/main.go

マウントされたリソースを読んでるだけですね。

package main

import (
    "fmt"
    "log"
    "os"
    "regexp"
    "strings"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    e := echo.New()
    e.Use(middleware.Recover())
    e.Use(middleware.Logger())

    e.GET("/:dir/:path", func(c echo.Context) error {
        path := c.Param("path")

        r, _ := regexp.Compile(`.*\.\w+$`)
        if !r.MatchString(path) {
            return c.String(404, "Not Found")
        }

        extR, _ := regexp.Compile(`\.\w+$`)
        extension := strings.TrimPrefix(extR.FindString(path), ".")

        if extension == "jpg" {
            extension = "jpeg"
        }

        var contentType string
        switch extension {
        case "jpeg":
            contentType = "image/jpeg"
        case "png":
            contentType = "image/png"
        case "gif":
            contentType = "image/gif"
        case "webp":
            contentType = "image/webp"
        case "svg":
            contentType = "image/svg+xml"
        case "bmp":
            contentType = "image/bmp"
        case "ico":
            contentType = "image/vnd.microsoft.icon"
        case "json":
            contentType = "application/json"
        case "xml":
            contentType = "application/xml"
        case "pdf":
            contentType = "application/pdf"
        case "txt":
            contentType = "text/plain"
        default:
            contentType = "application/octet-stream"
        }

        ls, err := os.ReadFile(fmt.Sprintf("/go/src/app/gcs/%s/%s", c.Param("dir"), c.Param("path")))
        if err != nil {
            return c.String(404, "Not Found")
        }

        return c.Blob(200, contentType, ls)
    })

    log.Fatal(e.Start(fmt.Sprintf(":8080")))
}

Artifact Registry のレポジトリ作成

先述の通り Cloud Run はコンテナを扱うため、まずはそのイメージをpushする必要があります。
GCPには Artifact Registry というサービスがあり、そこでイメージファイルを登録することができるので、そのためのレポジトリを作成します。

# Artifact Registry を使うための認証情報設定
# 下記は us-central1 のリージョンに配置する場合
$ gcloud auth configure-docker us-central1-docker.pkg.dev 

# レポジトリの作成
$ gcloud artifacts repositories create cloudrun-sample --location=us-central1 --repository-format=docker --project=[PROJECT_ID]

以下のように作成されます。

Google Artifact Registry にレポジトリを作成

今回 us-central1 のリージョンで作成をしました。
Cloud Run をデプロイする際には別リージョンの指定もできるので、一つのリージョンに統一するという形で問題ないかと思います。

レポジトリにイメージをpushする

次に、作成した Docker イメージをビルドしてpushします。

# docker のビルド
$ docker build -t us-central1-docker.pkg.dev/[PROJECT_ID]/cloudrun-sample/cloudrun-image:tag1 .

# dockerイメージのpush
$ docker push us-central1-docker.pkg.dev/[PROJECT_ID]/cloudrun-sample/cloudrun-image:tag1

うまくいくと、以下のように作成されます。

Artifact Registry でレポジトリを作成 : Miistin&#x27;s Tech Blog

サービスアカウントの作成

次に、 Cloud Run 実行時に付与するサービスアカウントを作成します。 今回はGCSにアクセスをするだけなので、 roles/storage.objectViewer だけを付与したサービスアカウントを作成します。

# 変数
EXPORT PROJECT_ID=[プロジェクトID]
EXPORT SERVICE_ACCOUNT_ID=[サービスアカウント名] #例: cloudrun-sample とか

# サービスアカウントの作成
$ gcloud iam service-accounts create ${SERVICE_ACCOUNT_ID} \
  --description="Cloud Run Sample" \
  --display-name="CloudRunSample" \
  --project ${PROJECT_ID}

# 必要なロールの設定
$ gcloud projects add-iam-policy-binding ${PROJECT_ID} \
   --member="serviceAccount:${SERVICE_ACCOUNT_ID}@${PROJECT_ID}.iam.gserviceaccount.com" \
   --role="roles/storage.objectViewer"

デプロイ

最後にデプロイをします。

$ gcloud run deploy cdnservice \
    --image us-central1-docker.pkg.dev/${PROJECT_ID}/cloudrun-sample/cloudrun-image:tag1 \ # さっきpushしたイメージファイル
   --platform=managed \
   --project=[PROJECT_ID]  \
   --region=asia-northeast1 \
   --service-account [サービスアカウント]@${PROJECT_ID}.iam.gserviceaccount.com # さっき作成したサービスアカウント

Deploying container to Cloud Run service [cdnservice] in project [xxxx] region [asia-northeast1]
✓ Deploying... Done.
  ✓ Creating Revision...
  ✓ Routing traffic...
Done.
Service [cdnservice] revision [cdnservice-00003-jeq] has been deployed and is serving 100 percent of traffic.
Service URL: https://cdnservice-xxxxxx-an.a.run.app

デプロイ完了後、GCPコンソール上では以下のように表示されます。

CloudRun デプロイ完了後 : Miistin&#x27;s Tech Blog

アクセスすると、きちんとGCSに配置した画像を読み込むことができました。

Cloud Run で画像を表示する : Miistin&#x27;s Tech Blog

補足: Cloud Storage ボリューム でもいいかも?

Cloud Run では、そもそも Cloud Storageをボリュームとしてマウントすることができるそうです。

cloud.google.com

やってることは、今回手動で行ったやり方と同じみたいなのでこれでやる方がよりシンプルに作れるかもですね。
この存在知ったのが作った後だったのですが、今度使ってみようかと思います。

まとめ

今回は Cloud Run 部分のみにフォーカスを当てて使い方を紹介しました。 先述した通り、実際にはオリジンサーバーへのアクセス制限が必要な他CDNの用意など考えないといけないことも多いですが、AppEngineと違ってDockerイメージをそのままサーブできるのでやれることはぐんと広がりそうです。

技術ブログを始めます

はじめまして、株式会社ミースチンの小田です。
2023年3月に会社を設立し、少しずつ落ち着いてきたので技術ブログを不定期に書いていこうと思います。
なお、Qiitaにもちょくちょく書いたりしているのでよければそちらも見てみて下さい。

qiita.com

今後、Qiitaは雑多に備忘録的な記事やポエムを書き、こちらでは カテゴリなどでまとめた内容を書いていこうと思います。

ちなみに、業務では主に以下を取り扱ってます。なので、それらの話題が主になります。