🪅

ORAS で OCI アーティファクトを扱う

2024/09/01に公開

ORAS とは

The OCI Registry as Storage (ORAS) は OCI Artifact を扱うためのクライアントツールで、 CNCF の Sandbox プロジェクトとなっています。

https://oras.land/

Docker CLI を使ってローカル環境とリモートのコンテナレジストリ間でコンテナイメージを push/pull できるように、ORAS はローカル環境とリモートのレジストリ間で OCI アーティファクト を push/pull できるように設計されたクライアントツール (CLI) となっています。

ドキュメントに OCI アーティファクトの説明や ORAS コマンドの使用例がいくつか載っていますが、OCI アーティファクトに馴染みがないと一読しただけではどのように使うか分かりづらい部分もあります。そのため、ここでは OCI アーティファクトの説明を混ぜつつ ORAS でどのようなことがことができるか見てみます。
また、私自身 OCI アーティファクトに慣れていない部分もあるので備忘録も兼ねています。

OCI Artifact と OCI Artifact Registry

ORAS Introduction に書いてあることの簡単なまとめ
Introduction では OCI アーティファクトの必要性や経緯について簡単に書いてあります。

まだ OCI アーティファクト仕様が決まってなかった頃は、 Dockerfile でイメージ内に pdf やテキスト、バイナリなどを配置してそのイメージをコンテナレジストリに push することで、イメージ自体に関係ある/なしに関わらず任意のファイルを含めることができました(今もできますが)。
しかしこの方法は正当な方法とは言い難くファイル自身の管理に難がある、イメージに不要なファイルを含むためサイズが肥大するなどのいろいろな問題があったため、より正当な方法で特定のガイドラインに沿った方法でファイル等を共有できるような仕様を定めました。Open Container Initiative (OCI) によって定められたこの仕様を OCI Image Manifest Specification、OCI イメージマニフェストの仕様に準拠して管理されるファイルやメタデータのまとまりを OCI Artifact と呼びます。また、OCI マニフェストを管理できるように設定されたコンテナレジストリのことを OCI Artifact Registry と呼びます。OCI アーティファクトを扱うことができる他のツールも既にありますが、ORAS は OCI アーティファクトに焦点を当ててより容易に管理することを目的に開発されています。

What ORAS does differently is shift the focus from container images to other types of artifacts.

Registries supporting OCI Artifacts に一例がある通り主要なクラウドプロバイダーや OSS のコンテナレジストリでは現時点で OCI アーティファクトをサポートしているため、「OCI Artifact Registry は OCI アーティファクトを管理できるように拡張されたコンテナレジストリを指す」という認識でひとまず問題ないかと思います。

Docker イメージの OCI レイアウトの調査

ORAS で OCI アーティファクトを扱う前に OCI アーティファクトの構造について簡単に見ておきます。Understanding OCI artifacts に OCI アーティファクトの構造を図示したものがあるので下図に引用します。


OCI アーティファクトの構造 (Understanding OCI artifacts より引用)。index, manifest, blob の入れ子構造で構成される。

OCI アーティファクトは上記のように Index, Manifest, Blob の入れ子構成になっています、index と manifest はどのようなアーティファクトが入っているかを表す json 形式のメタデータであり、Blob が実際にアーティファクト内に保存されるファイル等となっています。
とはいえこれだけだと具体的なイメージが湧きづらいので、ここでは dockerhub 上に存在する nginx のイメージを例にとって実際に OCI アーティファクトの構造を見ていきます。

nginx イメージは amd64, arm, s390 など複数のアーキテクチャに対応しているため、docker manifest inspect nginx でマニフェストを参照するとまず複数のアーキテクチャの manifest list が記載された情報が取得できます。これが OCI アーティファクトにおける Index に対応しており、manifests[] 以下に各アーキテクチャに対応する個々のマニフェストが記載されています。

  • mediaType: マニフェストの形式。マニフェスト自体は json 形式となので +json となっている。OCI Image Media Types に使用可能な値の一覧がある。
  • digest: マニフェストの digest
  • size: マニフェストのサイズ (byte)
  • platform: os やアーキテクチャの情報が記載されている。
`docker manifest inspect nginx` の結果
index
schemaVersion: 2
mediaType: application/vnd.oci.image.index.v1+json
manifests:
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2295
    digest: sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
    platform:
      architecture: amd64
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:2b7d6ce2be05018291aaa1979ccf1e744fa7082eea89142faac3dd067607875e
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2297
    digest: sha256:6289301f3b918b98577fe0f80c13761aae5791b0059ed9e1d554edeae7c54b82
    platform:
      architecture: arm
      os: linux
      variant: v5
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:3e576b43defa219d279961a042477c8df1b2e46d15ec8cd98827eae750e33fa6
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2297
    digest: sha256:4bcb64ff3494e929c732a97eacef3fddc8ab27347f23e12d7e6ffc2e2cecb0a2
    platform:
      architecture: arm
      os: linux
      variant: v7
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:1fb32bf81384775941104a4d95d6303586ff0699b31182de738c2157b211ef1b
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2297
    digest: sha256:bab0713884fed8a137ba5bd2d67d218c6192bd79b5a3526d3eb15567e035eb55
    platform:
      architecture: arm64
      os: linux
      variant: v8
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:9b1e294be5c53a9edbb775540981332a842602564ab184cf6c775f9a2b0f7688
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2294
    digest: sha256:09279664808b1f74e3698a901d94d9640f11af5986b85c3b7ca7944a7dded1be
    platform:
      architecture: "386"
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:a46d7c4bd192f2e0ee360619ea657b141a5667d9daee98d6ff5accc193a868e2
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2298
    digest: sha256:41852102bec70d119236a95437f8a98e11d8404e50d3b2eaf6942d725f037df7
    platform:
      architecture: mips64le
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 567
    digest: sha256:f2917769e890c8c9faf9c22ef264fec8d5a0daf27b4777fb7f2fba7e35d0173c
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2297
    digest: sha256:822ae767e1045bf2818edf6929c8b1fb89687b9a8d33e809287842ed443b77d7
    platform:
      architecture: ppc64le
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:0ccc36a3515f0d50aa41079d38c75ead02799c48caa58a4117968c9a74a46e69
    platform:
      architecture: unknown
      os: unknown
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 2295
    digest: sha256:612962b8456a7aff0fe6b18d7313bb90e44b7b34aced6ce432543884886cc3a4
    platform:
      architecture: s390x
      os: linux
  - mediaType: application/vnd.oci.image.manifest.v1+json
    size: 841
    digest: sha256:cff2127d747263db959a262b1fe407cee4b1881946cb6dd0287e35739e36ead4
    platform:
      architecture: unknown
      os: unknown

ちなみにコマンド実行時に -v をつけることで各アーキテクチャのマニフェストの内容を合わせて取得できます。
例えば以下の内容では OCIManifest 以下が amd64 のアーキテクチャのマニフェストとなっており、図中の Manifest に対応しています。

`docker manifest inspect nginx -v` の結果の一部
- Ref: docker.io/library/nginx:latest@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
  Descriptor:
    mediaType: application/vnd.oci.image.manifest.v1+json
    digest: sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
    size: 2295
    platform:
      architecture: amd64
      os: linux
  Raw: ewogICJzY2hlbWFWZXJzaW9uIjogMiwKICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsCiAgImNvbmZpZyI6IHsKICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5jb25maWcudjEranNvbiIsCiAgICAiZGlnZXN0IjogInNoYTI1Njo1ZWY3OTE0OWUwZWM4NGE3YTlmOTI4NGMzZjkxYWEzYzIwNjA4ZjgzOTFmNTQ0NWVhYmU5MmVmMDdkYmRhMDNjIiwKICAgICJzaXplIjogNzQ4NgogIH0sCiAgImxheWVycyI6IFsKICAgIHsKICAgICAgIm1lZGlhVHlwZSI6ICJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLmxheWVyLnYxLnRhcitnemlwIiwKICAgICAgImRpZ2VzdCI6ICJzaGEyNTY6ZTRmZmYwNzc5ZTZkZGQyMjM2NjQ2OWYwODYyNmMzYWIxODg0YjVjYmUxNzE5YjI2ZGEyMzhjOTVmMjQ3YjMwNSIsCiAgICAgICJzaXplIjogMjkxMjYyMzIKICAgIH0sCiAgICB7CiAgICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5sYXllci52MS50YXIrZ3ppcCIsCiAgICAgICJkaWdlc3QiOiAic2hhMjU2OjJhMGNiMjc4ZmQ5Zjc3MzdlZjVkZGM1MmI0MTk4ODIxZGQwMmU4N2VkMjA0Zjc0ZDdlNDkxMDE2Yjk2ZWJlN2YiLAogICAgICAic2l6ZSI6IDQxODc1NzgyCiAgICB9LAogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1Njo3MDQ1ZDZjMzJhZTJkM2RjMDAyZjMzYmViMGMxY2RkN2Y2OWIyNjYzYTk3MjAxMTdhYzliODJlYzI4ODY1ZTMwIiwKICAgICAgInNpemUiOiA2MjcKICAgIH0sCiAgICB7CiAgICAgICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9jaS5pbWFnZS5sYXllci52MS50YXIrZ3ppcCIsCiAgICAgICJkaWdlc3QiOiAic2hhMjU2OjAzZGUzMWFmYjAzNTczZTBmYTY3OWQ2Nzc3YmEzMjY3YzJiOGVjMDg3Y2JjMGVmYTQ2NTI0YzFkZTA4ZjQzZWMiLAogICAgICAic2l6ZSI6IDk1NgogICAgfSwKICAgIHsKICAgICAgIm1lZGlhVHlwZSI6ICJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLmxheWVyLnYxLnRhcitnemlwIiwKICAgICAgImRpZ2VzdCI6ICJzaGEyNTY6MGYxN2JlOGRjZmYyZTJjMjdlZTZhMzNjMWJhY2M1ODJlNzFmNzZmODU1YzJkNjlkNTEwZjJhOTNkZjg5NzMwMyIsCiAgICAgICJzaXplIjogMzk0CiAgICB9LAogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoxNGI3ZTVlOGYzOTQ2ZGEwZjkxMjBkYWIzYjBlMDVlZjI0YTVjYTg3NGJhNDg0MzI3ZGI4YjMzMDhhOTJiNTMyIiwKICAgICAgInNpemUiOiAxMjEwCiAgICB9LAogICAgewogICAgICAibWVkaWFUeXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubGF5ZXIudjEudGFyK2d6aXAiLAogICAgICAiZGlnZXN0IjogInNoYTI1NjoyM2ZhNWE3Yjk5YTY4NTI1ODg4NTkxOGM0NjhkZWQwNDJiOTViNWE3YzU2Y2VlNzU4YTY4OWY0ZjdlNTk3MWUwIiwKICAgICAgInNpemUiOiAxMzk4CiAgICB9CiAgXSwKICAiYW5ub3RhdGlvbnMiOiB7CiAgICAiY29tLmRvY2tlci5vZmZpY2lhbC1pbWFnZXMuYmFzaGJyZXcuYXJjaCI6ICJhbWQ2NCIsCiAgICAib3JnLm9wZW5jb250YWluZXJzLmltYWdlLmJhc2UuZGlnZXN0IjogInNoYTI1Njo3MGQ0YzA0MzAyYmRjZDcxYzRmYTIxYjZjMTJlOTk4ODgzODBhMDdmMDRlM2Q0NDQ1MmI5NjFiY2EwNDY0ODlkIiwKICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYmFzZS5uYW1lIjogImRlYmlhbjpib29rd29ybS1zbGltIiwKICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuY3JlYXRlZCI6ICIyMDI0LTA4LTE0VDIxOjMxOjEyWiIsCiAgICAib3JnLm9wZW5jb250YWluZXJzLmltYWdlLnJldmlzaW9uIjogImU3OGNmNzBjZTdiNzNhMGM5ZWE3MzRjOWNmOGFhYWEyODNjMWNjNWEiLAogICAgIm9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5zb3VyY2UiOiAiaHR0cHM6Ly9naXRodWIuY29tL25naW54aW5jL2RvY2tlci1uZ2lueC5naXQjZTc4Y2Y3MGNlN2I3M2EwYzllYTczNGM5Y2Y4YWFhYTI4M2MxY2M1YTptYWlubGluZS9kZWJpYW4iLAogICAgIm9yZy5vcGVuY29udGFpbmVycy5pbWFnZS51cmwiOiAiaHR0cHM6Ly9odWIuZG9ja2VyLmNvbS9fL25naW54IiwKICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVyc2lvbiI6ICIxLjI3LjEiCiAgfQp9
  OCIManifest:
    schemaVersion: 2
    mediaType: application/vnd.oci.image.manifest.v1+json
    config:
      mediaType: application/vnd.oci.image.config.v1+json
      digest: sha256:5ef79149e0ec84a7a9f9284c3f91aa3c20608f8391f5445eabe92ef07dbda03c
      size: 7486
    layers:
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:e4fff0779e6ddd22366469f08626c3ab1884b5cbe1719b26da238c95f247b305
        size: 29126232
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:2a0cb278fd9f7737ef5ddc52b4198821dd02e87ed204f74d7e491016b96ebe7f
        size: 41875782
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:7045d6c32ae2d3dc002f33beb0c1cdd7f69b2663a9720117ac9b82ec28865e30
        size: 627
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:03de31afb03573e0fa679d6777ba3267c2b8ec087cbc0efa46524c1de08f43ec
        size: 956
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:0f17be8dcff2e2c27ee6a33c1bacc582e71f76f855c2d69d510f2a93df897303
        size: 394
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:14b7e5e8f3946da0f9120dab3b0e05ef24a5ca874ba484327db8b3308a92b532
        size: 1210
      - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
        digest: sha256:23fa5a7b99a685258885918c468ded042b95b5a7c56cee758a689f4f7e5971e0
        size: 1398
    annotations:
      com.docker.official-images.bashbrew.arch: amd64
      org.opencontainers.image.base.digest: sha256:70d4c04302bdcd71c4fa21b6c12e99888380a07f04e3d44452b961bca046489d
      org.opencontainers.image.base.name: debian:bookworm-slim
      org.opencontainers.image.created: "2024-08-14T21:31:12Z"
      org.opencontainers.image.revision: e78cf70ce7b73a0c9ea734c9cf8aaaa283c1cc5a
      org.opencontainers.image.source: https://github.com/nginxinc/docker-nginx.git#e78cf70ce7b73a0c9ea734c9cf8aaaa283c1cc5a:mainline/debian
      org.opencontainers.image.url: https://hub.docker.com/_/nginx
      org.opencontainers.image.version: 1.27.1
...

次に manifest より下の構造を見ていくために amd64 の PC 上でイメージを pull してローカルに展開します。

# イメージをダウンロード
$ docker pull nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
docker.io/library/nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f: Pulling from library/nginx
e4fff0779e6d: Already exists
2a0cb278fd9f: Pull complete
7045d6c32ae2: Pull complete
03de31afb035: Pull complete
0f17be8dcff2: Pull complete
14b7e5e8f394: Pull complete
23fa5a7b99a6: Pull complete
Digest: sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
Status: Downloaded newer image for nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f
docker.io/library/nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f


# イメージを nginx.tar に出力
$ docker save nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f > nginx.tar

# 展開
$ mkdir test
$ tar xvf nginx.tar -C test

test ディレクトリに展開したファイルを見てみると blobs ディレクトリや oci-layout などのファイルが存在していることがわかります。
このように複数の blobs と各 json 形式を含むディレクトリ構成を OCI Layout または OCI Image Layout とよびます。oci-layout が満たすべき基準や各ファイルの説明は OCI Image Layout Specification で決められています。

tree の実行結果
.
├── blobs
│   └── sha256
│       ├── 228a13938b2b1dd85f37d629df6e0cff088dd83b31bc9e9b7f19bdc2a8bc8e2d
│       ├── 23f842574fa2f03da3f7ac8d8f3c6d94c66d76cc00fc982dd9470c83f0049fa2
│       ├── 55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d
│       ├── 5ba9eb7201206bb0a7bef734333205a60a5466b0badf49d2a8841448c44d939b
│       ├── 5ef79149e0ec84a7a9f9284c3f91aa3c20608f8391f5445eabe92ef07dbda03c
│       ├── 5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
│       ├── 72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
│       ├── 7d2ee22b1699d37a35181e47d42a275803f3acf547c22a41bb8a53891c7b4f2c
│       ├── 8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0
│       ├── 97cfa79367882fde35f0089339609799c3e98fb79d7a4b241d1c2b6ed58b7d56
│       ├── 9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
│       ├── c20af9497622696cbac2919bd19bcf67672d012b74cc0408acb178b688ac8f0d
│       ├── dd8a2508f00668508e4fe4bd8030fec6e4262103a239404daa8386f1c6537797
│       ├── ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8
│       ├── f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
│       └── f5e50abe9b0f6d2fe7532dc473c0639d9f1729455dd2c9b3bce575f705d20e80
├── index.json
├── manifest.json
└── oci-layout

index.json は index の内容になっています。OCI Image Index Specification で定義されています。

index.json
schemaVersion: 2
mediaType: application/vnd.oci.image.index.v1+json
manifests:
  - mediaType: application/vnd.oci.image.manifest.v1+json
    digest: sha256:7d2ee22b1699d37a35181e47d42a275803f3acf547c22a41bb8a53891c7b4f2c
    size: 1307

manifest.json はこの amd64 のマニフェストにどのような情報が保存されているかが記載されており、LayerSources に各 blobs のサイズ等の情報が含まれています。Dockerfile の文脈ではよく レイヤー の話が出てきますが、それが以下の Layers に対応しています。

manifest.json
- Config: blobs/sha256/5ef79149e0ec84a7a9f9284c3f91aa3c20608f8391f5445eabe92ef07dbda03c
  RepoTags: null
  Layers:
    - blobs/sha256/9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
    - blobs/sha256/72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
    - blobs/sha256/8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0
    - blobs/sha256/ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8
    - blobs/sha256/55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d
    - blobs/sha256/f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
    - blobs/sha256/5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
  LayerSources:
    sha256:55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 2560
      digest: sha256:55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d
    sha256:5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 7168
      digest: sha256:5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
    sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 113963520
      digest: sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
    sha256:8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 3584
      digest: sha256:8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0
    sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 77832704
      digest: sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
    sha256:ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 4608
      digest: sha256:ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8
    sha256:f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 5120
      digest: sha256:f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344

Blob の中身

blobs ディレクトリ内の各 digest は layer に対応する実際のファイルやディレクトリとなっています。今見ている nginx の debian ベースの Dockerfile は以下にソースコードがあります。
https://github.com/nginxinc/docker-nginx/blob/master/mainline/debian/Dockerfile

manifest.json 内では Layers と LayerSources が 7 つありますが、Dockerfile では RUN, COPY が合わせて 6 つあるのでそれぞれに対応していることがわかります(残り 1 つはベースイメージの debian:bookworm-slim に対応)。

例えば、Dockerfile の以下の部分ではそれぞれのシェルスクリプトを /docker-entrypoint.d 以下にコピーしています。

Dockerfile
COPY 20-envsubst-on-templates.sh /docker-entrypoint.d
COPY 30-tune-worker-processes.sh /docker-entrypoint.d

この処理で 5, 6 つめのレイヤーが作成されるので中身を見てみます。
docker image inspect nginx@[digest] でレイヤーの digest を確認。

$ docker image inspect nginx@sha256:5f0574409b3add89581b96c68afe9e9c7b284651c3a974b6e8bac46bf95e6b7f | yq -P
- Id: sha256:5ef79149e0ec84a7a9f9284c3f91aa3c20608f8391f5445eabe92ef07dbda03c
  ...
  RootFS:
    Type: layers
    Layers:
      - sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
      - sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
      - sha256:8b87c0c6652495401acfbe029ede84a5f327664770561ba5f8b7fe9149f52dd0
      - sha256:ec1a2ca4ac8784def146544fc7068db06a188d2da4fd7c4e134a76415b8bc1a8
      - sha256:55e54df86207fa772302a6fc1e78eb60bd7e3ebd4f913ef7f5ad668ad69ab64d
      - sha256:f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
      - sha256:5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a

sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034 はベースイメージの debian のレイヤーなので除外すると、5 つめのレイヤーの digest は sha256:f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344 であることがわかります。
blob ディレクトリに含まれるファイルは tar 形式となっており、cat 等で確認すると実際にコンテナ内に配置される 20-envsubst-on-templates.sh の内容が確認できます。

f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
$ file f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344: POSIX tar archive

$ cat f4f00eaedec7933a48b09f3948c685c41d55f0bf5906295dd022c05b65082344
docker-entrypoint.d/0000755000000000000000000000000014657440731013161 5ustar0000000000000000docker-entrypoint.d/20-envsubst-on-templates.sh0000755000000000000000000000571214657440704020223 0ustar0000000000000000#!/bin/sh

set -e

ME=$(basename "$0")

entrypoint_log() {
    if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
        echo "$@"
    fi
...

同様に、6 つめのレイヤー sha256:5f027... では 30-tune-worker-processes.sh の中身が確認できます。

5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
$ cat 5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a
docker-entrypoint.d/0000755000000000000000000000000014657440731013161 5ustar0000000000000000docker-entrypoint.d/30-tune-worker-processes.sh0000755000000000000000000001101314657440704020222 0ustar0000000000000000#!/bin/sh
# vim:sw=2:ts=2:sts=2:et

set -eu

LC_ALL=C
ME=$(basename "$0")
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

[ "${NGINX_ENTRYPOINT_WORKER_PROCESSES_AUTOTUNE:-}" ] || exit 0
...

また、tar を展開することで実際に /docker-entrypoint.d 以下にシェルスクリプトが配置されていることが確認できます。

$ mkdir test
$ tar xvf 5f0272c6e96d5cd8ea1c6507cfce81980d4b99322bd037d99250a79d4c0b9f1a -C test
docker-entrypoint.d/
docker-entrypoint.d/30-tune-worker-processes.sh

$ tree test
test
└── docker-entrypoint.d
    └── 30-tune-worker-processes.sh

manifest の中で最も size の大きいレイヤー 72db5... は一見するとベースイメージの debian レイヤーに対応してそうですが、レイヤーの top は 98535... なのでこちらが debian のレイヤー、72db5... は nginx Dockerfile の最初の RUN に対応するレイヤーであることがわかります。

  Layers:
    - blobs/sha256/9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
    - blobs/sha256/72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
    - ...
  LayerSources:
    sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 113963520
      digest: sha256:72db5db515fdd9ae82b759fc207fdfbcc31567c28bb87950abc94ce1d60b2d40
    sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034:
      mediaType: application/vnd.oci.image.layer.v1.tar
      size: 77832704
      digest: sha256:9853575bc4f955c5892dd64187538a6cd02dba6968eba9201854876a7a257034
    sha256:ec1a2ca4ac87

Dockerfile を見ると RUN でパッケージのインストールなどいろいろな処理を行っていることでサイズが増えているようです。また、blobs ディレクトリの中にはマニフェストの Layers に表示されていない digest もありますが、これらはベースイメージである debian イメージに関連するレイヤーとなっています。

ORAS で OCI Artifact を push/pull する

普段使っている docker イメージの内部構造を見て OCI アーティファクトの構造についてある程度イメージがつかめたので、本題に戻って ORAS で扱う OCI アーティファクトの内容を見ていきます。

まずは Quick startHow-to Guides Pushing and Pulling に沿ってアーティファクトを作成しますが、アーティファクトを push/pull 保存するためにアーティファクトレジストリが必要となるので zot を docker で起動します。

docker run -d -p 5000:5000 --name zot ghcr.io/project-zot/zot-linux-amd64:latest

quickstart の通り artifact.txt のテキストファイルのみを含む単純なアーティファクトを作成して zot に push します。

$ echo "hello world" > artifact.txt
$ oras push localhost:5000/hello-artifact:v1 \
./artifact.txt
✓ Exists    application/vnd.oci.empty.v1+json                                                                                                                                                                                                   2/2  B 100.00%     0s
  └─ sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
✓ Exists    artifact.txt                                                                                                                                                                                                                      12/12  B 100.00%     0s
  └─ sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
✓ Uploaded  application/vnd.oci.image.manifest.v1+json                                                                                                                                                                                      591/591  B 100.00%   17ms
  └─ sha256:a91334e84d5371e2a5add8baf8c5052c21effcf352df39e4a44840d84d9c9bee
Pushed [registry] localhost:5000/hello-artifact:v1
ArtifactType: application/vnd.unknown.artifact.v1
Digest: sha256:a91334e84d5371e2a5add8baf8c5052c21effcf352df39e4a44840d84d9c9bee

次に oras manifest fetch localhost:5000/hello-artifact:v1 コマンドで zot に push したアーティファクトの内容を取得します。

schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
artifactType: application/vnd.unknown.artifact.v1
config:
  mediaType: application/vnd.oci.empty.v1+json
  digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
  size: 2
  data: e30=
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar
    digest: sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
    size: 12
    annotations:
      org.opencontainers.image.title: artifact.txt
annotations:
  org.opencontainers.image.created: "2024-08-27T14:33:13Z"

先程確認した docker イメージのマニフェスト (OCIManifest) と比較すると、上記の内容がイメージマニフェストに対応していることが確認できます。
config に注目すると mediaType: application/vnd.oci.empty.v1+json となっていますが、これは Manifest Specification における Guidance for an Empty Descriptor の記述方法に従っており、メタデータが空であることが確認できます data: e30={} を base64 encode したもので 2 文字なので size 2 となっています。

$ echo "e30=" | base64 -d
{}

アーティファクトに含まれるファイル等の情報は layers[] 以下に追加されます。
今回は artifact.txt 1 つのみを指定して push したためそれが含まれており、ファイルサイズが 12 byte なので size:12 となっています。mediaTypeannotations は添付したファイルやディレクトリに応じて ORAS が自動で追加するようです。

アーティファクトレジストリ上に存在するアーティファクトを取得する場合は docker CLI のように oras pull localhost:5000/hello-artifact:v1 でローカルにダウンロードできます。この例では添付した artifact.txt が直接取得できます。

$ oras pull localhost:5000/hello-artifact:v1
✓ Pulled      artifact.txt                                                                                                                                                                                                   12/12  B 100.00%  113µs
  └─ sha256:a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
✓ Pulled      application/vnd.oci.image.manifest.v1+json                                                                                                                                                                   591/591  B 100.00%     0s
  └─ sha256:a91334e84d5371e2a5add8baf8c5052c21effcf352df39e4a44840d84d9c9bee
Pulled [registry] localhost:5000/hello-artifact:v1
Digest: sha256:a91334e84d5371e2a5add8baf8c5052c21effcf352df39e4a44840d84d9c9bee

$ ls
artifact.txt

複数のファイルを添付する場合は Pushing and Pulling のコマンド例のように複数の layers を設定するかディレクトリを指定することでアーティファクトに含めることができます。

mkdir docs
echo "Docs on this artifact" > ./docs/readme.md
echo "More content for this artifact" > ./docs/readme2.md
oras push localhost:5000/hello-artifact:v2 \
./docs
✓ Uploaded  docs                                                                                                                                                                                                                            193/193  B 100.00%    5ms
  └─ sha256:a549a5915fee8fa9eff01724373616df4f0e8663e8c0244a84bdb7d0c9a763ba
✓ Exists    application/vnd.oci.empty.v1+json                                                                                                                                                                                                   2/2  B 100.00%     0s
  └─ sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
✓ Uploaded  application/vnd.oci.image.manifest.v1+json                                                                                                                                                                                      730/730  B 100.00%    5ms
  └─ sha256:538eee55d7942b37ca99e563687a6ccd523c67a4657bdf30c600b3dbdacb0cd8
Pushed [registry] localhost:5000/hello-artifact:v2
ArtifactType: application/vnd.unknown.artifact.v1
Digest: sha256:538eee55d7942b37ca99e563687a6ccd523c67a4657bdf30c600b3dbdacb0cd8

ディレクトリを指定すると mediaType は自動で ...tar+gzip となります。ドキュメントでは tar となっていますが実際にやってみると gzip も付加されました。

hello-artifact v2
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
artifactType: application/vnd.unknown.artifact.v1
config:
  mediaType: application/vnd.oci.empty.v1+json
  digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
  size: 2
  data: e30=
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:a549a5915fee8fa9eff01724373616df4f0e8663e8c0244a84bdb7d0c9a763ba
    size: 193
    annotations:
      io.deis.oras.content.digest: sha256:c1636d5e6d89031e478fc32fdb0e54a04a0bab70154def36652a46b0a83009b2
      io.deis.oras.content.unpack: "true"
      org.opencontainers.image.title: docs
annotations:
  org.opencontainers.image.created: "2024-08-27T15:34:25Z"

ローカル環境で事前に tar.gz でまとめてから添付することも可能。

oras push localhost:5000/hello-artifact:v3 res.tar.gz
✓ Uploaded  res.tar.gz                                                                                                                                                                                                                      286/286  B 100.00%    2ms
  └─ sha256:bf93d6344cae0c4cab7b0927cb7d4e1ebbc2177d5f88699f752649e390800d6e
✓ Exists    application/vnd.oci.empty.v1+json                                                                                                                                                                                                   2/2  B 100.00%     0s
  └─ sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
✓ Uploaded  application/vnd.oci.image.manifest.v1+json                                                                                                                                                                                      590/590  B 100.00%     0s
  └─ sha256:0df32bf3ae06b0c6baae12af3e2dcefb9f087e6c5ec235e9aa71f179f83dc3f6
Pushed [registry] localhost:5000/hello-artifact:v3
ArtifactType: application/vnd.unknown.artifact.v1
Digest: sha256:0df32bf3ae06b0c6baae12af3e2dcefb9f087e6c5ec235e9aa71f179f83dc3f6

この場合の mediaType は application/vnd.oci.image.layer.v1.tar となり size は tar.gz のサイズに一致している。

hello-artifact v3
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
artifactType: application/vnd.unknown.artifact.v1
config:
  mediaType: application/vnd.oci.empty.v1+json
  digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
  size: 2
  data: e30=
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar
    digest: sha256:bf93d6344cae0c4cab7b0927cb7d4e1ebbc2177d5f88699f752649e390800d6e
    size: 286
    annotations:
      org.opencontainers.image.title: res.tar.gz
annotations:
  org.opencontainers.image.created: "2024-08-27T15:41:53Z"
$ du -b res.tar.gz
286     res.tar.gz

Annotation をつける

Annotation はオプションで設定可能な Key-Value 形式のメタデータです。OCI Image の仕様でいくつかルールや予約語が決められていますが、これを満たせば任意の値を設定することができます。ORAS では oras push でアーティファクトを push する際に key-value 形式で指定したり、annotation を記載したファイルを指定することで設定できます。Using a JSON File の例では config, manifest, layer にそれぞれ annotation を追加しています。

annotation.json
{
  "$config": {
    "hello": "world"
  },
  "$manifest": {
    "foo": "bar"
  },
  "cake.txt": {
    "fun": "more cream"
  }
}

push 時に --annotation-file でファイルを指定。

oras push --annotation-file annotation.json localhost:5000/hello-artifact:ann

annotations が追加されていることが確認できます。

$ oras manifest fetch localhost:5000/hello-artifact:ann | yq -P
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
artifactType: application/vnd.unknown.artifact.v1
config:
  mediaType: application/vnd.oci.empty.v1+json
  digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
  size: 2
  annotations:
    hello: world
  data: e30=
layers:
  - mediaType: application/vnd.oci.empty.v1+json
    digest: sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
    size: 2
    data: e30=
annotations:
  foo: bar
  org.opencontainers.image.created: "2024-08-27T17:29:08Z"

レジストリにもよりますが、例えば harbor ではアーティファクトに設定された annotations が overview に表示されます。

使い勝手としては docker label のような感じでアーティファクトに関連する情報を付加するのが良さそうです。例えば ingress-nginx の helm chart のアーティファクトではソースコードの github の URL, version, 作成者などの情報が annotation に設定されています。

$ oras manifest fetch harbor.centre.com/helm/ingress-nginx:4.11.2 | yq -P
schemaVersion: 2
config:
  mediaType: application/vnd.cncf.helm.config.v1+json
  digest: sha256:f1fbe187a6e6a03bc72f5cc77ccdb50cfec0c3d620812b4d4e76784d55ed0f15
  size: 728
layers:
  - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
    digest: sha256:df79d6acb94c4c7c6e06624722a9c131c8cbc9fb5eaa5f3c962e003702aa8ea8
    size: 58090
annotations:
  artifacthub.io/changes: |
    - Update Ingress-Nginx version controller-v1.11.2
  artifacthub.io/prerelease: "false"
  org.opencontainers.image.authors: cpanato, Gacko, puerco, rikatz, strongjz, tao12345666333
  org.opencontainers.image.created: "2024-08-27T00:00:53+09:00"
  org.opencontainers.image.description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
  org.opencontainers.image.source: https://github.com/kubernetes/ingress-nginx
  org.opencontainers.image.title: ingress-nginx
  org.opencontainers.image.url: https://github.com/kubernetes/ingress-nginx
  org.opencontainers.image.version: 4.11.2

Distributing OCI Layouts

https://oras.land/docs/how_to_guides/distributing_oci_layouts/ の内容の話

OCI Image Layout は OCI イメージが満たすべき OCI Image Layout Specification のディレクトリ構造を指し、以下のファイル、ディレクトリが含まれている必要があります。

  • oci-layout
  • index.json
  • blobs

既に確認しているように docker イメージを docker save コマンドで tar に書き出した中身はこの仕様に準拠しています。
oras ではローカルに oci-layout に準拠したディレクトリが存在している場合にその内容をアーティファクトとしてレジストリに push できるようになっています。ドキュメントに沿って docker buildx で oci-layout のアーティファクトを作成してみます。

Dockerfile
FROM alpine
CMD echo 'hello world!'

ビルド

$ docker buildx build . -f Dockerfile -o type=oci,dest=hello-world.tar -t hello-world:v4

これでローカル環境に hello-world.tar が作成されるので hello-artifact:v4 としてレジストリに push

oras cp --from-oci-layout ./hello-world.tar localhost:5000/hello-artifact:v4

oras manifest fetch localhost:5000/hello-artifact:v4 でマニフェストを取得

hello-artifact v4
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
config:
  mediaType: application/vnd.oci.image.config.v1+json
  digest: sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
  size: 794
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
    size: 3622892

先程 oras push で直接テキストファイルを追加して作成したアーティファクトのマニフェストと比較すると、config の size が 794 となっているので何らかの内容が入っていることがわかります。
config の内容は oras manifest fetch-config localhost:5000/hello-artifact:v4 で確認できます。見てみるとアーキテクチャの情報(amd64 のマシンでビルドしているので amd64 になっている)やイメージ内のコマンド、history などのメタデータが入っていることが確認できます。

hello-artifact v4 config
architecture: amd64
config:
  Env:
    - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  Cmd:
    - /bin/sh
    - -c
    - echo 'hello world!'
  ArgsEscaped: true
created: "2024-07-22T22:26:43.778747613Z"
history:
  - created: "2024-07-22T22:26:43.622487881Z"
    created_by: '/bin/sh -c #(nop) ADD file:99093095d62d0421541d882f9ceeddb2981fe701ec0aa9d2c08480712d5fed21 in / '
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: '/bin/sh -c #(nop)  CMD ["/bin/sh"]'
    empty_layer: true
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: CMD ["/bin/sh" "-c" "echo 'hello world!'"]
    comment: buildkit.dockerfile.v0
    empty_layer: true
os: linux
rootfs:
  type: layers
  diff_ids:
    - sha256:78561cef0761903dd2f7d09856150a6d4fb48967a8f113f3e33d79effbf59a07

この内容は元の hello-world.tar の blob に含まれる 1 つに対応しています。

.
├── blobs
│   └── sha256
│       ├── 31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
│       ├── 3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce
│       └── c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
├── index.json
└── oci-layout
31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
architecture: amd64
config:
  Env:
    - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  Cmd:
    - /bin/sh
    - -c
    - echo 'hello world!'
  ArgsEscaped: true
created: "2024-07-22T22:26:43.778747613Z"
history:
  - created: "2024-07-22T22:26:43.622487881Z"
    created_by: '/bin/sh -c #(nop) ADD file:99093095d62d0421541d882f9ceeddb2981fe701ec0aa9d2c08480712d5fed21 in / '
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: '/bin/sh -c #(nop)  CMD ["/bin/sh"]'
    empty_layer: true
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: CMD ["/bin/sh" "-c" "echo 'hello world!'"]
    comment: buildkit.dockerfile.v0
    empty_layer: true
os: linux
rootfs:
  type: layers
  diff_ids:
    - sha256:78561cef0761903dd2f7d09856150a6d4fb48967a8f113f3e33d79effbf59a07

残りの blob は 3aee... が実際のマニフェストの json、c6a83fe... がベースイメージに指定した alpine のレイヤーに対応しています。

3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
config:
  mediaType: application/vnd.oci.image.config.v1+json
  digest: sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
  size: 794
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
    size: 3622892

ORAS のサブコマンド

ORAS discover

https://oras.land/docs/commands/oras_discover/

まだプレビュー版の状況ですが、oras discover コマンドではマニフェストの referrer を取得することができます。
referrer というのは Understanding References in the Open Container Initiative (OCI) Standard に説明がある通りイメージやアーティファクトを特定するための参照(ポインタのようなもの)を指しています。
docker では docker pull nginx で dockerhub 上の latest タグに対応するイメージを pull できますが、省略されている部分を明示的に書くと docker.io/library/nginx@[digest] となり、レジストリ、repository, タグに対応するイメージの digest を指定してイメージを一意に特定してから pull しています。
digest を用いることで対象のアーティファクトを一意に特定できますが、長くて人間にとっては覚えづらいのでタグを使う場合が多いかと思います。referrer はタグから digest への参照を取得するために使用されます。
例えば oras のチュートリアルで使用される zot registry では referrer API が実装されているため、v4 タグを指定するとそれに対応するアーティファクトの digest を含む参照が取得できます。

$ oras discover localhost:5000/hello-artifact:v4
localhost:5000/hello-artifact@sha256:3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce

一方で referrer が使用できるかどうかは、アーティファクトを格納する registry が referrer の機能に対応しているかどうかに依存するようです。例えばコンテナイメージレジストリの Harbor は OCI アーティファクトに対応しているのでアーティファクトを push/pull できますが、 referrer API はまだ実装されていないので取得しようとしてもエラーとなります。

$ oras discover harbor.centre.com/helm/oras-test:latest
Error response from registry: unauthorized: un-recognized request: GET /v2/helm/oras-test/referrers/sha256:ac88e565f0f104bdd7189eaf658bc9973a6192f430a6f551635f7ef5d2fa7b2a: un-recognized request: GET /v2/helm/oras-test/referrers/sha256:ac88e565f0f104bdd7189eaf658bc9973a6192f430a6f551635f7ef5d2fa7b2a

ORAS blob

https://oras.land/docs/commands/oras_blob_fetch

先程作成した alpine ベースイメージの OCI レイアウトでは 3 つの blob が含まれていますが、oras blob コマンドではこの blob の内容を直接取得できます。

oras manifest コマンドで各 blob に対応する digest が取得できるので、レジストリ上に保存されているアーティファクトの名前やタグがわかっていれば blob の値を知らなくても調べることができます。
まず oras manifest fetch localhost:5000/hello-artifact:v4 でアーティファクトのマニフェストを取得します。

manifest
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
config:
  mediaType: application/vnd.oci.image.config.v1+json
  digest: sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
  size: 794
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
    size: 3622892

この結果を見ることで config に対応する digest が 31c.., layers の 1 つが c6a... であると判別できます。
また、イメージタグ v4 に対応する digest は oras discover で取得できます。

$ oras discover localhost:5000/hello-artifact:v4
localhost:5000/hello-artifact@sha256:3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce

よってアーティファクト localhost:5000/hello-artifact:v4 に関しては以下 3 つの blob が存在することがわかります。

内容 digest
config の json sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
layer sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
index sha256:3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce

一方で、先程ローカルで展開した blob ディレクトリは以下のようになっているので上記と一致しています。なので実際に oci-layout の中身を知らなくてもどのような blob が含まれているか確認できます。

.
├── blobs
│   └── sha256
│       ├── 31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
│       ├── 3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce
│       └── c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6

blob の内容を取得するには oras blob -o - fetch [digest] で指定します。
-o オプションは出力先を指定し、-o - の場合は標準出力に表示します。
試しに config に対応する blob を取得してみます。

$ oras blob -o - fetch localhost:5000/hello-artifact@sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce | yq -P
architecture: amd64
config:
  Env:
    - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  Cmd:
    - /bin/sh
    - -c
    - echo 'hello world!'
  ArgsEscaped: true
created: "2024-07-22T22:26:43.778747613Z"
history:
  - created: "2024-07-22T22:26:43.622487881Z"
    created_by: '/bin/sh -c #(nop) ADD file:99093095d62d0421541d882f9ceeddb2981fe701ec0aa9d2c08480712d5fed21 in / '
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: '/bin/sh -c #(nop)  CMD ["/bin/sh"]'
    empty_layer: true
  - created: "2024-07-22T22:26:43.778747613Z"
    created_by: CMD ["/bin/sh" "-c" "echo 'hello world!'"]
    comment: buildkit.dockerfile.v0
    empty_layer: true
os: linux
rootfs:
  type: layers
  diff_ids:
    - sha256:78561cef0761903dd2f7d09856150a6d4fb48967a8f113f3e33d79effbf59a07

これは oras manifest fetch-config localhost:5000/hello-artifact:v4 を実行した際と同じ内容となっていますが、config の実態は blob 内に保存されている json ファイルであるため、 blob を取得することも確認できるということになっています。
layers の方の blob はベースイメージの alpine のファイルシステム (tar) に対応しています。これは標準出力には表示できないのでファイルに出力してから展開することで確認できます。

$ oras blob fetch localhost:5000/hello-artifact@sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6 -o output.tar.gz
✓ Downloaded  application/octet-stream                                                                                                                                                                                                    3.46/3.46 MB 100.00%     0s
  └─ sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
$ tar zxf output.tar.gz -C output
$ tree -L 1 output
output
├── bin
├── dev
├── etc
├── home
├── lib
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var

$ cat output/etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.20.2
PRETTY_NAME="Alpine Linux v3.20"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"

最後の index の digest 3ae... の blob の実態はマニフェストの json であるため、oras manifest fetch localhost:5000/hello-artifact:v4 と同じ内容になっています。

$ oras blob -o - fetch localhost:5000/hello-artifact@sha256:3aee1155f4ace57c66a37bec188b626973416d5a5dc46a63220874de03a4b8ce | yq -P
schemaVersion: 2
mediaType: application/vnd.oci.image.manifest.v1+json
config:
  mediaType: application/vnd.oci.image.config.v1+json
  digest: sha256:31c62eb4c507b8934e284ced6bbebdcffb80c0346f5cc65efd43b182184f19ce
  size: 794
layers:
  - mediaType: application/vnd.oci.image.layer.v1.tar+gzip
    digest: sha256:c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6
    size: 3622892

このように oras blob コマンドではアーティファクト内に含まれる特定の blob を取得したりダウンロードすることができます。ただこのコマンドを使って直接 BLOB を見る機会があるかどうかは不明。

ORAS を使用するプロジェクト

Projects using ORAS

Helm

Helm では v3.8.0 から OCI 機能がサポートされ OCI アーティファクトレジストリに helm chart を push/pull できるようになっています。

https://v3.helm.sh/docs/topics/registries/

このページでは特に ORAS の記載はありませんが、github で検索してみると ORAS の go 言語用のライブラリ oras-go が裏で使用されていることが伺えます。

また、helm chart 自体が OCI アーティファクトであるので今までと同様にアーティファクトの push/pull や情報の取得ができます。試しに nginx ingress controller の helm chart をローカルに立てた harbor レジストリに push してアーティファクトを見てみます。

helm repo add nginx https://kubernetes.github.io/ingress-nginx
helm pull nginx/ingress-nginx

helm registry login harbor.centre.com
helm push ingress-nginx-4.11.2.tgz oci://harbor.centre.com/helm/helm-nginx

oras manifest fetch で chart のマニフェストが取得できます。helm chart では config.mediaType が helm 用に設定されていたり、annotation に chart の詳細などが記載されています。

manifest
$ oras manifest fetch harbor.centre.com/helm/ingress-nginx:4.11.2 | yq -P
schemaVersion: 2
config:
  mediaType: application/vnd.cncf.helm.config.v1+json
  digest: sha256:f1fbe187a6e6a03bc72f5cc77ccdb50cfec0c3d620812b4d4e76784d55ed0f15
  size: 728
layers:
  - mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
    digest: sha256:df79d6acb94c4c7c6e06624722a9c131c8cbc9fb5eaa5f3c962e003702aa8ea8
    size: 58090
annotations:
  artifacthub.io/changes: |
    - Update Ingress-Nginx version controller-v1.11.2
  artifacthub.io/prerelease: "false"
  org.opencontainers.image.authors: cpanato, Gacko, puerco, rikatz, strongjz, tao12345666333
  org.opencontainers.image.created: "2024-08-27T00:00:53+09:00"
  org.opencontainers.image.description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
  org.opencontainers.image.source: https://github.com/kubernetes/ingress-nginx
  org.opencontainers.image.title: ingress-nginx
  org.opencontainers.image.url: https://github.com/kubernetes/ingress-nginx
  org.opencontainers.image.version: 4.11.2

oras manifest fetch-config で config の中身が取得できます。

config の中身
$ oras manifest fetch-config harbor.centre.com/helm/ingress-nginx:4.11.2 | yq -P
name: ingress-nginx
home: https://github.com/kubernetes/ingress-nginx
sources:
  - https://github.com/kubernetes/ingress-nginx
version: 4.11.2
description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
keywords:
  - ingress
  - nginx
maintainers:
  - name: cpanato
  - name: Gacko
  - name: puerco
  - name: rikatz
  - name: strongjz
  - name: tao12345666333
icon: https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Nginx_logo.svg/500px-Nginx_logo.svg.png
apiVersion: v2
appVersion: 1.11.2
annotations:
  artifacthub.io/changes: |
    - Update Ingress-Nginx version controller-v1.11.2
  artifacthub.io/prerelease: "false"
kubeVersion: '>=1.21.0-0'

oras pulloras blob fetch -o [output] [chart]:[layer digest] で chart に対応するアーティファクトが取得できます。

$ oras blob -o chart.tar.gz fetch harbor.centre.com/helm/ingress-nginx:4.11.2@sha256:df79d6acb94c4c7c6e06624722a9c131c8cbc9fb5eaa5f3c962e003702aa8ea8
✓ Downloaded  application/octet-stream                                                                                                                                                                             56.7/56.7 kB 100.00%  421µs
  └─ sha256:df79d6acb94c4c7c6e06624722a9c131c8cbc9fb5eaa5f3c962e003702aa8ea8

中身は Github の chart の内容 になっています。

$ mkdir ingress-nginx
$ tar xvf -C ingress-nginx
$ tree ingress-nginx -L 2
ingress-nginx
└── ingress-nginx
    ├── Chart.yaml
    ├── OWNERS
    ├── README.md
    ├── README.md.gotmpl
    ├── changelog
    ├── ci
    ├── templates
    ├── tests
    └── values.yaml

また、helm chart を管理する際によく用いられる Artifact Hub でも ORAS CLI で OCI アーティファクトを push する例が記載されています。

https://artifacthub.io/docs/topics/repositories/helm-charts/#oci-support

Link

Discussion