BIGLOBEの「はたらく人」と「トガッた技術」

やらなきゃ損!? 円安時代の AWS Graviton2 移行

AWS Fargate コンテナの Graviton2 移行について実例を交えてご紹介します。

こん**は。 新型コロナウイルス感染症は5類感染症に移行となりましたが、読者の皆様はお変わりなくお過ごしでしょうか。お久しぶりの投稿となります、プロダクト技術本部の江角です。 前回執筆させていただきましたGitログの記事では「ほぼフルリモート!」とお伝えしていましたが、近況に変化がありましたので少しお話できれば、と思います。

BIGLOBEは4月より組織改編等もあり、「リアルでの会話、議論を重視したい」という流れのもと、今までは疎らだったオフィスに人が戻って来つつあります。 私が今所属しているグループでは「会議が被る曜日はメンバーで出社を揃えよう」という試みも実施していたりします。 『ほぼフルリモートだと聞いていたのに全然違った!😡』ということが無いよう、あくまで直近のご報告とさせていただきます。

さて本題ですが、 以前のチーム内で試行錯誤し、AWS Fargate コンテナを Graviton2 へ移行したノウハウを記事として公開します。 Graviton2 がわかった気になる基本から、ちょっと凝った応用まで、どどーーーん!とボリューミーな内容でお届けします。

目的に応じて実践できそうな箇所がありましたら是非やってみてください!

想定読者

  • AWS Fargate の料金を安くしたい方
  • AWS CodeBuild を用いてビルドしている方
  • Java17 プロジェクトで、まだ Graviton2 へ移行していない方

AWS Fargate コンテナを Graviton2 へ移行させてみた

本セクションでは、Graviton2 へ移行するにあたっての前知識やメリットについて記載します。実践編は後ほど。

Graviton2 とは

AWS が独自開発した ARM ベース、第二世代のプロセッサです。 ARM に聞き馴染みの無い方も、お持ちのスマートフォンやタブレットに利用されている CPU は ARM ベースで、小型であるにもかかわらず高性能で省電力なことが特徴とされています。 ちなみに、最近価格高騰でなかなか手が出せない Apple 社の M2 チップ搭載 MacBook も、Apple 社が独自開発した ARM ベースのプロセッサです。

本文にて用語が混在するため、 以下ざっくりとした理解でお読みください。

従来型の CPU
x86_64 ≒ AMD64 ≒ Intel64
コスト効率が良い CPU (例: AWS Graviton2, Apple M2)
AArch64 ≒ ARM64

Graviton2 のメリット

ズバリ「コスパが良い」ということです。

具体的な数字としては 20% 低費用で、コストパフォーマンスが最大 40% 向上するとのことです。 aws.amazon.com

コンテナ化などのサーバーレス化が主流となっている中、AWS Fargate は継続して動かすユースケースが多いので、お財布に優しいだけでなく性能まで向上してくれるのであれば今すぐ導入しない手はありませんね。

実運用で判明したコスト削減効果

私達のチームでは 2022年9月に AWS Fargate コンテナを Graviton2 へ移行しました。 月ごとの Amazon ECS 利用料金 をまとめたものは以下のグラフの通りです。

一部飛びぬけている月もありますが、平均約25%のコスト削減が出来ているように見えます。

円安の影響で 110円/$ から 137円/$ へ上がったとしても、 Graviton2 へ移行することで、今まで通りのコストで運用できますね。

※私達のチームにおける費用軽減効果であり、その効能を保証するものではありません!

実践編

ここからは実践編です。 実際に動かすまでを目的としていますので、ソースコードや画像が多くなります。

一部、公式イメージを利用することで対応が不要になったセクションも敢えて記載しています。Corretto 21 に応用できる可能性もあるため、是非目を通して頂けると嬉しいです。

【初級】タスク定義 を AArch64 対応させる

まずは、Amazon ECS で使用するタスク定義を AArch64 対応させましょう。 マネジメントコンソールから操作する場合、Linux/ARM64 を選択するだけで完了です。

terraformでタスク定義を管理されている場合は、aws_ecs_task_definition に 以下runtime_platformを追加して terraform applyしてください。

resource "aws_ecs_task_definition" "graviton2_test" {
 runtime_platform {
    operating_system_family = "LINUX"
    cpu_architecture        = "ARM64"
  }
}

registry.terraform.io

AWS Fargateの設定としては以上で、一応動きます。 でも、AArch64 に対応したコンテナイメージを用意しないといけないのでそれは【中級】で記載します。

【中級】AWS Codebuild で AArch64 イメージをビルドする

続いて、上記作成した AArch64 対応したタスク定義で動かす AArch64 対応のイメージを AWS Codebuild で作れるようにしましょう。

新規、及び既存のビルドプロジェクトより、 イメージを aws/codebuild/amazonlinux2-aarch64-standard:3.0 に設定すれば完了です。

⚠️最新のイメージとバージョンについては以下の AWS ドキュメントを参照してください。 docs.aws.amazon.com

マネジメントコンソールから操作するとこんな感じです。

terraformでビルド設定を管理されている場合は、aws_codebuild_projectenvironment.image を AArch64 対応イメージに、typeARM_CONTAINERに変更し、 terraform applyしてください。

resource "aws_codebuild_project" "aarch64_build" {
  ・・・
  environment {
    compute_type = "BUILD_GENERAL1_SMALL"
    image        = "aws/codebuild/amazonlinux2-aarch64-standard:3.0"
    type         = "ARM_CONTAINER"
  }
  ・・・
}

registry.terraform.io

【中級】の内容は以上です。 Amazon ECS で動かすためのコンテナイメージが作れたので、これで一通りの運用は可能となります。

【上級】AArch64 の Corretto17 イメージ でビルドする

2023年5月現在、AWS CodeBuild ランタイムで AArch64 の Corretto 17 イメージが使えるようになりました。 docs.aws.amazon.com

2022年9月時点で、Corretto 17 は x86_64 対応イメージしか利用できなかったため、 Java17 を開発で使っていた私達のチームでは AArch64 対応 Corretto 17 を自作し、Amazon ECR に登録。作成した AArch64 対応 Corretto 17 をランタイムに用いて AWS CodeBuild で Java17 のプロジェクトをビルド、デプロイしていました。

⚠️ここでは当時の手法をお伝えしますが、公式イメージが利用可能な場合は、AWS CodeBuild の設定からマネージド型イメージでAmazon Linux 2 AArch64 standard:3.0を設定すれば問題ありません。 【上級】の以降で説明する、イメージを自作したりカスタムイメージを設定したりという手順は不要となります。

全体の流れ

私達のチームで行っているデプロイ構成はこんな感じです。

  1. 事前準備として AArch64 の Corretto 17 を作成し、Amazon ECR (runtime) に push しておきます。
  2. AWS CodeBuild ではカスタムイメージ機能を用い、①で作成した AArch64 Corretto 17 を用いて Java17 の AArch64 イメージをビルド。イメージを Amazon ECR (application) に pushします。イメージの definition.json を Amazon S3 に格納します。
  3. Amazon S3 に definition.json が格納されたことを契機として AWS CodePipeline が走り、Amazon ECR (application) から 先ほど push したイメージを Amazon ECS の コンテナに デプロイして完了です。

ここでは AArch64 対応 Corretto 17 でビルドをするにあたって修正が必要な、①と②に絞って当時の内容をそのまま記載します。

1. AArch64 の イメージを作成する

①の前準備です。 AWS 公式の aws/aws-codebuild-docker-images より aws/codebuild/amazonlinux2-aarch64-standard:2.0 のイメージを作成します。

aws/aws-codebuild-docker-images リポジトリをクローン、 x86_64 の PC で AArch64 イメージ を作成するために docker buildx build コマンドでビルドしていきます。

## リポジトリをクローン
$ git clone git@github.com:aws/aws-codebuild-docker-images.git

## ビルド
$ docker buildx build --platform linux/arm64 al2/aarch64/standard/2.0 -t codebuild-runtime:al2-aarch64-standard-2.0 --load

⚠️PC スペックにも依存しますが、私達のチームではイメージ完成まで16時間程要し、 システムと Docker が使用するディスクに 20GB 程度の空きが必要でした。

続いて、作成したイメージを ECR に push しておきます。 詳細は省きますが、************ という AWS アカウントID に対して、事前に codebuild-runtime という Amzon ECR リポジトリを作成しておき、ビルドしたイメージを push する形となります。

## Amazon ECR login
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ************.dkr.ecr.ap-northeast-1.amazonaws.com

## tagging
$ docker tag codebuild-runtime:al2-aarch64-standard-2.0 \
>       ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:al2-aarch64-standard-2.0

## push
$ docker push ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:al2-aarch64-standard-2.0

2. Corretto 17 対応させて、再度イメージを作る

1. で作成したイメージは Corretto 11 までしか入っていません。 そのため、1. で作成したイメージに、Corretto 17 を入れて再度イメージを作成するというのがここでやりたいことです。

まずは、任意のディレクトリ配下に Dockerfileと runtimes.yml を作成します。 今回は etc/docker ディレクトリ配下に作成するものとして記載します。

etc/docker/Dockerfile(クリックで展開)

FROM ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:al2-aarch64-standard-2.0

ENV JAVA_17_HOME="/usr/lib/jvm/java-17-amazon-corretto.aarch64" \
    JDK_17_HOME="/usr/lib/jvm/java-17-amazon-corretto.aarch64" \
    JRE_17_HOME="/usr/lib/jvm/java-17-amazon-corretto.aarch64"

RUN set -x \
    # Install Amazon Corretto 17
    # Note: We will use update-alternatives to make sure JDK11 has higher priority for all the tools
    && export GNUPGHOME="$(mktemp -d)" \
    && curl -fL -o corretto.key https://yum.corretto.aws/corretto.key \
    && gpg --batch --import corretto.key \
    && gpg --batch --export --armor '6DC3636DAE534049C8B94623A122542AB04F24E3' > corretto.key \
    && rpm --import corretto.key \
    && rm -r "$GNUPGHOME" corretto.key \
    && curl -fL -o /etc/yum.repos.d/corretto.repo https://yum.corretto.aws/corretto.repo \
    && grep -q '^gpgcheck=1' /etc/yum.repos.d/corretto.repo \
    && yum update -y \
    && yum install -y java-17-amazon-corretto-devel \
    && (find /usr/lib/jvm/java-17-amazon-corretto.x86_64 -name src.zip -delete || true) \
    && yum install -y fontconfig \
    && yum clean all \
    && for tool_path in $JAVA_HOME/bin/*; do \
          tool=`basename $tool_path`; \
          update-alternatives --install /usr/bin/$tool $tool $tool_path 10000; \
          update-alternatives --set $tool $tool_path; \
        done

COPY runtimes.yml /codebuild/image/config/runtimes.yml

ENTRYPOINT ["dockerd-entrypoint.sh"]

etc/docker/runtimes.yml(クリックで展開)

version: 0.1

runtimes:
  android:
    versions:
      28:
        requires:
          java: ["corretto8"]
        commands:
          - echo "Installing Android version 28 ..."
      29:
        requires:
          java: ["corretto8"]
        commands:
          - echo "Installing Android version 29 ..."
  java:
    versions:
      corretto17:
        commands:
          - echo "Installing corretto(OpenJDK) version 17 ..."

          - export JAVA_HOME="$JAVA_17_HOME"

          - export JRE_HOME="$JRE_17_HOME"

          - export JDK_HOME="$JDK_17_HOME"

          - |-
            for tool_path in "$JAVA_HOME"/bin/*;
             do tool=`basename "$tool_path"`;
              if [ $tool != 'java-rmi.cgi' ];
              then
               rm -f /usr/bin/$tool /var/lib/alternatives/$tool \
                && update-alternatives --install /usr/bin/$tool $tool $tool_path 20000;
              fi;
            done
      corretto11:
        commands:
          - echo "Installing corretto(OpenJDK) version 11 ..."

          - export JAVA_HOME="$JAVA_11_HOME"

          - export JRE_HOME="$JRE_11_HOME"

          - export JDK_HOME="$JDK_11_HOME"

          - |-
            for tool_path in "$JAVA_HOME"/bin/*;
             do tool=`basename "$tool_path"`;
              if [ $tool != 'java-rmi.cgi' ];
              then
               rm -f /usr/bin/$tool /var/lib/alternatives/$tool \
                && update-alternatives --install /usr/bin/$tool $tool $tool_path 20000;
              fi;
            done
      corretto8:
        commands:
          - echo "Installing corretto(OpenJDK) version 8 ..."

          - export JAVA_HOME="$JAVA_8_HOME"

          - export JRE_HOME="$JRE_8_HOME"

          - export JDK_HOME="$JDK_8_HOME"

          - |-
            for tool_path in "$JAVA_8_HOME"/bin/* "$JRE_8_HOME"/bin/*;
             do tool=`basename "$tool_path"`;
              if [ $tool != 'java-rmi.cgi' ];
              then
               rm -f /usr/bin/$tool /var/lib/alternatives/$tool \
                && update-alternatives --install /usr/bin/$tool $tool $tool_path 20000;
              fi;
            done
  golang:
    versions:
      1.12:
        commands:
          - echo "Installing Go version 1.12 ..."
          - goenv global  $GOLANG_12_VERSION
      1.13:
        commands:
          - echo "Installing Go version 1.13 ..."
          - goenv global  $GOLANG_13_VERSION
      1.14:
        commands:
          - echo "Installing Go version 1.14 ..."
          - goenv global  $GOLANG_14_VERSION
  python:
    versions:
      3.9:
        commands:
          - echo "Installing Python version 3.9 ..."
          - pyenv global  $PYTHON_39_VERSION
      3.8:
        commands:
          - echo "Installing Python version 3.8 ..."
          - pyenv global  $PYTHON_38_VERSION
      3.7:
        commands:
          - echo "Installing Python version 3.7 ..."
          - pyenv global  $PYTHON_37_VERSION
  php:
    versions:
      7.4:
        commands:
          - echo "Installing PHP version 7.4 ..."
          - phpenv global $PHP_74_VERSION
      7.3:
        commands:
          - echo "Installing PHP version 7.3 ..."
          - phpenv global $PHP_73_VERSION
  ruby:
    versions:
      2.6:
        commands:
          - echo "Installing Ruby version 2.6 ..."
          - rbenv global $RUBY_26_VERSION
      2.7:
        commands:
          - echo "Installing Ruby version 2.7 ..."
          - rbenv global $RUBY_27_VERSION
  nodejs:
    versions:
      10:
        commands:
          - echo "Installing Node.js version 10 ..."
          - n $NODE_10_VERSION
      12:
        commands:
          - echo "Installing Node.js version 12 ..."
          - n $NODE_12_VERSION
  docker:
    versions:
      18:
        commands:
          - echo "Using Docker 19"
      19:
        commands:
          - echo "Using Docker 19"
  dotnet:
    versions:
      3.1:
        commands:
          - echo "Installing .NET version 3.1 ..."

尚、上記2ファイルは https://github.com/aws/aws-codebuild-docker-images のソースを参考に作成したもので、ライセンスは https://github.com/aws/aws-codebuild-docker-images/blob/master/LICENSE.txt に従います。 こちらの環境での動作確認はしておりますが、動作を保証するものではないことをご了承ください。

これで再度イメージをビルドします。

$ docker buildx build --platform linux/arm64 etc/docker -t codebuild-runtime:aarch64 --load

Amazon ECR に push して完了です。

## Amazon ECR login
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ************.dkr.ecr.ap-northeast-1.amazonaws.com

## tagging
$ docker tag codebuild-runtime:aarch64 ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:aarch64

## push
$ docker push ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:aarch64

これで (自作の)AArch64 対応 Corretto 17 が Amazon ECR から利用可能になりました。

3. AWS CodeBuild の設定を変更する

②の設定として、AWS CodeBuild の環境設定を変更します。 カスタムイメージを選択し、2. で作成したイメージを使用します。

マネジメントコンソールからの場合、以下の設定にします。

terraformで管理されている場合は、aws_codebuild_projectenvironment.image を 2. で作成したイメージに、typeを”ARM_CONTAINER”に書き換えて terraform applyしてください。

resource "aws_codebuild_project" "aarch64_build" {
  ・・・
  environment {
    compute_type = "BUILD_GENERAL1_SMALL"
    image        = "************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:aarch64"
    type         = "ARM_CONTAINER"
  }
  ・・・
}

registry.terraform.io

もし AWS CodeBuild で初めて Corretto 17 を使うのであれば、AWS CodeBuild で使用している buildspec.yml も Corretto 17 に変更しておきましょう。

buildspec.yml(クリックで展開)

・・・
phases:
  install:
    runtime-versions:
      java: corretto17
・・・

そのほかの設定について、特別な対応は不要なので、省略します。

繰り返しにはなりますが、公式の AArch64 対応 Corretto 17 を使えば、Amazon ECR に登録したりカスタムイメージを使ったりということは不要です。マネージド型イメージから公式イメージを設定することで使えるようになります。

以上、【上級】の内容でした。 本セクションの内容は既に古い内容ですが、将来的に AArch64 対応 Corretto 21 イメージがなかなか提供されずに困っている際などには使えるかもしれないですね。

【応用】マルチアーキテクチャイメージを AWS CodeBuild (バッチビルド) で作る

続いて応用編です。

例えば、多くのシステムで共通的に使う、サイドカーのようなコンテナイメージが存在しており、 メインのアプリケーションだけでなく、サイドカーのイメージも AArch64 に対応させないといけないケースがあるかもしれません。

サイドカーのコンテナイメージ は 共通部品のため、x86_64 を使いたいチームも、AArch64 を使いたいチームもいます。

そこで、スマートに解決してくれるのがマルチアーキテクチャイメージです。

マルチアーキテクチャイメージと作り方

マルチアーキテクチャイメージである【単一の Docker イメージ:タグ】 を指定すると、使われる環境(OS・CPUアーキテクチャ)を判別し、自動で最適なイメージを使ってくれるという便利なイメージです。

マルチアーキテクチャイメージは簡単に作成できます。 仮に、x86_64 と AArch64 の2種類のイメージがあるケースでは manifest 作成時に 2種類のイメージを指定して作成するだけで完成です。 更にAmazon ECR から利用したい場合は、x86_64 イメージと AArch64 イメージ、あと manifest を Amazon ECR に push するだけです。

👇手でやるとこんな感じ。割と簡単です!

## aarch64 と x86_64 のイメージが存在する状態でスタート
$ docker images
REPOSITORY                                                      TAG         IMAGE ID       CREATED        SIZE
************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage   aarch64     bf2578f2e983   7 weeks ago    8.4GB
************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage   x86_64      74df2af5cae2   7 weeks ago    8.4GB

## manifest を作成
$ docker manifest create ************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage:latest \
>            ************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage:aarch64 \
>            ************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage:x86_64

## Amazon ECR へ push
$ docker push ************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage:aarch64
$ docker push ************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage:x86_64
$ docker manifest push ************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage:latest

利用者側はマルチアーキテクチャイメージという点を意識せず、 ************.dkr.ecr.ap-northeast-1.amazonaws.com/dockerImage:latest を利用するだけで、 あとは、x86_64 環境では x86_64 のイメージを、AArch64 環境では AArch64 のイメージを 自動判別して使ってくれます。

全体の流れ

さて、本題のマルチアーキテクチャイメージを作成する流れです。 AWS CodeBuild(バッチビルド)を用いて、x86_64、AArch64 を並列でイメージのビルドと Amazon ECR への push を実施。 双方が完了後、manifest を作成し manifest も Amazon ECR に push することで、マルチアーキテクチャイメージを Amazon ECR 上から利用可能としています。

上図において、 x86_64 build と AArch64 build は buildspec-image.yml で、 manifest create と manifest push は buildspec-manifest.yml で、 全てを取りまとめる バッチビルドは buildspec-batch.yml で実装します。

尚、AWS DevOps Blog では AWS CodePipeline を用いて AWS CodeBuild を 3つ 個別に設定し、マルチアーキテクチャイメージを作成する方法も紹介されていましたので別例としてリンクのみのご紹介とさせていただきます。

aws.amazon.com

1. buildspec-image.yml の作成

1. については、あくまで一例程度のご紹介です。 docker build をして push するだけの操作を想定して記載しています。

REPO_NAME等の変数は 3. で作成する buildspec-batch.yml にて設定しますので「あ~、なんか入れてるな~🧐」程度の飛ばし読みでお願いします。

buildspec-image.yml(クリックで展開)

version: 0.2

phases:
  install:
    commands:
      - echo Starting the Docker daemon...
      - /usr/local/bin/dockerd-entrypoint.sh
  pre_build:
    commands:
      - ECR_REPO_NAME=$(aws ecr describe-repositories --repository-names $REPO_NAME --output text --query "repositories[0].repositoryUri")
      - IMAGE_SIDECAR_WITH_TAG=$ECR_REPO_NAME:$DEPLOY_IMAGE_TAG_SIDECAR$DEPLOY_IMAGE_TAG_POSTFIX
  build:
    commands:
      # Dockerイメージの作成
      - docker build -t $IMAGE_SIDECAR_WITH_TAG etc/docker/sidecar/
  post_build:
    commands:
      # Amazon ECRへログイン
      - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $ECR_REPO_NAME
      # Dockerイメージをデプロイ用のイメージタグでpush
      - docker push $IMAGE_SIDECAR_WITH_TAG

3. で後述しますが、ランタイムイメージを x86_64 と AArch64 で 個別に設定するため、 AArch64 イメージのビルド時も docker buildx build コマンドを使わず docker build コマンドで問題ありません。

2. buildspec-manifest.yml の作成

ここでは docker manifest create コマンドを実行し、Amazon ECR に push する処理を記載します。

変数については1. 同様、3. で作成する buildspec-batch.yml にて設定します。

buildspec-manifest.yml(クリックで展開)

version: 0.2

phases:
  install:
    commands:
      - echo Starting the Docker daemon...
      - /usr/local/bin/dockerd-entrypoint.sh
  pre_build:
    commands:
      - ECR_REPO_NAME=$(aws ecr describe-repositories --repository-names $REPO_NAME --output text --query "repositories[0].repositoryUri")
      - IMAGE_SIDECAR=$ECR_REPO_NAME:$DEPLOY_IMAGE_TAG_SIDECAR
  post_build:
    commands:
      # Amazon ECRへログイン
      - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $ECR_REPO_NAME
      # Dockerイメージを取得
      - for postfix in $DEPLOY_IMAGE_TAG_POSTFIXES; do docker pull $IMAGE_SIDECAR$postfix; done
      # マルチアーキテクチャのためのマニフェストを作成
      - for postfix in $DEPLOY_IMAGE_TAG_POSTFIXES; do echo $postfix; done | sed "s,^,$IMAGE_SIDECAR," | xargs docker manifest create $IMAGE_SIDECAR
      # マニフェストをpush
      - docker manifest push $IMAGE_SIDECAR

内容はコメントで記載の通りですので、説明は省きます。

3. buildspec-batch.yml の作成

ここではバッチビルドの設定を記載します。

identifier:x86_64では x86_64 の、aarch64では AArch64 のイメージで実行する設定となっています。 identifier:multiarch_manifestは、x86_64、AArch64 どちらのイメージで実行しても問題ありませんが、以下例では AArch64 のイメージで実施しています。

depend-on:で identifier を指定し、x86_64aarch64 が完了した後、buildspec-manifest.yml が実行されるような設定としています。

buildspec-batch.yml(クリックで展開)

version: 0.2

env:
  variables:
    REPO_NAME: "sidecar"
    DEPLOY_IMAGE_TAG_SIDECAR: "release-1.0.0"
    DEPLOY_IMAGE_TAG_POSTFIXES: ".x86_64 .aarch64"
    BUILD_IMAGE_X86_64: "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
    BUILD_IMAGE_AARCH64: "aws/codebuild/amazonlinux2-aarch64-standard:2.0"

batch:
  build-graph:
    - identifier: x86_64
      buildspec: buildspec/buildspec-image.yml
      env:
        image: $BUILD_IMAGE_X86_64
        type: LINUX_CONTAINER
        variables:
          DEPLOY_IMAGE_TAG_POSTFIX: ".x86_64"
        privileged-mode: true
    - identifier: aarch64
      buildspec: buildspec/buildspec-image.yml
      env:
        image: $BUILD_IMAGE_AARCH64
        type: ARM_CONTAINER
        variables:
          DEPLOY_IMAGE_TAG_POSTFIX: ".aarch64"
        privileged-mode: true
    - identifier: multiarch_manifest
      buildspec: buildspec/buildspec-manifest.yml
      env:
        image: "aws/codebuild/amazonlinux2-aarch64-standard:2.0"
        type: ARM_CONTAINER
      depend-on:
        - x86_64
        - aarch64

4. AWS CodeBuild の設定

最後にAWS CodeBuild の設定です。

環境は AArch64 の公式イメージを指定しておきます。

buildspecは 3. で作成した buildspec-batch.yml を指定します。

バッチ設定はチェックしておきます。

terraform で管理されている場合は、 aws_codebuild_projectリソースにbuild_batch_configの設定を追記してください。

resource "aws_codebuild_project" "sidecar_build" {
・・・
  build_batch_config {
    timeout_in_mins = 60
    service_role    = aws_iam_role.codebuild_for_build_image_sidecar.arn


    restrictions {
      compute_types_allowed  = []
      maximum_builds_allowed = 100
    }
  }
・・・
}

registry.terraform.io

また、バッチビルドにする場合、AWS CodeBuild を実行する IAM ポリシーにも権限の追加が必要です。 docs.aws.amazon.com

マネジメントコンソールから編集される場合は、バッチ設定で設定したサービスロールに対して、IAM の ポリシー から3つ(StopBuild, StartBuild, RetryBuild)の設定を追記します。

{
    "Statement": [
   ・・・
        {
            "Action": [
                "codebuild:StopBuild",
                "codebuild:StartBuild",
                "codebuild:RetryBuild"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Sid": ""
        },
   ・・・
    ],
    "Version": "2012-10-17"
}

terraform で管理されている場合、aws_iam_policy_documentに対して、上記JSON同様の設定を追記して terraform apply すれば完了です。

data "aws_iam_policy_document" "codebuild_for_build_image_sidecar" {
・・・
  statement {
    effect = "Allow"


    actions = [
      "codebuild:StartBuild",
      "codebuild:StopBuild",
      "codebuild:RetryBuild",
    ]


    resources = ["*"]
  }
・・・
}

大変お疲れ様でした! これで、マルチアーキテクチャイメージを AWS CodeBuild (バッチビルド) で作成できますね!

【Appendix】Java17 のマルチアーキテクチャイメージをビルド

えっ、「マルチアーキテクチャイメージを Java17 でビルドしたい」ですって!? 「集大成みたいなのキタ━━━━(゚∀゚)━━━━!!(古」って感じでしょうか。

ほとんど【応用】と同じですが、3点ほど修正を加えることで可能です。

  1. codebuild-runtime の Corretto 17 をマルチアーキテクチャイメージにする
  2. buildspec-image.yml を修正する
  3. AWS CodeBuild の環境設定

1. codebuild-runtime の Corretto 17 をマルチアーキテクチャイメージにする

【上級】の 2. Corretto 17 対応させて、再度イメージを作る 方法で作成した AArch64 の Corretto 17 イメージでも利用可能ですが、今回は公式のイメージを用いてCorretto 17 のマルチアーキテクチャイメージを作成します。

## AArch64 イメージビルド
$ docker buildx build --platform linux/arm64 al2/aarch64/standard/3.0 -t codebuild-runtime:aarch64 --load
## x86_64 イメージビルド
$ docker build al2/x86_64/standard/4.0 -t codebuild-runtime:x86_64 --load

完了後、manifest ファイルを作成し、AWS ECR へ push します。

## tagging
$ docker tag  codebuild-runtime:aarch64 \
>            ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:aarch64
$ docker tag  codebuild-runtime:x86_64 \
>            ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:x86_64

## manifest を作成
$ docker manifest create ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:latest \
>            ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:aarch64 \
>            ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:x86_64

## Amazon ECR login
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ************.dkr.ecr.ap-northeast-1.amazonaws.com

## Amazon ECR へ push
$ docker push ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:aarch64
$ docker push ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:x86_64
$ docker manifest push ************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:latest

2. buildspec-image.yml を修正する

【応用】で3種類作成した、buildspec-●●.yml ですが、1種類の修正が必要です。

buildspec-image.yml は jar ファイルの作成を実施していませんでしたが、今回 Java のビルドが入るため、 installphase ではruntime-versionscorretto17 を、buildphase では jar ファイル作成と cp の 2コマンドを追記しました。

buildspec-image.yml(クリックで展開)

version: 0.2

phases:
  install:
    runtime-versions:
      java: corretto17
    commands:
      - echo Starting the Docker daemon...
      - /usr/local/bin/dockerd-entrypoint.sh
  pre_build:
    commands:
      - ECR_REPO_NAME=$(aws ecr describe-repositories --repository-names $REPO_NAME --output text --query "repositories[0].repositoryUri")
      - IMAGE_SIDECAR_WITH_TAG=$ECR_REPO_NAME:$DEPLOY_IMAGE_TAG_SIDECAR$DEPLOY_IMAGE_TAG_POSTFIX
  build:
    commands:
      # jarファイル作成 & cp
      - ./gradlew --no-daemon clean build
      - cp build/libs/sidecar.jar etc/docker/sidecar/
      # Dockerイメージの作成
      - docker build -t $IMAGE_SIDECAR_WITH_TAG etc/docker/sidecar/
  post_build:
    commands:
      # Amazon ECRへログイン
      - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $ECR_REPO_NAME
      # Dockerイメージをデプロイ用のイメージタグでpush
      - docker push $IMAGE_SIDECAR_WITH_TAG

3. AWS CodeBuild の環境設定

buildspec-batch.yml では公式イメージを使用していましたが、今回 Corretto 17 でビルドする必要があるため、環境イメージをマルチアーキテクチャイメージに変更。 buildspec-batch.yml の変数は、AWS CodeBuild の環境変数で上書きします。

マネジメントコンソールからは、AWS CodeBuild の「環境を編集」から書き換えます。

カスタムイメージで、 1. で作成した マルチアーキテクチャイメージを設定します。

buildspec-batch.yml で使う2つの環境変数にも、1. で作成したマルチアーキテクチャイメージ(************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:latest)を設定します。

terraform 管理の場合、aws_codebuild_projectenvironmentの中身を書き換えてください。

resource "aws_codebuild_project" "sidecar_build" {
・・・
  environment {
    compute_type = "BUILD_GENERAL1_SMALL"
    image        = "************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:latest"
    type         = "LINUX_CONTAINER"
    ・・・
    environment_variable {
      name  = "BUILD_IMAGE_X86_64"
      value = "************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:latest"
    }
    environment_variable {
      name  = "BUILD_IMAGE_AARCH64"
      value = "************.dkr.ecr.ap-northeast-1.amazonaws.com/codebuild-runtime:latest"
    }
  }
・・・
}

大変大変お疲れ様でしたー! これで、Java17 プロジェクトにおいて マルチアーキテクチャイメージのビルドも、1つの AWS CodeBulild(バッチビルド)で実装できますね!

最後に

本記事は AS IS で公開されます。 内部事情をぶっちゃけますと、執筆途中で、AWS公式より AArch64 対応 Corretto 17 が利用可能となり、内容を急遽修正しました。 AWS公式より AWS Fargate の Graviton2 対応が発表されてから暫く経過しており、且つ 公開当初から OUTDATED な内容も記載している掟破りなTechBlogとなってしまい、読みにくい箇所もあったと思いますが、ご容赦いただけますと幸いです。

いやはや、執筆した当人が言うのもナンですが、長すぎて読む気が失せry…(´∀`;)

当時の対応方法もご紹介しましたが、最後まで読んでいただいた皆様に何かしら新しい発見があれば良いなーと思っております。 長文お読みいただきお疲れ様でした、そしてありがとうございました🙇

ご感想などありましたら、はてなブックマークや Twitter シェア時にコメントいただけると執筆者がエゴサして一喜一憂しますっ👀!!w

BIGLOBEでは、新しい技術を積極的に取り入れて開発を行っています。 記事を読んで「一緒に働いてみたい」と思われた方、「私ならもっと良い方法が出来る」と思われた方、ぜひカジュアル面談にお越しください! hrmos.co

※ Amazon Web Services、AWS、AWS Fargate、Correttoは、米国その他諸国における、Amazon.com, Inc. またはその関連会社の商標です。

※ Apple 及び MacBook は、Apple Inc.の商標です。

※Java は、Oracle、その子会社及び関連会社の米国及びその他の国における登録商標です。

※ Twitter は、Twitter,Inc.の米国およびその他の国における登録商標です。

※ 記載している団体、製品名、サービス名称は各社またはその関連会社の商標または登録商標です。