ちょっと話題の記事

[アップデート]全 AWS Fargate 利用者必見! Seekable OCI インデックスによりコンテナの起動が大幅に高速化するようになりました

コンテナイメージを変更せずにコンテナをより速く起動できるようになりました!検証では、約50%高速化しました。
2023.07.18

はじめに

昨年、AWSはSeekable OCI(SOCI)の導入により、アプリケーションの起動と同時にコンテナからデータを非同期にダウンロードするコンテナイメージの遅延読み込みを実現しました。

これにより、コンテナイメージを変更せずにアプリケーションをより速く起動できるようになりました。

今回、SOCIがAWS Fargateにもサポートされました!

SOCIは、ECRに保存されているコンテナイメージと同じECRにインデックスを作成しておくことで、イメージ全体をダウンロードせずに個々のファイルを抽出してコンテナを迅速に起動できます。

Amazon ECR リポジトリからイメージをダウンロードする際には、自動的にSOCI インデックスの有無を検出し、イメージ全体のプルを待たずにコンテナを起動します。

もちろん、ECR リポジトリにSOCI インデックスがないコンテナイメージも引き続き実行できます。

追記:2023/7/27(日本語のAWSブログも公開されました)

SOCI インデックスの概要

SOCI インデックスは、イメージの遅延読み込み(非同期読み込み)を可能にするアーティファクトです。

SOCI インデックスは、下図の右側のSOCI インデックスマニフェストzTOC (圧縮データのインデックス)で構成されます。

SOCI インデックスマニフェストには、zTOC のリストと、マニフェストが生成されたイメージが含まれています。

引用:https://aws.amazon.com/jp/blogs/aws/aws-fargate-enables-faster-container-startup-using-seekable-oci/

以下のリンクで用語の詳細が解説されております

SOCI インデックスの考慮点

Fargate で SOCI インデックスを使用してコンテナイメージを遅延読み込みする場合は、次の点を考慮してください。

  • SOCI インデックスを使用できるのは、Linux プラットフォーム バージョン1.4.0で実行されるタスクのみです
  • X86_64 CPU アーキテクチャで実行されるタスクのみがサポートされます
  • イメージサイズが 250 MiB を超えるコンテナイメージで遅延読み込みを試すことをお勧めします。サイズが小さい場合、コンテナ起動の改善時間が低くなります。
  • 追加コストなしでSOCIを利用できますが、SOCI インデックスのイメージをECRに保存する場合に料金が発生します。

SOCI インデックスの作成方法

SOCI インデックスの作成方法は2つあります。

  1. AWS SOCI Index Builder を使用する
    • AWS SOCI Index Builder は、 コンテナイメージのSOCI インデックスを作成するためのサーバーレスソリューションです。
    • 以下のCloudFormationのスタックが用意されております
      • Amazon EventBridgeルールが ECRアクションイベントを識別し、定義されたフィルターに一致するAWS Lambdaを呼び出します。別のAWS Lambda関数が SOCI インデックスを生成し、ECR リポジトリにプッシュします。
      • 引用:https://aws-ia.github.io/cfn-ecr-aws-soci-index-builder/
  2. SOCI インデックスを手動で作成する
    • SOCI Snapshoter というプラグインを利用して作成します。

ECRにイメージをプッシュすると、自動でSOCI インデックスが作成されるAWS SOCI Index Builderの使用がおすすめです。

今回は、AWS SOCI Index Builderを利用します。

環境

  • PC:M2 MacBook Pro
  • 利用するイメージ:Nginx

イメージを作成するディレクトリ構成

/ $ tree
.
├── 1GB.zip
├── Dockerfile
├── index.html
├── task-query-nginx.sh
└── task-query-nginx-soci.sh

1GB.zipは、1GB程度のファイルです。

Dockerfile

FROM nginx:1.20.0

COPY ./index.html /usr/share/nginx/html
COPY ./1GB.zip /usr/share/nginx/html

EXPOSE 80

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <h1>SOCI Test1</h1>
</body>
</html>

task-query-nginx.shtask-query-nginx-soci.shは、タスクの起動時間を確認するため、「開始時刻(startedAt) 」と「作成時刻(createdAt)」を出力するコマンドです。

後で解説しますが、以下の設定です。

  • クラスター名
    • nginx
  • タスク定義
    • nginx-soci
    • nginx

task-query-nginx-soci.sh

CLUSTER=nginx
TASKDEF=nginx-soci
REGION=ap-northeast-1
TASKS=$(aws ecs list-tasks \
    --cluster $CLUSTER \
    --family $TASKDEF \
    --region $REGION \
    --query 'taskArns[*]' \
    --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

task-query-nginx.sh

CLUSTER=nginx
TASKDEF=nginx
REGION=ap-northeast-1
TASKS=$(aws ecs list-tasks \
    --cluster $CLUSTER \
    --family $TASKDEF \
    --region $REGION \
    --query 'taskArns[*]' \
    --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

ECRを作成

SOCI インデックスの有無を比較するため、同じコンテナイメージ(今回は、Nginx)を使用します。

また、SOCI インデックスが存在するECR リポジトリ(nginx-soci1)とSOCI インデックスを存在しないECR リポジトリ(nginx)を下記コマンドで作成します。

Macのターミナルを利用しています。

$ aws ecr create-repository --region ap-northeast-1 --repository-name nginx-soci
$ aws ecr create-repository --region ap-northeast-1 --repository-name nginx

AWS SOCI Index Builder をデプロイ

こちらからCloudFormationのスタックを作成します。

パラメータのSOCI repository image tag filtersは、リポジトリ名:タグ名のため、nginx-soci:latestにします。

ECRにプッシュしたイメージ全て、SOCI インデックスを作成したい場合、*:*にします。

他は、デフォルトです。

デプロイ成功したことを確認します。

イメージをECRにプッシュする

ディレクトリ構成は、以下のとおりです。

/ $ tree
.
├── 1GB.zip
├── Dockerfile
├── index.html
├── task-query-nginx.sh
└── task-query-nginx-soci.sh

イメージを作成し、自分のアカウントのECRリポジトリにプッシュします。

//アカウントIDは、自身のアカウントIDに変える

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com

$ docker build -t nginx.  --platform linux/x86_64
$ docker tag nginx:latest アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest
$ docker push アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest

$ docker build -t nginx-soci . --platform linux/x86_64
$ docker tag nginx-soci:latest アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/nginx-soci:latest
$ docker push アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/nginx-soci:latest

nginx-socリポジトリには、SOCI インデックスイメージインデックスという2 つの追加オブジェクトが確認できました。

イメージインデックスによって、Fargate はコンテナイメージに関連付けられた SOCI インデックスを検出できるようになります。

タスク定義を作成

タスク定義をそれぞれjson形式で作成します。(アカウントIDは、自身のアカウントIDに変えること)

nginx-soci

{
    "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:アカウントID:task-definition/nginx-soci:1",
    "containerDefinitions": [
        {
            "name": "nginx-soci",
            "image": "アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/nginx-soci:latest",
            "cpu": 0,
            "portMappings": [
                {
                    "name": "nginx-soci-80-tcp",
                    "containerPort": 80,
                    "hostPort": 80,
                    "protocol": "tcp",
                    "appProtocol": "http"
                }
            ],
            "essential": true,
            "environment": [],
            "environmentFiles": [],
            "mountPoints": [],
            "volumesFrom": [],
            "ulimits": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-create-group": "true",
                    "awslogs-group": "/ecs/nginx-soci",
                    "awslogs-region": "ap-northeast-1",
                    "awslogs-stream-prefix": "ecs"
                }
            }
        }
    ],
    "family": "nginx-soci",
    "executionRoleArn": "arn:aws:iam::アカウントID:role/ecsTaskExecutionRole",
    "networkMode": "awsvpc",
    "revision": 1,
    "volumes": [],
    "status": "ACTIVE",
    "requiresAttributes": [
        {
            "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
        },
        {
            "name": "ecs.capability.execution-role-awslogs"
        },
        {
            "name": "com.amazonaws.ecs.capability.ecr-auth"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
        },
        {
            "name": "ecs.capability.execution-role-ecr-pull"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
        },
        {
            "name": "ecs.capability.task-eni"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
        }
    ],
    "placementConstraints": [],
    "compatibilities": [
        "EC2",
        "FARGATE"
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512",
    "runtimePlatform": {
        "cpuArchitecture": "X86_64",
        "operatingSystemFamily": "LINUX"
    },
    "registeredAt": "2023-07-18T06:22:41.983Z",
    "registeredBy": "arn:aws:sts::アカウントID:assumed-role/cm-hirai.yuji/cm-hirai.yuji",
    "tags": []
}

nginx

]
{
    "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:アカウントID:task-definition/nginx:1",
    "containerDefinitions": [
        {
            "name": "nginx",
            "image": "アカウントID.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest",
            "cpu": 0,
            "portMappings": [
                {
                    "name": "nginx-80-tcp",
                    "containerPort": 80,
                    "hostPort": 80,
                    "protocol": "tcp",
                    "appProtocol": "http"
                }
            ],
            "essential": true,
            "environment": [],
            "environmentFiles": [],
            "mountPoints": [],
            "volumesFrom": [],
            "ulimits": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-create-group": "true",
                    "awslogs-group": "/ecs/nginx",
                    "awslogs-region": "ap-northeast-1",
                    "awslogs-stream-prefix": "ecs"
                }
            }
        }
    ],
    "family": "nginx",
    "executionRoleArn": "arn:aws:iam::アカウントID:role/ecsTaskExecutionRole",
    "networkMode": "awsvpc",
    "revision": 1,
    "volumes": [],
    "status": "ACTIVE",
    "requiresAttributes": [
        {
            "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
        },
        {
            "name": "ecs.capability.execution-role-awslogs"
        },
        {
            "name": "com.amazonaws.ecs.capability.ecr-auth"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
        },
        {
            "name": "ecs.capability.execution-role-ecr-pull"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
        },
        {
            "name": "ecs.capability.task-eni"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29"
        }
    ],
    "placementConstraints": [],
    "compatibilities": [
        "EC2",
        "FARGATE"
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512",
    "runtimePlatform": {
        "cpuArchitecture": "X86_64",
        "operatingSystemFamily": "LINUX"
    },
    "registeredAt": "2023-07-18T06:25:51.408Z",
    "registeredBy": "arn:aws:sts::アカウントID:assumed-role/cm-hirai.yuji/cm-hirai.yuji",
    "tags": []
}

タスクを作成

SOCI インデックスが存在するECR リポジトリ(nginx-soci1)とSOCI インデックスを存在しないECR リポジトリ(nginx)からイメージをタスクを5つずつ作成し、起動までの時間を比較します。

タスク起動で必要なネットワーク設定は、以下のように設定します。

$ NETWORK_CONFIG="awsvpcConfiguration={subnets=[サブネットID],securityGroups=[セキュリティグループID],assignPublicIp=ENABLED}"

セキュリティグループは、HTTPのインバウンドを許可します。

サブネットは、パブリックサブネットです。

クラスター名は、nginx です。

$ NETWORK_CONFIG="awsvpcConfiguration={subnets=[subnet-xxxxxxx],securityGroups=[sg-xxxxxxxx],assignPublicIp=ENABLED}"

// アカウントIDは、自身のアカウントIDに変えること
$ aws ecs run-task \
    --task-definition arn:aws:ecs:ap-northeast-1:アカウントID:task-definition/nginx:1 \
    --region ap-northeast-1 \
    --count 5 \
    --launch-type FARGATE \
    --network-configuration "${NETWORK_CONFIG}" \
    --cluster nginx 


$ aws ecs run-task \
    --task-definition arn:aws:ecs:ap-northeast-1:アカウントID:task-definition/nginx-soci:1 \
    --region ap-northeast-1 \
    --count 5 \
    --launch-type FARGATE \
    --network-configuration "${NETWORK_CONFIG}" \
    --cluster nginx

起動時間の確認は、task-query-nginx-soci.shtask-query-nginx.shのスクリプトを実行します

$ chmod 744 task-query-nginx.sh
$ ./task-query-nginx.sh                                              
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|                                                                         DescribeTasks                                                                         |
+----------------------------------+-----------------------------------+----------------------------------------------------------------------------------------+
|             createdAt            |             startedAt             |                                        taskArn                                         |
+----------------------------------+-----------------------------------+----------------------------------------------------------------------------------------+
|  2023-07-18T18:17:24.551000+09:00|  2023-07-18T18:18:01.655000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/f36a06e0dea04d418d783072c3379dbe   |
|  2023-07-18T18:17:24.551000+09:00|  2023-07-18T18:18:02.498000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/e5935fd181b14c32ba6006c398582cc3   |
|  2023-07-18T18:17:24.551000+09:00|  2023-07-18T18:18:02.288000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/d2ced06ac4b84d04a09e0092d0462dba   |
|  2023-07-18T18:17:24.551000+09:00|  2023-07-18T18:18:02.956000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/c7dbfcd90aa84a4c96d4fbbefff58692   |
|  2023-07-18T18:17:24.551000+09:00|  2023-07-18T18:18:03.983000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/af82a62589354c7aa61bfbae11ef8d9f   |
+----------------------------------+-----------------------------------+----------------------------------------------------------------------------------------+

$ chmod 744 task-query-nginx-soci.sh
$ ./task-query-nginx-soci.sh

-----------------------------------------------------------------------------------------------------------------------------------------------------------------
|                                                                         DescribeTasks                                                                         |
+----------------------------------+-----------------------------------+----------------------------------------------------------------------------------------+
|             createdAt            |             startedAt             |                                        taskArn                                         |
+----------------------------------+-----------------------------------+----------------------------------------------------------------------------------------+
|  2023-07-18T18:09:11.683000+09:00|  2023-07-18T18:09:33.585000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/a67582e8022242599a1fddf70766ef5c   |
|  2023-07-18T18:09:11.683000+09:00|  2023-07-18T18:09:31.600000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/61f71fc8fb424647884f6c97c0188e3b   |
|  2023-07-18T18:09:11.683000+09:00|  2023-07-18T18:09:31.714000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/48eaca68a5ae465998e10106dd542e30   |
|  2023-07-18T18:09:11.683000+09:00|  2023-07-18T18:09:32.283000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/37b2bf6431754faba283532c1293bfa2   |
|  2023-07-18T18:09:11.683000+09:00|  2023-07-18T18:09:32.749000+09:00 |  arn:aws:ecs:ap-northeast-1:アカウントID:task/nginx/20b84cd866824a98838476f9c4cafab8 
+----------------------------------+-----------------------------------+----------------------------------------------------------------------------------------+

起動時間の改善結果

リポジトリ SOCI インデックスの有無 起動時間
nginx なし 38秒
nginx-soci あり 21秒

リポジトリにSOCI インデックスが存在する場合、存在しないリポジトリに比べて、コンテナの起動が45%高速化することがわかりました!

高速化の改善の差

下記の記事では、サンプルアプリケーションを利用し、Fargate で SOCI インデックスを使用すると、SOCI インデックスを使用しない場合に比べて約 50% 高速化しました。

アプリケーションによって差があることが分かりました。

最後に

今回は、SOCI インデックスにより、コンテナイメージを変更せずにアプリケーションをより速く起動できるようになりました。

AutoScalingの起動時間の速度に悩まれていた方は、導入を検討してみてください!

参考