Seekable OCI (SOCI) のご紹介

本記事は、[AWS Containers Advent Calendar 2023](https://qiita.com/advent-calendar/2023/aws-containers) の 2 日目 (12/2) のエントリーです。

## Seekable OCI (SOCI) とは？
コンテナイメージの遅延読み込みを行うテクノロジーです。

containerd では、コンテナのファイルシステムを管理するコンポーネントを snapshotter と呼びます。SOCI では、コンテナイメージの遅延読み込みを行うために containerd の snapshotter として [SOCI Snapshotter](https://github.com/awslabs/soci-snapshotter) を公開しています。先日、発表された [AWS Fargate で Seekable OCI を使用してコンテナ起動の高速化を実現](https://aws.amazon.com/jp/about-aws/whats-new/2023/07/aws-fargate-container-startup-seekable-oci/) では、Fargate の下回りでこの SOCI Snapshotter が利用されています。

なお、SOCI の発音は "so-CHEE" (ソーチー / ソチ) で、SOCI Snapshotter の GitHub リポジトリでも明記されています。

> "SOCI" is short for "Seekable OCI", and is pronounced "so-CHEE".

## コンテナイメージの遅延読み込み
containerd のデフォルト snapshotter である overlayfs は、コンテナイメージ全体を取得・解凍した後でコンテナを起動します。一方、SOCI snapshotter では、コンテナイメージ全体をダウンロードする前にコンテナを起動し、コンテナレジストリからコンテナイメージの一部（ファイル）を遅延読み込みします。こうすることで、特にイメージサイズの大きなコンテナに対して、従来よりもコンテナ起動時間を短縮することが可能です。

![lazy-loading.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79239/cc9a607c-0360-e14a-0214-5350347bbad6.png)

## SOCI index
コンテナイメージの遅延読み込みを行うためには、コンテナイメージ (イメージレイヤー) から任意のファイルを取得する必要があります。ただし、多くのコンテナイメージ (イメージレイヤー) は `.tar.gz` (gzip 圧縮された tar アーカイブ) で保管されているため、このままでは任意のファイルを取得することができません。SOCI では、SOCI index と呼ばれるメタデータをオリジナルのコンテナイメージに対して作成することで、コンテナイメージ (イメージレイヤー) から任意のファイルを取得可能にしています。

SOCI index とオリジナルのコンテナイメージの関係は次のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79239/1f7dfaab-38ac-bea7-d276-9c3287506197.png)

上記のように、SOCI index では、zTOC と呼ばれるメタデータをイメージレイヤーごとに作成しています。zTOC は、次の 2 つの要素で構成されます。

- Table of Contents (TOC)
    - イメージレイヤーに含まれるファイルのファイル名 / ファイルサイズのリスト
    - tar アーカイブ内で、そのファイルの位置を表すオフセット情報
- zinfo
    - ファイルと span を対応させる辞書情報
    - span: 独立して解凍可能な、gzip 圧縮された tar アーカイブのデータチャンク

イメージとしては、次のような関係です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79239/8875190a-1791-7fed-f344-2470acebcce2.png)

例として、上記における `foo.txt` を遅延読み込みする場合を考えます。

zTOC に含まれる TOC　により、`foo.txt` がどの span に含まれているのかを知ることができます。この例では、span N に含まれています。

次に、zinfo により span N がイメージレイヤー (layer.tar.gz) のどの位置に該当するのかというオフセット情報が得られるので、範囲指定した GET リクエスト (Range) で span N を取得します。zinfo には、解凍後の非圧縮状態での span が tar アーカイブのどの位置に該当するのかについても情報を持っているため、TOC が持つ「そのファイルの位置を表すオフセット情報」と併せて、解凍後の span N から `foo.txt` を取得します。

以上が、zTOC を利用した任意のファイル取得の流れです。SOCI snapshotter では、ファイルアクセスがあった場合と、バックグラウンドでの継続的な処理として span の取得が行われます。

ここまでの流れを、SOCI snapshotter がファイルを取得する際のステップとして整理すると次のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79239/29f4414c-3e2d-c5a6-df5d-c21c3540be85.png)

1. SOCI snapshotter がファイルを含むレイヤーを検索
2. イメージレイヤーの tar アーカイブ内でのファイルの位置を TOC から取得
3. オフセットと zInfo テーブルを使用して、そのファイルのデータを含む span の集合を特定
4. 必要な span だけをダウンロード・解凍し、ファイルのデータを取得

## ECS on Fargate での SOCI を利用した遅延読み込み
ECS on Fargate のタスクで SOCI を利用した遅延読み込みを行いたい場合、対象のコンテナに対して SOCI index を作成し、SOCI index を ECR private registry に保管しておくだけで OK です。

本機能のリリース当初は、遅延読み込みを行いたい ECS on Fargate タスクに含まれる「全てのコンテナ」に対して SOCI index を作成する必要がありましたが、[先日のアップデート](https://aws.amazon.com/jp/about-aws/whats-new/2023/11/aws-fargate-amazon-ecs-tasks-selectively-leverage-soci/) により遅延読み込みを行いたいコンテナに対してのみ SOCI index を作成すれば、タスク内の「SOCI index が存在するコンテナ」のみが遅延読み込みされるようになりました。
（このアップデートにより、Service Connect や ECS Runtime Monitoring のように「AWS 側が自動でサイドカーコンテナを追加する」場合でも、メインコンテナに対して遅延読み込みを行うことができるようになっています！）

したがって、ECS on Fargate で SOCI を利用した遅延読み込みを行う手順は、次のようになります。

1. 遅延読み込みをしたいコンテナイメージに対して SOCI index を作成する
1. 1 で作成した SOCI index を、ECR private registry のリポジトリに保存
1. 1 で SOCI index を作成したコンテナイメージ URI を指定し、ECS タスク定義を作成
1. 3 で作成した ECS タスク定義を利用して、ECS タスクを実行 (スタンドアロンタスク or サービス)

ここだけ見ると煩雑に思えますが、遅延読み込みを行わない場合でも 3 および 4 は実施するため、実際に必要となるのは「SOCI index の作成」と「SOCI index の保存」のみです。

これらの作業を手動で行う場合や CI/CD パイプラインに処理として組み込む場合、[SOCI Snapshotter](https://github.com/awslabs/soci-snapshotter) のリポジトリで提供している `soci` CLI が利用できます。`soci` CLI により、SOCI index の作成と ECR private リポジトリへの保存が行えます。

また、[CFN AWS SOCI Index Builder on AWS](https://aws-ia.github.io/cfn-ecr-aws-soci-index-builder/) で提供している CloudFormation テンプレートを利用することで、ECR private リポジトリへのイメージ push をトリガーに、自動で SOCI index の作成・保存を行う仕組みをデプロイすることもできます。仕組みとしては、以下のように ECR private リポジトリへのイメージ push をトリガーイベントとして、Lambda 関数で SOCI index の作成・保存を行っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79239/e0a72735-a09a-b250-8231-26fa0736b233.png)

## Deep dive: SOCI による遅延読み込み
SOCI Snapshotter の遅延読み込みでは、大きく以下の 2 つのタイミングでコンテナレジストリから span の取得を行います。

1. アプリケーションからファイルアクセスが発生し、そのファイルがローカルに存在しない場合
1. バックグラウンドプロセスによる定期処理

前者については、ファイルの実体がローカルに存在しない状況であるため、そのファイルを含む span の取得・解凍が完了するまでアプリケーションスレッドがブロックされます。そのため、"裏でファイルの実体を取得しているタイミング" の処理のみ、overlayfs snapshotter （= コンテナイメージ全体を取得・解凍した後にコンテナを起動する場合）と比較して処理に時間がかかる場合があります。

後者については、SOCI Snapshotter のバックグラウンドプロセスにより、ローカルに存在しない span の取得が順次行われています。そのため、すべてのファイル (span) がファイルアクセスのタイミングで取得されるのではなく、すでにローカルに存在している場合もあります。なお、現時点 (2023.12.01) での SOCI Snapshotter の最新バージョンである v0.4.0 では、バックグラウンドプロセスが取得する span の順序をユーザーが制御する仕組みは提供されていません。ただし、SOCI Snapshotter の[設計思想](https://github.com/awslabs/soci-snapshotter/blob/v0.4.0/docs/design-docs/README.md)としては「組み込むべき機能」として考えており、将来的には実装される予定となっています。 

## おわりに
本記事では、コンテナイメージの遅延読み込みを行うテクノロジーである Seekable OCI (SOCI, ソーチー / ソチ) についてご紹介しました。

巨大なコンテナイメージのため起動時間の長さに悩んでいるワークロードをお持ちの場合、ぜひ SOCI による遅延読み込みの活用をご検討ください。ご意見・ご要望などは GitHub で公開している[コンテナロードマップ](https://github.com/aws/containers-roadmap)にお寄せいただけますと幸いです :raised_hands: