SOCIとは?
SOCI(Seekable OCI)はAWSが開発しているコンテナイメージの遅延読み込みのための仕組みです。コンテナイメージには起動時には使わないデータも多く含まれています。遅延読み込みによって、起動時には最小限のデータのみ読み込んで起動時間を短縮することを目的としています。コンテナ起動時の遅延読み込み自体は以前から存在しており、有名なものにestargzがあります。estargzはコンテナイメージの中に遅延読み込みのためのインデックスを保存しますが、SOCIはインデックスをOCI Artifactsとしてレジストリに保存します。このため、既存のイメージに手を入れる必要がないという特徴があります。
AWS ECS FargateはSOCIをサポートしており、遅延読み込みによりコンテナ起動速度の向上を図れます。
詳解 : Seekable OCI を使用した AWS Fargate におけるコンテナイメージの遅延読み込み
SOCIの作成
SOCIを利用するにはインデックスデータが必要です。AWS公式のインデックス作成ツールはSOCI Index Builderとsoci-snapshotterがあります。
SOCI Index BuilderはECRにイメージをpushすると、EventBridgeトリガーLambdaが起動し、インデックスを生成します。CloudFormationテンプレートが提供されているので構築の手間はかかりませんが、管理し続けるのも煩わしいので、今回はsoci-snapshotterを利用して、GitHub Actions内でコンテナビルドと同時に行います。
SOCI Index Builderを使ったSOCIインデックスの生成は、AWSが公式ドキュメントを提供しているので、参考にしてください。AWS Fargate はシーク可能な OCI を使用してより高速なコンテナ起動を可能に
soci-snapshotterの利用要件
soci-snapshotterを利用するためにはcontainerd >= 1.4が必要です。GitHub ActionsはDockerが使われるので、そのままでは利用できません。そこで以下の流れを取ります。
- Dockerイメージをビルドし、tar形式で出力
- containerdにtar形式のイメージをインポート
- soci-snapshotterでSOCIインデックスを生成
- イメージとインデックスをレジストリにpush
はじめからDockerを使わずcontainerdでビルドしてもよいですが、Dockerでビルドする方が既存のActionを利用できたりと何かと便利かと思います。
GitHub Actionsへのcontainerd導入
Actions Marketplaceでghaction-setup-containerdというそのままなワークフローが提供されているので、これを利用します。基本的にはオプションなど不要で、そのまま書けば動きます。
Dockerfileはなんでもいいので、下記のcowsayコンテナをにしました。
FROM debian:bookworm-slim RUN apt-get update && apt-get install -y cowsay --no-install-recommends && rm -rf /var/lib/apt/lists/* ENV PATH $PATH:/usr/games CMD ["cowsay"]
containerdに出力したい場合のdocker build
Dockerのイメージストアではなく、containerdに格納する場合、docker buildで-o type=oci
オプションを利用し、OCI Exporterを用いて、出力先をtar一時ファイルにします。この部分はcrazy-max/ghaction-setup-containerd
のREADMEをほぼそのまま流用しました。
- name: Build Docker images uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile tags: ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }}:1 outputs: type=oci,dest=/tmp/image.tar
containerdへのインポートとSOCIインデックスの生成
containerdにインポート後、soci create
コマンドを実行するとSOCIインデックスが生成されます。そしてsoci push
コマンドでインデックスをレジストリにpushします。
コンテナイメージのpushはctrコマンドを利用します。
- name: Import image in containerd run: sudo ctr i import --base-name ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }} --digests --all-platforms /tmp/image.tar - name: Create soci index run: sudo soci create ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }}:1 - name: Push image with containerd run: sudo ctr i push --user AWS:$(aws ecr get-login-password --region ap-northeast-1) ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }}:1 - name: push SOCI index run: sudo soci push --user AWS:$(aws ecr get-login-password --region ap-northeast-1) ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }}:1
Actions workflowの全体はこちらにあります。
Docker >= 24.0のcontainerd統合を利用したビルド
Docker 24.0で実験的機能としてイメージストアにcontainerdが利用可能になりました。実験的機能なので安定していない可能性がありますが、これを使うともっと手軽にSOCIインデックスを作成できます。
GitHub ActionsではUbuntu 22.04, 20.04ともに20230821.1.0以降のランナーイメージでDocker 24.0がインストールされています。
containerd image store with Docker Engine
Dockerのcontainerd統合を有効化
/etc/docker/daemon.json
ファイルで以下のようにフィーチャーフラグを設定し、dockerを再起動すると、containerdが使われるようになります。
{ "features": { "containerd-snapshotter": true } }
Actionでは以下のように記述しています。
- name: Use containerd as a docker imagestore run: | echo '{"features":{"containerd-snapshotter":true}}' | jq | sudo tee /etc/docker/daemon.json sudo systemctl restart docker sudo docker system info
containerd統合環境でのdocker build
docker build
が2回並んでいますがが、間違いではありません。1回目はイメージのビルドとDockerイメージストアへの読み込み、2回目はECRへのpushです。buildxの仕様上、--load
オプションを使わないとイメージがローカルDockerに保存されません。soci
はローカルのイメージを使ってインデックスを生成するので、一度ローカルに保存するためにビルドしています。
2回目のdocker build
でレジストリにpushされますが、2回目はレイヤーキャッシュがそのまま残っているため、実際のビルド時間はほとんどかかりません。
- name: Build Docker images uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile tags: ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }}:2 load: true - name: Build Docker images uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile tags: ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }}:2 push: true
docker buildが完了したらSOCIインデックスを作成し、pushします。この方法ではDockerから直接containerdが使われるので、改めてのインポートは不要です。この時--namespace moby
でDockerが使っているnamespaceの指定します。
READMEには事前にdocker login
すればsoci push
の--user
オプションは不要とあったのですが、うまく動いてくれなかったので、aws ecr get-login-password --region ap-northeast-1
コマンドで認証情報を生成しています。
- name: Create soci index run: sudo soci --namespace moby create ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }}:2 - name: push SOCI index run: sudo soci --namespace moby push --user AWS:$(aws ecr get-login-password --region ap-northeast-1) ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY_NAME }}:2
workflowの全体はこちらにあります。
ECRでのSOCIインデックス確認
上記の手順でイメージとインデックスを作成した後、ECRのイメージリストを確認すると、以下のようにイメージと合わせて"SOCI Index"が追加されていることがわかります。
Fargateはインデックスがレジストリに追加されていれば自動的に利用するため、必要な作業はこれで完了です。
Dockerのcontainerd統合機能を使う方がシンプルでよいですが、まだ実験的な実装なので安定性は疑問です。正式リリースされたらcontainerd統合が便利なので、containerd統合の正式リリースが楽しみですね。
実際に起動して起動時間を比較
SOCIを使うとどのくらい高速化するのでしょうか。AWSによればおおむね250MiB以上のイメージなら高速化メリットがあるようです。coswayは小さすぎるので、Elasticsearchイメージでテストします。Elasticsearchイメージはarm64アーキテクチャで400MB以上あります。
ElasticsearchイメージをPrivate ECRにpushして、SOCIインデックスの有無で起動時間を比較します。Fargate Taskで起動して起動時間(startedAt - createdAt)を集計しています。きちんと条件を揃えたり統計を取っているわけではないので、参考値としてみてください。
- オリジナル: 35秒程度
- SOCI有効: 17秒程度
比較してみると、SOCIを有効化することでタスク起動時間が約半分になりました。既存のコンテナイメージに手を入れずに起動時間を半分にできるのは効果が大きいですね。ここまで都合よく性能が向上するとは限りませんが、導入工数も既存のシステムに与える影響も小さいので一度検証してみてはいかがでしょうか。
担当:松浦
備考
この記事で使われているワークフローやDockerfileは下記リポジトリに置いてます。