🐳

SOCI Index Manifest v2の構造をのぞいてみる

に公開

バニッシュ・スタンダードでインフラを担当していますTmaと申します。
先日、コンテナイメージの遅延読み込みを実現する技術であるSeekable OCI (SOCI) のv2がリリースされました。SOCIは、コンテナ起動時にイメージ全体をダウンロードすることなく、必要なファイルだけをオンデマンドで取得することで、特に大規模なイメージの起動時間を劇的に短縮することを目的としています 。筆者は過去にSOCI v1の導入を検討しましたが、当時は明確な改善効果が見られませんでした。

SOCI v2導入検討を行う前に、v1と何が違うのかをみてみました。

SOCI v1とv2の基本的な違い

SOCI Index Manifest v2の詳細については下記が非常に詳しいです。
https://aws.amazon.com/jp/blogs/news/improving-amazon-ecs-deployment-consistency-with-soci-index-manifest-v2/

SOCI Indexとコンテナイメージが、イメージインデックスのアノテーションを介して相互に関連付けられていることを表しています。
コンテナイメージと SOCI Indexの双方向の関連付けにより、一貫性のあるコンテナの実行が可能となります。イメージインデックスを削除または更新しない限り、SOCI Indexを削除・更新することはできません。

SOCI v1のアーキテクチャには、以下のような制約がありました。

  • 一方向の関連付け: SOCI Indexが、そのsubjectフィールドで対象のコンテナイメージを一方的に参照する関係でした。
  • 一貫性の欠如: この一方向の関連性のため、デプロイメントの途中でSOCI Indexが意図せず変更・削除された場合、コンテナの起動挙動(遅延読み込みされるか、全ダウンロードされるか)に一貫性がなくなり、性能が不安定になるという問題がありました 。

SOCI v2では、この問題がイメージインデックスを介したアーキテクチャ変更によって解決されました。

  • 双方向の関連付け: イメージインデックスを中央ハブとして使用し、コンテナイメージのManifestとSOCI IndexのManifestを相互に参照します。
  • 堅牢な一貫性: 具体的には、イメージManifestがアノテーションで自身のSOCI Indexを指し、SOCI Indexもまたアノテーションで対象のイメージManifestを指します。この双方向の結合により、イメージインデックス自体を更新しない限り、SOCI Indexを個別に変更・削除することができなくなり、デプロイメントの一貫性が保証されます 。

そこで実際にSOCI Manifest v2で作成されたコンテナの内部構造を見てみようというのが本記事の趣旨になります。

SOCI Manifest v2 イメージを作る

Amazon Linux上でnginxを題材に試します。

soci-snapshotterのインストール

soci-snapshotterは、SOCI Indexを利用してコンテナの遅延読み込みを実現するcontainerdのプラグインです 。Index作成用のCLIツールも同梱されています。

curl -L -o soci-snapshotter.tar.gz  https://github.com/awslabs/soci-snapshotter/releases/download/v0.11.1/soci-snapshotter-0.11.1-linux-amd64.tar.gz
tar -C /usr/local/bin -xvf soci-snapshotter.tar.gz

soci-snapshotterの起動

Index作成プロセスで内部的に利用されるsoci-snapshotterを起動しておきます。

soci-snapshotter-grpc &

コンテナイメージの取得

nginxのイメージを落としてきます。

$ nerdctl pull nginx:latest

SOCI v2 イメージへの変換

sociコマンドでSOCI Manifest V2 Imageに変換します。

$ soci convert docker.io/library/nginx:latest docker.io/library/nginx:soci-v2
$ soci convert --namespace moby convert docker.io/library/nginx:latest docker.io/library/nginx:soci-v2 
ztoc skipped - layer sha256:ec5daaed1d0ab6f72e80b08a5799cab465017b6922afeb1871eca633697c91a3 (application/vnd.oci.image.layer.v1.tar+gzip) size 1398 is less than min-layer-size 10485760
...
ztoc skipped - layer sha256:1e537b66692c42a9806e45bef1e182661bd99a093f18008e05f9efd383f472ba (application/vnd.oci.image.layer.v1.tar+gzip) size 629 is less than min-layer-size 10485760
layer sha256:3da95a905ed546f99c4564407923a681757d89651a388ec3f1f5e9bf5ed0b39d -> ztoc sha256:339e425c4fcbb30ebc7967e3d2b37e8f045a8f894917672d480b805c3060e0ee
layer sha256:037111f539a0a1c8ea417a56a5f307a771b19d2c3ab17e7f95327ac16c398d2f -> ztoc sha256:bafc9ee602df50760bac6fce529e02256c882cfb0fcb6f6fbf286b3d8fb0ffcd

SOCI v2 イメージの内部構造

イメージをtarに固めて保存します。

$ nerdctl -n moby save -o nginx_v2.tar nginx:soci-v2

tarを展開するとこんな感じです。

$ tree
.
├── blobs
│   └── sha256
│       ├── 037111f539a0a1c8ea417a56a5f307a771b19d2c3ab17e7f95327ac16c398d2f
│       ├──... (多数のblobファイル)
│       ├── ec5daaed1d0ab6f72e80b08a5799cab465017b6922afeb1871eca633697c91a3
│       └── f0ebe04ed7bd59669fcca1607cb9d1951b8f6a149de2a0fc13704c5cf5a773bd
├── index.json
├── manifest.json
└── oci-layout

index.jsonをのぞいてみます。

$ cat index.json
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "digest": "sha256:f0ebe04ed7bd59669fcca1607cb9d1951b8f6a149de2a0fc13704c5cf5a773bd",
      "size": 10656,
      "annotations": {
        "io.containerd.image.name": "docker.io/library/nginx:soci-v2",
        "org.opencontainers.image.ref.name": "soci-v2"
      }
    }
  ]
}

こちらにはイメージインデックスのdigest f0ebe0... が記載されています。イメージインデックスはマルチアーキテクチャイメージのコンテナイメージを関連づける仕組みでもあります。
イメージインデックスの中身をのぞいてみます。

$ cat f0ebe04ed7bd59669fcca1607cb9d1951b8f6a149de2a0fc13704c5cf5a773bd | jq
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:0d65a51106bd268f2ac41c9ec5e34cc53213ee8048f53d647c07047f0612ee12",
      "size": 2063,
      "annotations": {
        "com.amazon.soci.index-digest": "sha256:4f0bb3f54aa10e38507f9aae2207d01fa0360a5d32d8d7dcf889d030e31d47db",
        "com.docker.official-images.bashbrew.arch": "amd64",
        "org.opencontainers.image.base.digest": "sha256:28c1e76b454bb65a9138a2562708e7fa3a937ae88a36b30fe8a12e3e1d447ce3",
        "org.opencontainers.image.base.name": "debian:bookworm-slim",
        "org.opencontainers.image.created": "2025-07-14T22:07:26Z",
        "org.opencontainers.image.revision": "b2faad22d5d15d966e46922033681639b2a6d6fa",
        "org.opencontainers.image.source": "https://github.com/nginx/docker-nginx.git#b2faad22d5d15d966e46922033681639b2a6d6fa:mainline/debian",
        "org.opencontainers.image.url": "https://hub.docker.com/_/nginx",
        "org.opencontainers.image.version": "1.29.0"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:35b47dc26ee0f0a0d04d338f62f5f254b4545f002663f4e206c64a84761e3b0b",
      "size": 841,
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "amd64",
        "vnd.docker.reference.digest": "sha256:b2002bf8c6ecf5702af03862b55c473b9816824c722907efbc26cbfe636ccddb",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },

.......(他のCPUアーキテクチャの指定)

{
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:669d0d6773b903622f9c0b83566d2a3dfab3d3ba27beb7d1d56e82c1b84f7677",
      "size": 2292,
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "s390x",
        "org.opencontainers.image.base.digest": "sha256:ccbf2c799c72de648f648c84a1f8ceebcbb1e2a1fe72b4993f6744767265fbb0",
        "org.opencontainers.image.base.name": "debian:bookworm-slim",
        "org.opencontainers.image.created": "2025-07-15T02:07:49Z",
        "org.opencontainers.image.revision": "b2faad22d5d15d966e46922033681639b2a6d6fa",
        "org.opencontainers.image.source": "https://github.com/nginx/docker-nginx.git#b2faad22d5d15d966e46922033681639b2a6d6fa:mainline/debian",
        "org.opencontainers.image.url": "https://hub.docker.com/_/nginx",
        "org.opencontainers.image.version": "1.29.0"
      },
      "platform": {
        "architecture": "s390x",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:c3cd643d08a6a3b1d1ea48d5363f71b36518bb7f86d71add2fe937f683cea60f",
      "size": 841,
      "annotations": {
        "com.docker.official-images.bashbrew.arch": "s390x",
        "vnd.docker.reference.digest": "sha256:669d0d6773b903622f9c0b83566d2a3dfab3d3ba27beb7d1d56e82c1b84f7677",
        "vnd.docker.reference.type": "attestation-manifest"
      },
      "platform": {
        "architecture": "unknown",
        "os": "unknown"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:4f0bb3f54aa10e38507f9aae2207d01fa0360a5d32d8d7dcf889d030e31d47db",
      "size": 1026,
      "annotations": {
        "com.amazon.soci.image-manifest-digest": "sha256:0d65a51106bd268f2ac41c9ec5e34cc53213ee8048f53d647c07047f0612ee12"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "artifactType": "application/vnd.amazon.soci.index.v2+json"
    }
  ]
}

途中省いていますが、様々なCPUアーキテクチャ毎のイメージが指定されています。IBM Zメインフレーム用の指定もあるんですね。驚きでした。

    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:0d65a51106bd268f2ac41c9ec5e34cc53213ee8048f53d647c07047f0612ee12",
      "size": 2063,
      "annotations": {
        "com.amazon.soci.index-digest": "sha256:4f0bb3f54aa10e38507f9aae2207d01fa0360a5d32d8d7dcf889d030e31d47db",
        "com.docker.official-images.bashbrew.arch": "amd64",
        "org.opencontainers.image.base.digest": "sha256:28c1e76b454bb65a9138a2562708e7fa3a937ae88a36b30fe8a12e3e1d447ce3",
        "org.opencontainers.image.base.name": "debian:bookworm-slim",
        "org.opencontainers.image.created": "2025-07-14T22:07:26Z",
        "org.opencontainers.image.revision": "b2faad22d5d15d966e46922033681639b2a6d6fa",
        "org.opencontainers.image.source": "https://github.com/nginx/docker-nginx.git#b2faad22d5d15d966e46922033681639b2a6d6fa:mainline/debian",
        "org.opencontainers.image.url": "https://hub.docker.com/_/nginx",
        "org.opencontainers.image.version": "1.29.0"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }

amd64用Image Manifest (0d65a5...): annotations内にcom.amazon.soci.index-digestというキーがあり、これが自身のSOCI Index Manifest (4f0bb3...)を指し示しています。これがイメージからIndexへの参照です。

    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:4f0bb3f54aa10e38507f9aae2207d01fa0360a5d32d8d7dcf889d030e31d47db",
      "size": 1026,
      "annotations": {
        "com.amazon.soci.image-manifest-digest": "sha256:0d65a51106bd268f2ac41c9ec5e34cc53213ee8048f53d647c07047f0612ee12"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "artifactType": "application/vnd.amazon.soci.index.v2+json"
    }

SOCI Index Manifest (4f0bb3...): artifactTypeフィールドがapplication/vnd.amazon.soci.index.v2+jsonに設定されており、これがSOCI v2 Indexであることを明確に示しています 。また、annotations内のcom.amazon.soci.image-manifest-digestが、自身がどのイメージManifest(0d65a5...)をIndexしているかを指し示しています。これがIndexからイメージへの参照です。

digestで指定されている4f0bb3... をみてみます。

$ cat  4f0bb3f54aa10e38507f9aae2207d01fa0360a5d32d8d7dcf889d030e31d47db | jq
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.amazon.soci.index.v2+json",
    "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
    "size": 2
  },
  "layers": [
    {
      "mediaType": "application/octet-stream",
      "digest": "sha256:339e425c4fcbb30ebc7967e3d2b37e8f045a8f894917672d480b805c3060e0ee",
      "size": 1356944,
      "annotations": {
        "com.amazon.soci.image-layer-digest": "sha256:3da95a905ed546f99c4564407923a681757d89651a388ec3f1f5e9bf5ed0b39d",
        "com.amazon.soci.image-layer-mediaType": "application/vnd.oci.image.layer.v1.tar+gzip"
      }
    },
    {
      "mediaType": "application/octet-stream",
      "digest": "sha256:bafc9ee602df50760bac6fce529e02256c882cfb0fcb6f6fbf286b3d8fb0ffcd",
      "size": 1224032,
      "annotations": {
        "com.amazon.soci.image-layer-digest": "sha256:037111f539a0a1c8ea417a56a5f307a771b19d2c3ab17e7f95327ac16c398d2f",
        "com.amazon.soci.image-layer-mediaType": "application/vnd.oci.image.layer.v1.tar+gzip"
      }
    }
  ],
  "annotations": {
    "com.amazon.soci.build-tool-identifier": "AWS SOCI CLI v0.2"
  }
}

このSOCI Index Manifestのlayersセクションには、ファイルシステムのレイヤーではなく、zTOC(seekable table of contents)と呼ばれるバイナリIndexファイルへの参照がリストされています 。

まとめ

  1. index.json は、全体の入り口であるイメージインデックスを指します。
  2. イメージインデックスは、複数のManifestを束ねる中央ハブで、この中に元のイメージManifest(例: amd64用)
    とSOCI Index Manifestの両方が含まれています。
  3. 元のイメージManifest内のアノテーション(com.amazon.soci.index-digest)が、同じリスト内にあるSOCI Index Manifestを「これが私のIndexです」と指し示します。
  4. SOCI Index Manifestは、自身の"レイヤー"として、実際の索引ファイルであるzTOCのリストを保持しています。

こうやってSOCI Indexとコンテナイメージが、イメージインデックスのアノテーションを介して相互に関連付けられているってことなんですね。

今回はコンテナイメージを展開してSOCIの構造を見てみました。実際に使ってみた改善効果もまた載せたいと思います。

株式会社バニッシュ・スタンダード

Discussion