Nick Perkins

Platform Engineer. Volunteer Motorsport Official. ADHD Brain. Bit of a nerd.

ORAS for Configuration Management

A challenge for any engineering team is handling configuration management in a secure and efficient way. Recently, I’ve explored using ORAS (OCI Registry As Storage), a tool which enhances OCI (Ope Container Initiative) registries by enabling them to store various artifacts, not just container images.

What is ORAS?

ORAS is an open source project which builds tools and libraries to enable using OCI registries to store any type of artifact, not just container images. The ability to leverage container registries in this way comes from how container images are constructed.

Container Image Basics

A container image is not a single file but is instead made up of layers. Just like a cake, each layer builds on top of the next layer to form the complete container image. Each layer contains the data that forms the image, which is stored as a blob. Another blob contains the configuration for the container image. Finally, a manifest brings all of this together to form the full container image. Below is the manifest for an alpine linux image.

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 1483,
      "digest": "sha256:011cd18df9954a4143ac1256824ad34026d382d26714a2b222d0a8f06286224f"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 3367154,
         "digest": "sha256:3d2af5f613c84e549fb09710d45b152d3cdf48eb7a37dc3e9c01e2b3975f4f76"
      }
   ]
}

OCI Artifacts and ORAS

It was always possible to piece together your own ‘container image’ that wasn’t really an image, but was a way to store other information in a container registry. In 2019, OCI approved a new project to look at standardising the way that artifacts were stored in OCI registries. This culminated with the release of v1.1.0 of the OCI Image and OCI Distribution specifications.

ORAS is the “de facto tool” for working with OCI artifacts. It allows you to not only push and pull artifacts to and from a container registry, but also attach artifacts to existing artifacts, including container images.

Pushing and Pulling Artifacts with ORAS

It’s very simple to push and pull an artifact to a container registry with ORAS. As a demonstration, we’ll start with a simple json file and push it to a local test container registry.

$ oras push localhost:5001/configs/simpleconfig:v1 config.json
✓ Uploaded  config.json                                  22/22  B 100.00%   24ms
  └─ sha256:3a3a3af8c336832376065d8138563bc288faa5ac1f33c17e66aa57a2a6e60451
✓ Uploaded  application/vnd.oci.empty.v1+json              2/2  B 100.00%   25ms
  └─ sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
✓ Uploaded  application/vnd.oci.image.manifest.v1+json 590/590  B 100.00%     0s
  └─ sha256:6bc7f143e75a7d5791e182ed80eadd242e62492b5aa5a541ec6842bde01b5296
Pushed [registry] localhost:5001/configs/simpleconfig:v1
ArtifactType: application/vnd.unknown.artifact.v1
Digest: sha256:6bc7f143e75a7d5791e182ed80eadd242e62492b5aa5a541ec6842bde01b5296

You can see that the blob is uploaded, along with the manifest and configuration. Let’s look at the manifest for this artifact.

$ oras manifest fetch localhost:5001/configs/simpleconfig:v1 --pretty
{
  "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:3a3a3af8c336832376065d8138563bc288faa5ac1f33c17e66aa57a2a6e60451",
      "size": 22,
      "annotations": {
        "org.opencontainers.image.title": "config.json"
      }
    }
  ],
  "annotations": {
    "org.opencontainers.image.created": "2024-06-29T06:46:22Z"
  }
}

As you can see, it’s very similar to a container image, with a config created by ORAS and the blob containing your artifact as a layer. ORAS makes it simple to pull that artifact back down too.

$ oras pull localhost:5001/configs/simpleconfig:v1 -o output
✓ Pulled      config.json                                22/22  B 100.00%    3ms
  └─ sha256:3a3a3af8c336832376065d8138563bc288faa5ac1f33c17e66aa57a2a6e60451
✓ Pulled      application/vnd.oci.image.manifest.v1+j. 568/568  B 100.00%   83µs
  └─ sha256:7abc32780ea52f9ca2d21a3c6e5aaeaa0773df17a7bb3a0150b3fe657b2790fc
Pulled [registry] localhost:5001/configs/simpleconfig:v1
Digest: sha256:7abc32780ea52f9ca2d21a3c6e5aaeaa0773df17a7bb3a0150b3fe657b2790fc

This has pull down our artifact and placed it in the output directory. Neat!

$ ls output/
config.json

Attaching Artifacts to Container Images

What if your configuration relates to a container image that you will be deploying? Wouldn’t it make sense to have them linked somehow, making it easier to get the right config file for the right image on deployment? ORAS makes that easy with the ability to attach artifacts to an existing container image.

Let’s push a container image up to demonstrate this. Since the test registry I am using is only able to accept OCI images, I’m using skopeo to push my image that I’ve exported with docker save.

$ skopeo copy --dest-tls-verify=false --format=oci docker-archive:testimage.tar docker://localhost:5001/testimage:v1
Getting image source signatures
Copying blob 11b9733114f7 done   |
Copying blob 48205ccc81c1 done   |
Copying blob 74f83e71d791 done   |
Copying blob 9cb81ccf927f done   |
Copying blob 72c690143394 done   |
Copying blob 70632f097359 done   |
Copying blob fbc1665d4d36 done   |
Copying config 007bc662c1 done   |
Writing manifest to image destination

Now I can attach my configuration to the image in the registry.

$ oras attach --artifact-type=custom/config localhost:5001/testimage:v1 config.json
✓ Exists    application/vnd.oci.empty.v1+json              2/2  B 100.00%     0s
  └─ sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
✓ Exists    config.json                                  22/22  B 100.00%     0s
  └─ sha256:3a3a3af8c336832376065d8138563bc288faa5ac1f33c17e66aa57a2a6e60451
✓ Uploaded  application/vnd.oci.image.manifest.v1+json 732/732  B 100.00%   18ms
  └─ sha256:9db7a89f5a639e35d1efd1f6f132ade14c3bda8858b7427ee9fb0f65bb82b97c
Attached to [registry] localhost:5001/testimage@sha256:ba0a3a59071528a8f02293e62a839a2cb90973a67a21b34f80508334b969ddcf
Digest: sha256:9db7a89f5a639e35d1efd1f6f132ade14c3bda8858b7427ee9fb0f65bb82b97c

You will see that because the blob already exists in the registry, it has reused that as part of our configuration. This is similar to how layers can be shared between container images.

Now that the configuration is attached, how do we see this and then access the artifact later? We can discover which artifacts refer to an image.

$ oras discover localhost:5001/testimage:v1
localhost:5001/testimage@sha256:ba0a3a59071528a8f02293e62a839a2cb90973a67a21b34f80508334b969ddcf
└── custom/config
    └── sha256:9db7a89f5a639e35d1efd1f6f132ade14c3bda8858b7427ee9fb0f65bb82b97c

You’ll see that we have the image and then a custom/config artifact and the related sha. We can easily pull down the artifact now as before.

$ oras pull localhost:5001/testimage@sha256:9db7a89f5a639e35d1efd1f6f132ade14c3bda8858b7427ee9fb0f65bb82b97c -o output
✓ Pulled      config.json                                22/22  B 100.00%  724µs
  └─ sha256:3a3a3af8c336832376065d8138563bc288faa5ac1f33c17e66aa57a2a6e60451
✓ Pulled      application/vnd.oci.image.manifest.v1+j. 732/732  B 100.00%  134µs
  └─ sha256:9db7a89f5a639e35d1efd1f6f132ade14c3bda8858b7427ee9fb0f65bb82b97c
Pulled [registry] localhost:5001/testimage@sha256:9db7a89f5a639e35d1efd1f6f132ade14c3bda8858b7427ee9fb0f65bb82b97c
Digest: sha256:9db7a89f5a639e35d1efd1f6f132ade14c3bda8858b7427ee9fb0f65bb82b97c
$ ls output/
config.json

Summary

Storing artifacts with your container images using ORAS makes it simple to distribute configuration files in the same way that you distribute your container images. The ability to then link these artifacts to your container images makes it simple to know exactly which configuration version to use when deploying your software. This is great in a situation where your ops team needs to run a rollback late at night - no need to consult separate documentation or raise the on call software engineer from their slumber.