KAKEHASHI Tech Blog

カケハシのEngineer Teamによるブログです。

ECS起動を高速化するSeekable OCI(SOCI)インデックスをGitHub Actionsでも作る

SOCIとは?

SOCI(Seekable OCI)はAWSが開発しているコンテナイメージの遅延読み込みのための仕組みです。コンテナイメージには起動時には使わないデータも多く含まれています。遅延読み込みによって、起動時には最小限のデータのみ読み込んで起動時間を短縮することを目的としています。コンテナ起動時の遅延読み込み自体は以前から存在しており、有名なものにestargzがあります。estargzはコンテナイメージの中に遅延読み込みのためのインデックスを保存しますが、SOCIはインデックスをOCI Artifactsとしてレジストリに保存します。このため、既存のイメージに手を入れる必要がないという特徴があります。

AWS ECS FargateはSOCIをサポートしており、遅延読み込みによりコンテナ起動速度の向上を図れます。

詳解 : Seekable OCI を使用した AWS Fargate におけるコンテナイメージの遅延読み込み

SOCIの作成

SOCIを利用するにはインデックスデータが必要です。AWS公式のインデックス作成ツールはSOCI Index Buildersoci-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 24.0 Release notes

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は下記リポジトリに置いてます。

soci-test