コネヒト開発者ブログ

コネヒト開発者ブログ

SOCIインデックスによるECSのデプロイ時間短縮について検証しました

こんにちは。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インデックス作成の流れ

  1. ECRイメージアクションイベントを検出しフィルタリング用のAWS Lambdaを呼び出す。
  2. CloudFormationのパラメータで提供されたフィルタに一致するイメージアクションイベントをフィルタリング。
  3. 一致するイメージのSOCIインデックスを生成し、ECRレジストリのイメージリポジトリにインデックスをプッシュバックする。

作成される各リソースの詳細を知りたい方は引用元のリンク先をご確認ください。

検証方法

SOCIインデックスの検証にあたり、今回2段階で実施しました。

  1. SOCIインデックスの効果検証

    • 実際のDev(開発)環境に適用する前に、まずはSOCIインデックスに本当に効果がありそうか調べました。
    • 現行のDev環境と同一のECSクラスター内にSOCIインデックス適用済みの別サービスを立て現行と起動時間の比較をしています。
  2. 現行の開発環境での動作検証
    • 検証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イメージの改善等)も併せて検討していく必要があると感じました。
  • プラットフォームグループでは引き続きデプロイフローの改善をはじめ、様々な開発環境の改善に向け取り組んでいきたいと思います。