maybe daily dev notes

私の開発日誌

AWS CDKでSOCIインデックスをデプロイする

TL;DR;

以下のコードでCDKからSOCIインデックスをビルドし、ECRにプッシュすることができます。

# 依存関係のインストール
npm install deploy-time-build
import { SociIndexBuild } from 'deploy-time-build;

const asset = new DockerImageAsset(this, 'Image', { directory: 'example-image' });
// DockerImageAssetに対応するSOCIインデックスをビルド・プッシュ
SociIndexBuild.fromDockerImageAsset(this, 'Index', asset);

はじめに

先日AWS FargateでSeekable OCI (SOCI)が利用できるようになりました。これにより、ECSタスクの起動時間を高速化することができます。

こちらのポストによれば、特にサイズの大きなイメージで効果を発揮するようです。 (141MBで25%、1GBで75%の高速化)

この機能を利用するためには、対象のコンテナイメージに対応するSOCIインデックスをビルドし、同じECRリポジトリにプッシュする必要があります。 このために主に次の方法が用意されており、それぞれ以下の特徴があります *1

  1. soci-snapshotter CLIの利用
    • ✅ シンプルなインターフェースのCLIで、他のパイプラインに組み込みやすい
    • Linuxのみ対応、containerd が必要など、実行環境を選ぶ
  2. cfn-ecr-aws-soci-index-builder ソリューションの利用
    • ✅ CloudFormation一発で構築可能
    • ❌ 非同期にインデックスがプッシュされるため、タスク実行時にはまだインデックスが存在しない状況が起こりえる
    • ❌ Lambda上でインデックスをビルドするため、扱えるコンテナイメージサイズに上限がある

ということで、どちらも微妙に面倒なんですよね。巷のベンチマーク結果だけ見て、うちは(試すのも大変だし)良いかと見送った人もいるんじゃないでしょうか。

さて、AWS CDKではコンテナイメージをCDKデプロイ時にビルドする慣習があります。 コンテナイメージと同様に、CDKからSOCIインデックスをデプロイできると便利そうですよね。 この記事では、そのための機能を作った話や使い方をまとめます。

作ったもの

deploy-time-build というコンストラクトライブラリを開発しました。これにより、CDKのデプロイ中にSOCIインデックスをビルド・プッシュできるようになります。

github.com

使い方

イメージのタグとECRリポジトリを指定することで、イメージに対応するインデックスをビルドし、同リポジトリにプッシュします。

以下は someRepository という名前のECRリポジトリの中の someTag タグがついたイメージに対して、インデックスを付加する例です:

import { Repository } from 'aws-cdk-lib/aws-ecr';
import { SociIndexBuild } from 'deploy-time-build;

new SociIndexBuild(this, 'Index', {
    imageTag: 'someTag', 
    repository: Repository.fromRepositoryName(this, "Repo", "someRepository") 
});

実用的には、CDKの DockerImageAsset に対してインデックスを付加したい場合が多いと思います。これも簡単に書けて、以下は example-image ディレクトリのDockerfileをビルドしつつ、そのイメージのインデックスを付加する例です:

import { DockerImageAsset } from 'aws-cdk-lib/aws-ecr-assets';

const asset = new DockerImageAsset(this, 'Image', { directory: 'example-image' });
SociIndexBuild.fromDockerImageAsset(this, 'Index', asset);

CDKのECSモジュールからコンテナイメージを参照する際は、AssetImage クラスを使うことも多いと思います。その場合は、先に定義した DockerImageAsset から AssetImage 作成すると良いでしょう:

import { AssetImage } from 'aws-cdk-lib/aws-ecs';
const assetImage = AssetImage.fromDockerImageAsset(asset);

SOCIインデックスがプッシュされてない状態では、ECSサービスをデプロイしたくない場合もあると思います。その場合は、ECSサービスのリソースに対する依存関係を設定すれば良いです:

import { FargateService } from 'aws-cdk-lib/aws-ecs';
const service = new FargateService(/* 略 */);
const index = SociIndexBuild.fromDockerImageAsset(/* 略 */);

// indexをデプロイした後にECSサービスをデプロイする
service.node.defaultChild!.node.addDependency(index);

Python CDKユーザーの人も同様に使えます:

pip install deploy-time-build

from deploy_time_build import SociIndexBuild

asset = DockerImageAsset(self, "Image", directory="example-image")
SociIndexBuild(self, "Index", image_tag=asset.asset_hash, repository=asset.repository)
SociIndexBuild.from_docker_image_asset(self, "Index2", asset)

上記が基本的な使い方となります。仕組みが気になる方は、次をご覧ください。

仕組み

CDKデプロイ時にCodeBuildプロジェクトのジョブを開始し、そのジョブ内でインデックスをビルド・プッシュしています。

CodeBuild上でSOCIインデックスをビルドするためにスタンドアロンなツールがほしかったので、soci-wrapperというCLIを作成・利用しています。これは上で紹介した cfn-ecr-aws-soci-index-builder のコードを流用しているため、生成物も同一になります。 このCLIの背景についてはこちらのIssueも参考になると思います: Ability to run soci create command in CodeBuild #760

理想的にはCDK CLIに同機能が組み込まれていると良いでしょう。(コンテナイメージ自体のビルドはローカルで行うため。)しかしながら、現状soci-snapshotterがLinux上でしか動かないことを考えると、Linux/Windows/Macで同様に動作する必要があるCDK CLIでは難しそうに思います。とりあえずIssueだけは立てています: core: push SOCI index when publishing docker image assets #26413

まとめ

AWS CDKから簡単にSOCIインデックスをデプロイするためのCDKコンストラクトライブラリを紹介しました。

昨日のJAWSコンテナ支部でSOCIインデックスが話題に出ていたので、半年前書きかけた記事を完成させられました。ありがとうございます!