Serving OCI Helm Charts in Helm Chart Repositories

Posted: June 3rd, 2024 | Author: | Filed under: Technology | Tags: , , | No Comments »

The introduction of OCI registries as a medium for storing Helm charts brought a number of advantages as compared to traditional Helm repositories including the ability to leverage the same infrastructure and primitives as standard containers along with reducing the overall effort that it takes to serve Helm charts for others to consume. However, even as the adoption of OCI based Helm charts continues to increase, there are several limitations compared to their traditional Helm Chart repository counterparts. Examples of these limitations include the inability to organize multiple charts amongst each other or the ability to search for charts that are stored within OCI registries.

A recent discussion within the Helm project GitHub repository brought to light a new method for which OCI based Helm charts could be managed. Charts stored in Helm repositories make use of an Index file which defines not only the charts that are being served, but the remote source where they are located.

The following is an example of a Helm index file:

apiVersion: v1
entries:
  oci-artifacts-demo:
  - apiVersion: v2
    appVersion: 1.16.0
    created: "2024-05-31T11:50:24.431916-05:00"
    description: Sample Helm Chart
    digest: db5c00dcae815f35b4d0d18d507aeae98f648e849ec1786a0573111210e5f337
    name: my-chart
    type: application
    urls:
    - https://example.com/charts/my-chart-0.1.1.tgz
    version: 0.1.1
  - apiVersion: v2
    appVersion: 1.16.0
    created: "2024-05-31T11:50:24.431418-05:00"
    description: Sample Helm Chart
    digest: a59db5293c542bdbe5f3e85aff3f357d1d0501ae308f51407541644baf8bd32a
    name: my-chart
    type: application
    urls:
    - https://example.com/charts/my-chart-0.1.0.tgz
    version: 0.1.0
generated: "2024-05-31T11:50:24.43089-05:00"

As illustrated by the index file above, the urls field specifies the location of the packaged chart. While the majority of Helm chart repositories serve packaged charts alongside the index file, this is not a hard requirement and a packaged chart could be served by any accessible location.

Under the covers, Helm utilizes an interface (Getter) for accessing charts stored in remote locations. As one can expect, there are two implementations of the interface: accessing charts stored in Helm charts repositories as well as OCI registries. The determination of which method to use is determined by the protocol specified within the URL. URL’s specifying the http or https scheme access charts from traditional Helm repositories, while those with the oci scheme access charts from OCI registries.

The mechanisms for which Helm retrieves remote chart content based on the specified protocol enables using a Helm index in new ways. Instead referencing the location of the packaged chart stored within an HTTP server, the chart could instead be stored in an OCI registry. All that needs to change is the location of the chart specified in the urls field. This reference needs to include the oci:// protocol so that the underlying functionality for retrieving OCI based charts is activated. Let’s see how we can accomplish this capability.

Enabling OCI Charts in Chart Repositories

First, create a new Helm chart called my-chart:

helm create my-chart

Package up the chart

helm package my-chart

We will create a second version of the chart so that the chart index we will build includes multiple versions. To increment the chart version, use the yq utility which enables modifying YAML based content. If yq is not currently installed on your machine, navigate to the project website and follow the steps to download and install in your machine.

Once yq has been installed, update the version of the my-chart Helm chart to version 0.1.1:

yq -i '.version = "0.1.1"' my-chart/Chart.yaml

Package the updated chart

helm package my-chart

At this point, there are two packaged charts in the current directory (versions 0.0.1 and 0.1.1). Before publishing these charts to an OCI registry, set an environment variable called HELM_REGISTRY_REFERENCE representing the reference of the remote registry where the charts will be stored (for example: myregistry.com/charts).

export HELM_REGISTRY_REFERENCE=myregistry.com/charts

Now, push both charts to the remote registry

helm push my-chart-0.1.0.tgz oci://${HELM_REGISTRY_REFERENCE}
helm push my-chart-0.1.1.tgz oci://${HELM_REGISTRY_REFERENCE}

Next, generate a Helm index file based on charts stored within the current directory

helm repo index .

With the index file generated, use yq to dynamically update the URL within the index file to reference the location of the chart in the OCI registry instead of the default path generated by Helm.

yq eval -i '. |= .entries[][] |= .urls[0] = "oci://" + env(HELM_REGISTRY_REFERENCE) + "/" + .name + ":" + .version' index.yaml

View the contents of the index file and you will notice that each chart version utilizes an OCI based reference in the urls field, enabling the use of the charts stored in the OCI registry.

apiVersion: v1
entries:
  my-chart:
    - apiVersion: v2
      appVersion: 1.16.0
      created: "2024-06-01T12:31:02.734732-05:00"
      description: A Helm chart for Kubernetes
      digest: a7f05a380cc6ed45feab837b65b1f0a51f8737236105d7325b1b13953d7abb96
      name: my-chart
      type: application
      urls:
        - oci://${HELM_REGISTRY_REFERENCE}/charts/my-chart:0.1.1
      version: 0.1.1
    - apiVersion: v2
      appVersion: 1.16.0
      created: "2024-06-01T12:31:02.734383-05:00"
      description: A Helm chart for Kubernetes
      digest: bbee6c6f09d1535bafdd4cb5c0c7344ed6812a0b23650e2afd005cb80450a89a
      name: my-chart
      type: application
      urls:
        - oci://${HELM_REGISTRY_REFERENCE}/charts/my-chart:0.1.0
      version: 0.1.0
generated: "2024-06-01T12:31:02.733967-05:00"

NOTE: ${HELM_REGISTRY_REFERENCE} in the example above will be rendered in your version

The index file can then be uploaded to an HTTP based web server for broader distribution and use by others. There are a number of options for which the index file can be hosted, including GitHub Pages. Otherwise, since the file only needs to be served briefly, we can use python to start a minimalistic web server locally.

Execute the following command to start the web server on port 8000 using the contents from the current directory.

python -m http.server

Confirm the index file is being served properly

curl http://localhost:8000/index.yaml

Now that the chart repository has been configured and confirmed to be accessible, add a new repository called repo-oci and update the contents.

helm repo add repo-oci http://localhost:8000
helm repo update repo-oci

Verifying the Approach

With all of the steps now complete for leveraging OCI based charts within chart repositories including adding the associated repository to the local machine, you can begin to use it it similar to any other helm repository, including searching for all charts and versions available

helm search repo repo-oci/ --versions

NAME             	CHART VERSION	APP VERSION	DESCRIPTION                
repo-oci/my-chart	0.1.1        	1.16.0     	A Helm chart for Kubernetes
repo-oci/my-chart	0.1.0        	1.16.0     	A Helm chart for Kubernetes

Confirm that typical operations for for interacting with a chart can still be achieved, such as inspecting the contents of the Chart.yaml file using the helm show chart command:

helm show chart repo-oci/my-chart

apiVersion: v2
appVersion: 1.16.0
description: A Helm chart for Kubernetes
name: my-chart
type: application
version: 0.1.1

You can even choose to install one of these charts to a Kubernetes cluster using the helm install or helm upgrade commands. However, since

Capabilities surrounding the use of OCI artifacts continue to evolve both within the Helm project and in the OCI community. However, until new features become more readily available which offers an improved experience working with OCI based content, the ability to combine the use of Helm repositories and registries provides a suitable middleground in the meantime.