こんにちは。2023年7月に入社しました、開発部プラットフォームグループ インフラエンジニアの @yosshi です。今回はSOCIインデックスによるECSのデプロイ時間の短縮効果について検証を行ったので、その検証内容と結果を共有したいと思います。
デプロイ時間短縮の方法は様々あると思いますが、SOCIインデックスはイメージやデプロイフローに手を入れずに、比較的簡単に取り入れられそうだったので導入を検討しました。
SOCI(Seekable OCI)インデックスとは
SOCIインデックスとは、イメージの遅延読み込み(非同期読み込み)を可能にする技術です。イメージ全体をダウンロードする前にコンテナイメージから個々のファイルを抽出できるようにし、コンテナを高速に起動することができます。
既存のコンテナイメージにあるファイルのインデックス (SOCI インデックス) を作成することによって機能します。
利用条件
- コンテナイメージが
x86_64
もしくはARM64
アーキテクチャであること - Linuxプラットフォームバージョンが
1.4.0
であること
参考記事
SOCIインデックスの適用方法
SOCIインデックスを作成する方法は、公式で用意されているAWS SOCI Index Builderを使用する方法と、手動で作成する方法の2パターンがありますが、今回はより簡単に導入できそうなAWS SOCI Index Builderを使用する方法を選択しました。
AWS SOCI Index Builderは、 AWS クラウド内のコンテナイメージのインデックスを作成するためのサーバーレスソリューションで、公式よりCloudFormaitonテンプレートが用意されています。
今回はこのCloudFormationテンプレートを利用し、Terraformで適用していきます。
以下のようなイメージです。
resource "aws_cloudformation_stack" "soci_index_builder" { name = "soci-index-builder" template_url = "https://aws-quickstart.s3.us-east-1.amazonaws.com/cfn-ecr-aws-soci-index-builder/templates/SociIndexBuilder.yml" <span style="color: #d32f2f">#公式で用意されているSOCIのテンプレートのパス</span> capabilities = ["CAPABILITY_IAM"] parameters = { "SociRepositoryImageTagFilters" = "<リポジトリ名>:<タグ名>" #SOCIインデックスの適用範囲のフィルターをかける } }
AWS SOCI Index Builderのアーキテクチャ
EventBridge・Lambda・IAM・CloudWatchなどのリソースが作成されます。
引用: https://aws-ia.github.io/cfn-ecr-aws-soci-index-builder/
SOCIインデックス作成の流れ
- ECRイメージアクションイベントを検出しフィルタリング用のAWS Lambdaを呼び出す。
- CloudFormationのパラメータで提供されたフィルタに一致するイメージアクションイベントをフィルタリング。
- 一致するイメージのSOCIインデックスを生成し、ECRレジストリのイメージリポジトリにインデックスをプッシュバックする。
作成される各リソースの詳細を知りたい方は引用元のリンク先をご確認ください。
検証方法
SOCIインデックスの検証にあたり、今回2段階で実施しました。
SOCIインデックスの効果検証
- 実際のDev(開発)環境に適用する前に、まずはSOCIインデックスに本当に効果がありそうか調べました。
- 現行のDev環境と同一のECSクラスター内にSOCIインデックス適用済みの別サービスを立て現行と起動時間の比較をしています。
- 現行の開発環境での動作検証
- 検証1でSOCIインデックスの一定の効果が見られたので、実際に使用している開発環境にSOCIインデックスを適用し比較しました。
それでは検証内容について詳しく説明していきます。
前提条件
弊社では以下環境を使用しています 。
- ECS on Fargate
- AWSリソースの反映:Terraform
- ECSのデプロイ:ecspresso
検証1:SOCIインデックスの効果検証
まずはDev環境の同一クラスター内にSOCIインデックス適用済み別サービスを立てて現行との起動時間を比較しました。
リソースの作成はTerraformで行っています。まずは必要なリソースを作成していきます。
ECRリポジトリ
resource "aws_ecr_repository" "example_soci_test" { name = "example-soci-test" image_scanning_configuration { scan_on_push = true } }
SOCI Index BuilderのCloudFormationテンプレート
example-soci-testリポジトリの全てにSOCIインデックスを適用するようフィルターを設定しています。
resource "aws_cloudformation_stack" "soci_index_builder" { name = "soci-index-builder" template_url = "https://aws-quickstart.s3.us-east-1.amazonaws.com/cfn-ecr-aws-soci-index-builder/templates/SociIndexBuilder.yml" capabilities = ["CAPABILITY_IAM"] parameters = { "SociRepositoryImageTagFilters" = "example-soci-test:*" } }
Terraformを使用し作成したリソースを適用します。
$ terraform apply
次に作成したECRリポジトリにイメージをプッシュします。
Dockerfileは従来のDev環境と同じものを使用しています。
イメージのビルド
$ docker build . -t example-soci-test:latest
リポジトリにイメージをプッシュできるように、イメージにタグ付け
$ docker tag example-soci-test:latest xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/example-soci-test:latest
ECRにイメージをプッシュ
$ docker push xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/example-soci-test:latest
AWSコンソール上でECRの対象リポジトリの画面から、作成したイメージに対して、アーティファクトタイプがSoci Index, Image Indexのイメージが別途作成されていることがわかります。この作成されたSOCIインデックス適用済みのイメージを使用します。
次にこのSOCIインデックス適用済みのイメージを使用しECS環境へデプロイします。
弊社ではデプロイツールとしてecspressoを使用しているため、ecspressoを利用しローカルからデプロイしたいと思います。(ecspressoを使用していない場合は別の方法でのデプロイを実施してください)
ecspresso initで既存のDev環境の設定ファイルをローカルに持ってきます。
$ ecspresso init \ --config config.yaml \ --region ap-northeast-1 \ --cluster <Dev環境のクラスター名> \ --service <Dev環境のサービス名>
実行することでローカルに3つのファイルが作成されます。
- config.yaml
- ecs-service-def.json
- ecs-task-def.json
作成されたファイルの内容を今回テストする内容に書き換えます。
- config.yaml
region: ap-northeast-1 cluster: example-cluster #Dev環境と同じクラスターを使用 service: example-soci-test-service #SOCIインデックス適用するためのサービス service_definition: ecs-service-def.json task_definition: ecs-task-def.json timeout: "10m0s"
- ecs-service-def.json
{ "capacityProviderStrategy": [ { "base": 1, "capacityProvider": "FARGATE_SPOT", "weight": 1 } ], "deploymentConfiguration": { "deploymentCircuitBreaker": { "enable": false, "rollback": false }, "maximumPercent": 200, "minimumHealthyPercent": 100 }, "deploymentController": { "type": "ECS" }, "desiredCount": 1, "enableECSManagedTags": false, "enableExecuteCommand": false, "healthCheckGracePeriodSeconds": 0, "launchType": "", "networkConfiguration": { "awsvpcConfiguration": { "assignPublicIp": "ENABLED", "securityGroups": [ <security-group> #現行で使用しているセキュリティグループを利用 ], "subnets": [ <subnet> #現行で使用しているsubnetを利用 ] } }, "pendingCount": 0, "platformFamily": "Linux", "platformVersion": "LATEST", "propagateTags": "NONE", "runningCount": 0, "schedulingStrategy": "REPLICA" }
- ecs-task-def.json
{ "containerDefinitions": [ { "cpu": 0, ], "essential": true, "image": "xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/example-soci-test:latest", "name": "example-soci-test", } ], "cpu": "256", "executionRoleArn": <execution-role-arn>, #現行で使用しているexecution roleを利用 "family": "example-soci-test-task", "ipcMode": "", "memory": "512", "networkMode": "awsvpc", "pidMode": "", "requiresCompatibilities": [ "FARGATE" ], "taskRoleArn": <task-role-arn> #現行で使用しているtask roleを利用 }
ファイルの作成が完了したら、ローカルからecspressoで適用していきます
$ ecspresso deploy --config <config.yamlのパス>
反映されたことが確認できたら、環境の準備が整っているので起動のためのテストしていきます。
以下のシェルスクリプトを用意し、現在のDev環境とSOCIインデックス適用済みの環境でそれぞれ実行することで、どの程度起動時間に差があるのか確認します。
CLUSTER=example-cluster #現行のDev環境のクラスター TASKDEF={テスト対象のタスク定義} REGION=ap-northeast-1 NUM_OF_TASKS=1 TASKS=$(aws ecs list-tasks \ --cluster $CLUSTER \ --family $TASKDEF \ --region $REGION \ --query "taskArns[:${NUM_OF_TASKS}]" \ --output text) aws ecs describe-tasks \ --tasks $TASKS \ --region $REGION \ --cluster $CLUSTER \ --query "tasks[] | reverse(sort_by(@, &createdAt)) | [].[{startedAt: startedAt, createdAt: createdAt, taskArn: taskArn}]" \ --output table
実行結果
SOCIインデックス適用前(現行のDev環境):1分05秒
--------------------------------------------------------------------------------------------------------------------- | DescribeTasks | +-----------+-------------------------------------------------------------------------------------------------------+ | createdAt | 2023-08-14T12:39:13.360000+09:00 | | startedAt | 2023-08-14T12:40:18.763000+09:00 | | taskArn | arn:aws:ecs:ap-northeast-1:xxxxxxxxxx:task/example-cluster/xxxxxxxxxxxxxxxxxxxxxxxx | +-----------+-------------------------------------------------------------------------------------------------------+
SOCIインデックス適用済みの環境:36秒(29秒の短縮)
--------------------------------------------------------------------------------------------------------------------- | DescribeTasks | +-----------+-------------------------------------------------------------------------------------------------------+ | createdAt | 2023-08-14T19:42:05.469000+09:00 | | startedAt | 2023-08-14T19:42:41.185000+09:00 | | taskArn | arn:aws:ecs:ap-northeast-1:xxxxxxxxxx:task/example-cluster/xxxxxxxxxxxxxxxxxxxxxxx | +-----------+-------------------------------------------------------------------------------------------------------+
30秒弱起動時間が短縮されたことがわかります。
検証2:現行のDev環境にSOCIインデックスを適用
上記のテストである程度効果がありそうなことがわかったので、次に実際にDev環境に対してSOCIインデックスを適用していきます。
弊社ではブランチ環境をDev環境にデプロイする際、branch_deploy
のイメージタグを使用しています。
よって今回はbranch_deploy
のイメージタグを使用している全ての対象に対してSOCIインデックスを適用していきます。
再度Indexbuilderのフィルターに今回の対象を追加していきます。
resource "aws_cloudformation_stack" "soci_index_builder" { name = "soci-index-builder" template_url = "https://aws-quickstart.s3.us-east-1.amazonaws.com/cfn-ecr-aws-soci-index-builder/templates/SociIndexBuilder.yml" capabilities = ["CAPABILITY_IAM"] parameters = { "SociRepositoryImageTagFilters" = "example-soci-test:*, *:branch_deploy" } }
Dev環境での検証は、Github Actionsのデプロイ時間をもとに計測していきます。
- Github Actionsの内容
※ 弊社で適用している内容から必要な項目のみ抜粋して記載しています。
name: Manually deploy to development on: workflow_dispatch env: IMAGE_TAG: ${{ (github.ref == 'refs/heads/main' && github.sha) || 'branch_deploy' }} AWS_ROLE_ARN: <aws_role_arn> permissions: id-token: write contents: read actions: read issues: write jobs: build: runs-on: ubuntu-latest outputs: ref_link: ${{ env.REF_LINK }} steps: - name: Checkout uses: actions/checkout@v3 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ap-northeast-1 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build, tag, and push image to Amazon ECR if: github.ref != 'refs/heads/main' env: DOCKER_BUILDKIT: 1 ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: <リポジトリ名> run: | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --build-arg GITHUB_ACCESS_TOKEN=${{ secrets.MACHINE_USER_GITHUB_ACCESS_TOKEN }} . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - name: Build, tag, and push image to Amazon ECR for main if: github.ref == 'refs/heads/main' # branch_depoloyタグ以外の処理が書いてあるので省略 # ... deploy: needs: build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ap-northeast-1 - uses: kayac/ecspresso@v2 with: version: v2.0.3 - name: Deploy to Amazon ECS run: | ecspresso deploy --config <configファイルのパス>
検証結果
弊社のとあるアプリケーションを例に、SOCIインデックス適用前と後でそれぞれ直近8回のデプロイ時間を計測し比較しました。
SOCIインデックス適用前のデプロイ時間
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
3分44秒 | 3分14秒 | 3分50秒 | 2分40秒 | 3分53秒 | 4分14秒 | 3分47秒 | 4分18秒 |
SOCIインデックス適用後のデプロイ時間
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
3分12秒 | 2分37秒 | 3分13秒 | 3分02秒 | 3分27秒 | 2分55秒 | 2分58秒 | 2分35秒 |
※ Github Actions内の”deploy”部分の時間を記載しています。
8回分のデプロイ時間を平均すると以下のようになりました。
- SOCIインデックス適用前:3分42秒
- SOCIインデックス適用後:2分59秒
- 短縮した時間:43秒
時間のばらつきはあるものの、平均すると上記の通りデプロイ時間を短縮できたことになります。
実際のDev環境でも一定の短縮効果があることがわかりました。
補足
Lambdaのランタイムについて
- 公式のCloudFormationテンプレートにより作成されるLambdaのランタイムがGo.1.xを使用しており、このサポートが2023年12月31日に終了するので、この点に関しては注意が必要そうです。
- 弊社でも将来的に新しいランタイムへの移行を計画しています。
おわりに
- 今回SOCIインデックスを試してみて、割と簡単に導入できた上に、一定の効果があったので、簡易的なデプロイ時間短縮の手段としてはアリだと思いました。
- Dev環境での動作が問題なさそうなことがわかったので、順次本番環境への適用も検討していきたいと思います。
- また一方で、なかなか大幅な改善とはならなかったので、改めて他の手段(Dockerイメージの改善等)も併せて検討していく必要があると感じました。
- プラットフォームグループでは引き続きデプロイフローの改善をはじめ、様々な開発環境の改善に向け取り組んでいきたいと思います。