💸

ECS on FargateのCPUアーキテクチャをarm64に移行し、ECSコストを約50%に削減しました

に公開

はじめに

こんにちは。ランサーズ株式会社でSREをしているtkです。

今回は、弊社のアプリケーション「ランサーズ」について、arm64移行によってECS費用を削減した際のお話をさせていただきます。

※ 記事の中で例を記載させていただいておりますが、実際に変更した内容の抜粋となっているため
あくまで参考例にしていただけますと幸いです。

前提

  • インフラの構成管理にはTerraformを利用
  • アプリケーションは、ECS on Fargateでホスト
  • コンテナイメージは、ECRで管理
  • デプロイは、GitHub Actions経由で実行

方針

デプロイフローから逆算して、下記の変更を加える方針で進めました。

  • Dockerfileに記載のライブラリについて、amd64, arm64のマルチプラットフォームでビルドできるように修正
  • GitHub Actionsにて、amd64, arm64のマルチプラットフォームイメージを作成するよう変更
  • ECSのタスク定義にて、CPUアーキテクチャの設定をamd64->arm64に変更

手順

Dockerfileの修正

amd64でのみインストールされていたライブラリについて、arm64とマルチプラットフォームに対応した形でインストールできるように修正します。

ライブラリがマルチアーキテクチャに対応している場合、パッケージマネージャ経由でインストールする方法が良いかと思いますが
今回はuname -mを利用して、アーキテクチャごとにインストールする方法をご紹介します。
(※ ライブラリのリリースノートを確認し、amd64, arm64のどちらにも対応していることを確認してください)

dd-trace-phpのライブラリをマルチアーキテクチャに対応した形でインストールする方法です。

# uname -m を利用して、実行環境のアーキテクチャを取得
RUN dnf -y install https://github.com/DataDog/dd-trace-php/releases/download/1.11.0/datadog-php-tracer-1.11.0-1.$(uname -m).rpm \

タスク定義の修正

terraformで管理している、ECSのタスク定義を修正します。

resource "aws_ecs_task_definition" "app" {
  family                   = "app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  task_role_arn            = "arn:aws:iam::${var.aws_account_id}:role/ecsTaskExecutionRole"
  execution_role_arn       = "arn:aws:iam::${var.aws_account_id}:role/ecsTaskExecutionRole"
  cpu                      = xxxx
  memory                   = xxxx
  container_definitions    = file("files/task_definitions/app.json")

  # cpu_architectureをarm64に設定
  runtime_platform {
    operating_system_family = "LINUX"
    cpu_architecture        = "ARM64"
  }
}

デプロイワークフローの修正

arm64対応前のデプロイワークフローですが、主に下記3ステップで構成されていました。
今回のarm64対応では、2.のDockerイメージのビルドステップを修正しました。

  1. Frontendのデプロイ
  2. Backend用Dockerイメージのビルド(amd64)
  3. DockerイメージをECSにデプロイ

マルチプラットフォームイメージをビルドするように、デプロイワークフローを修正します。
具体的には、下記の修正を行いました。

  • matrixを利用して、amd64とarm64のイメージを並列でビルドする
  • docker buildx imagetools createを利用して、マルチプラットフォームイメージをビルドする

(※ 変更箇所付近のみ抜粋しています)

  docker_build:
    runs-on: ${{ matrix.runner }}
    # matrixを利用して、amd64とarm64のイメージを並列でビルドする
    strategy:
      fail-fast: true
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
            arch: amd64
          - platform: linux/arm64
            runner: linux-arm64
            arch: arm64
    steps:
      - uses: actions/checkout@v4.2.1
        with:
          ref: ${{ inputs.branch }}

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4.0.0
        with:
          role-to-assume: ${{ secrets.AWS_PRE_IAM_ROLE_ARN_GITHUB_ACTION_OIDC_PROVIDER }}
          aws-region: ${{ env.AWS_REGION }}
          mask-aws-account-id: no

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1
        with:
          mask-password: 'true'

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3.0.0

      - name: Build and Push app image
        id: build-push-app
        uses: docker/build-push-action@v5
        with:
          context: .
          file: docker/dev/app/Dockerfile
          push: true
          platforms: ${{ matrix.platform }}
          tags: |
            ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPO_NAME }}:${{ inputs.branch }}-${{ matrix.arch }}
            ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPO_NAME }}:latest-${{ matrix.arch }}
          cache-from: type=gha,scope=${{ github.ref }}-app-${{ matrix.arch }}
          cache-to: type=gha,mode=max,scope=${{ github.ref }}-app-${{ matrix.arch }}
    create_multiplatform_image:
      - name: Create app manifest list and push
        run: |
          docker buildx imagetools create \
            -t ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPO_NAME }}:${{ inputs.branch }} \
            -t ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPO_NAME }}:latest \
            ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPO_NAME }}:${{ inputs.branch }}-${{ env.ARCH_AMD64 }} \
            ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPO_NAME }}:${{ inputs.branch }}-${{ env.ARCH_ARM64 }}

結果

先月(7/1~7/28)と今月(8/1~8/28)でECSの平均コストを比較したところ、54%ほどコストが削減できていることを確認しました 🥳

ECS費用のグラフ(7月から8月にかけて)

8/7にarm64対応をデプロイしたのですが、
デプロイ以降大きく費用が下がっていることが確認いただけると多います。

一般的にはarm64対応によるコスト削減効果は20%ほどと言われていますが、予想以上の効果が出ることとなりました。
(心当たりはないですが、arm64対応の要因もある可能性はあります)

課題

  • アプリケーションのレイテンシーが(若干)増加傾向にあること
  • マルチプラットフォームビルドによる、Github Cacheの圧迫
  • amd64, arm64のビルドアーティファクトの削除方法
    • ECRのLifeCycle Policyで削除していく方針を採用しましたが、GitHub Actionsにてデプロイ後すぐに削除する方法もあり、どちらがベストか決めかねています

特にGitHub Cache周りは最適化があまりできていないので、腰を据えて対応していきたいです。

終わりに

ここまでご覧いただきありがとうございました。
ECS on Fargate環境で、arm64へのアーキテクチャ変更をする際の参考になれば幸いです。

ランサーズ株式会社

Discussion