Skip to content

Add SIP for distributing Spin applications using OCI registries #1033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 364 additions & 0 deletions docs/content/sips/008-using-oci-registries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
title = "SIP 008 - Distributing Spin applications using OCI registries"
template = "main"
date = "2023-01-04T01:01:01Z"

---

Summary: This improvement proposal describes the reasoning and implementation
for distributing Spin applications using OCI registries.

Owners: [email protected]

Created: January 4, 2023

## Background

Since its first release, Spin has used
[Bindle](https://github.com/deislabs/bindle) as the mechanism for distributing
applications — an experimental aggregate object storage project originally
designed to distribute WebAssembly applications and their supporting files.

Since Bindle is a relatively early project, using it has surfaced a few issues
that could cause us to reconsider using it as the primary mechanism of
distribution:

* because there are no managed Bindle services, distributing Spin applications
requires users to run and manage their own infrastructure, increasing the
level of complexity required for the most simple applications
* when managing the Bindle infrastructure, scaling the infrastructure is an
[unsolved issue for the Bindle project](https://github.com/deislabs/bindle/issues/263)

[OCI](https://opencontainers.org), or the Open Container Initiative, emerged as
the standard for packaging and distributing container images. Initially used for
container images, the use of container registries has been expanding to more
artifact types with the introduction of the
[OCI Artifacts](https://github.com/opencontainers/artifacts) project.

All major cloud providers offer managed registry services, and among them, there
are services that already support distributing other artifact types.

## Proposal

This SIP proposes that Spin should support distributing applications using OCI
registries. This would solve the two issues outlined above:

* because of the plenty managed OCI registry services, users could use
existing services — such as GitHub Container Registry, Docker Hub, AWS Elastic
Container Registry, Azure Container Registry, Google Artifact registry, or
others.
* if using a managed service, scaling the infrastructure is no longer a concern
for users; if users decide to self-host, horizontally scaling a container
registry should be a more straightforward task, with more resources and
projects whose goal is scaling available when compared to Bindle.

### The user experience

The implementation should give users the ability to push an application to a
compatible registry, pull an application locally, and run an application:

```bash
$ spin oci push ghcr.io/<username>/my-spin-application:v1
INFO spin_publish::oci::client: Pushed "https://ghcr.io/v2/<username>/my-spin-application/manifests/sha256:9f4e7eebb27c0174fe6654ef5e0f908f1edc8f625324a9f49967ccde44a6516b"

$ spin oci pull ghcr.io/<username>/my-spin-application:v1
INFO spin_publish::oci::client: Pulled ghcr.io/<username>/my-spin-application:v1@sha256:9f4e7eebb27c0174fe6654ef5e0f908f1edc8f625324a9f49967ccde44a6516b

$ spin up --oci ghcr.io/<username>/my-spin-application:v1
INFO spin_publish::oci::client: Pulled ghcr.io/<username>/my-spin-application:v1@sha256:9f4e7eebb27c0174fe6654ef5e0f908f1edc8f625324a9f49967ccde44a6516b
Serving http://127.0.0.1:3000
```

The commands and arguments shown above are not final.

### Authentication

Historically, the `docker` CLI has been the toolchain used to interact with
container images and registries — as a result, given its popularity, the
`spin oci` functionality should be able to re-use credentials for already
logged-in users. Additionally, most container registry services have
instructions on how to log in to their services using the `docker login`
command.

However, having Docker installed locally should not be a prerequisite for using
Spin. To address this, a `spin oci login` command should be implemented that
authenticates the Spin CLI to the desired registry instance:

```bash
$ spin oci login --username <username> --password <password>
# OR
$ echo $CONTAINER_REGISTRY_PASSWORD | spin oci login --username <username> --password-stdin
```

This user experience would mirror
[the `docker login` command](https://docs.docker.com/engine/reference/commandline/login/).

### Migrating applications from Bindle to an OCI registry

Changing the distribution mechanism for Spin applications is a breaking change
for the project — to address this, the project should provide functionality
that users can use to migrate their applications from Bindle to an OCI registry.

To take the ephemeral usefulness of this tool into account, and to prevent
future breaking changes by the needing to remove it,
this functionality would be best suited as a Spin plugin, installed
when needed and distributed separately:

```bash
spin bindle2oci \
--bindle-server <server> \
--bindle-username <username> \
--bindle-password <password> \
---bindle <name> \
--oci <new-reference>
```

### Implementation

A Spin application is made up of metadata and component information
together with the Wasm modules and static assets that made up those components.
So conceptually, a Spin application is not *a single artifact*, but rather
multiple distinct objects.

This SIP proposes that, when distributed using an OCI registry, a Spin
application would become a new OCI *artifact* with multiple layers
(making the distinction clear, as *images* and *artifacts* are separate
entities in OCI). Specifically, the media type used in this implementation
for a Spin application is `application/vnd.fermyon.spin.application.v1+config`,
and each Wasm module and static asset from the Spin components becomes an individual
*layer* in the resulting registry entity. Because each file and Wasm module becomes
a separate *layer*, we can efficiently de-duplicate and distribute applications.

The remaining question is representing the Spin application definition — Spin
introduced the internal representation of a *locked application* - an
intermediate representation of a Spin application that *can* have a way to
content-address Wasm modules and static assets. The implementation for this SIP
uses the Spin locked application as the OCI configuration object.

Below is an example of a Spin application with one component and one static
asset, its OCI manifest, locked application, and resulting local directory
structure when pulling the application locally.

Consider the following `spin.toml` file for the application:

```toml
spin_version = "1"
authors = ["Radu Matei <[email protected]>"]
description = ""
name = "github-stars-webhook"
trigger = { type = "http", base = "/" }
version = "0.1.0"

[[component]]
id = "github-star-webhook"
source = "target/spin-http-js.wasm"
files = ["my-file.json"]
allowed_http_hosts = ["https://hooks.slack.com"]
[component.trigger]
route = "/..."
```

Distributing it to the GitHub Container Registry:

```bash
$ spin oci push ghcr.io/radu-matei/spin-example:v1
INFO spin_publish::oci::client: Pushed "https://ghcr.io/v2/radu-matei/spin-example/manifests/sha256:8f86a27fbc457416701c4d18680083f598076d0a52dca2a5936e92754a845ed1"

$ spin oci pull ghcr.io/radu-matei/spin-example:v1
INFO spin_publish::oci::client: Pulled ghcr.io/radu-matei/spin-example:v1@sha256:8f86a27fbc457416701c4d18680083f598076d0a52dca2a5936e92754a845ed1
```

This operation created the following local cache directory structure:

```bash
$ tree /Users/radu/Library/Application\ Support/fermyon/registry
└── oci
├── data
│   └── sha256:a4699e4f9ef3f4922f38f0d017aa26438908f38caf020a739e0ee27fe796eb02
├── manifests
│   └── ghcr.io
│   └── radu-matei
│   └── spin-example
│   └── v1
│   ├── config.json
│   └── manifest.json
└── wasm
└── sha256:55c29ad4b0ad0c6bd8ec1ffc8f04e63342e5901280037ef706b1b114475d3cbb
```

Looking at `manifest.json`, we see the top-level media type for the artifact
configuration to be `application/vnd.fermyon.spin.application.v1+config`

```json
{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.fermyon.spin.application.v1+config",
"digest": "sha256:b36160facea3076ad136c09bd4975a429805945ad313b4674363841d5a7f66a0",
"size": 643
},
"layers": [
{
"mediaType": "application/vnd.wasm.content.layer.v1+wasm",
"digest": "sha256:55c29ad4b0ad0c6bd8ec1ffc8f04e63342e5901280037ef706b1b114475d3cbb",
"size": 2147122
},
{
"mediaType": "application/vnd.wasm.content.layer.v1+data",
"digest": "sha256:a4699e4f9ef3f4922f38f0d017aa26438908f38caf020a739e0ee27fe796eb02",
"size": 178
}
]
}
```

Note: the media types used are not final and *can* change based on the community
standardization for Wasm modules in OCI registries. Expanding on this, there have
been several efforts to distribute Wasm *modules* using OCI registries,
each with its own media type:

* [`wasm-to-oci`](https://github.com/engineerd/wasm-to-oci) and
[`oci-distribution`](https://github.com/krustlet/oci-distribution)
use `application/vnd.wasm.content.layer.v1+wasm`
* [`solo-io/wasm/spec`](https://github.com/solo-io/wasm/blob/master/spec/spec.md)
uses `application/vnd.module.wasm.content.layer.v1+wasm`
* Docker's preview appears to distribute them as `application/vnd.docker.container.image.v1+json`

In short, there is no standard at this point. The argument for using
`application/vnd.module.wasm.content.layer.v1+wasm` content media type for *modules*
is the potential introduction of *components* in the future.

The manifest contains layers for the Wasm module for the component and for the
one static asset referenced by the component. For each additional component, the
Wasm module and static assets would be individual layers in the manifest above.

Let's explore the OCI configuration object referenced in the manifest —
`config.json` - it is a locked application manifest that Spin can use to run the
application from the local cache:

```json
{
"spin_lock_version": 0,
"metadata": {
"description": "",
"name": "github-stars-webhook",
"trigger": {
"base": "/",
"type": "http"
},
"version": "0.1.0"
},
"triggers": [
{
"id": "trigger--github-star-webhook",
"trigger_type": "http",
"trigger_config": {
"component": "github-star-webhook",
"executor": null,
"route": "/..."
}
}
],
"components": [
{
"id": "github-star-webhook",
"metadata": {
"allowed_http_hosts": ["https://hooks.slack.com"]
},
"source": {
"content_type": "application/wasm",
"digest": "sha256:55c29ad4b0ad0c6bd8ec1ffc8f04e63342e5901280037ef706b1b114475d3cbb"
},
"files": [
{
"digest": "sha256:a4699e4f9ef3f4922f38f0d017aa26438908f38caf020a739e0ee27fe796eb02",
"path": "my-file.json"
}
]
}
]
}
```

This is all the information required for Spin to be able to push, pull, then run
an application from an OCI registry.

### `spin oci push`

`spin oci push` is intended to give users the ability to distribute their
application using widely available container registry services, giving them
as much flexibility as possible in order to integrate `spin oci push` and `spin up`
into their *existing* workflows. To this end, it is intended to be as unopinionated
as possible when it comes to versioning and tag mutability.

As a result, `spin oci push` should allow the option to accept a user-defined
name and tag for the artifact pushed to the registry:

```bash
$ spin oci push --file <path to spin.toml> myregistry.com/myusername/myapp:v1
# OR
$ spin oci push --file <path to spin.toml> myregistry.com/myusername/myapp:latest
# OR
$ spin oci push --file <path to spin.toml> myregistry.com/myusername/myapp:v0.1.0+r2d2
```

However, it should also preserve deriving the reference and tag from the `spin.toml`
application name and version. For example, for the following `spin.toml`:

```toml
name = "myregistry.com/myusername/myapp"
version = "1.2.3"
```

Running `spin oci push` should result in the application being pushed without
having to specify the same information on the command line again:

```bash
$ spin oci push --file <path to spin.toml>
... Pushed the application to myregistry.com/myusername/myapp:v1.2.3
# OR
$ spin oci push --file <path to spin.toml> --buildinfo
... Pushed the application to myregistry.com/myusername/myapp:v1.2.3+r2d2
```

Note: deriving the reference and tag from `spin.toml` when no explicit value is passed
on the command line means the application name must contain the fully qualified
reference, and the tag can only be a semantic version.

### Impact to `spin deploy`

While `spin oci push` should offer the most flexibility when pushing and tagging
an application, `spin deploy` is part of an opinionated workflow which could
continue to enforce its tag mutability and versioning strategy.

In line with the gradual approach to this change, `spin deploy` will also change
to publish the application to OCI registries, and by the time this change
will be default, Fermyon Platform and Fermyon Cloud will support this change.

The goal is to have at least one minor release of Spin with `spin oci` before
updating the *default* `spin deploy` behavior to expect an OCI registry.
Once Fermyon Platform or Fermyon Cloud support accepting OCI references,
the intention is to implement `spin deploy --oci` functionality before
updating the default behavior.

### Implementation status

The `spin oci push`, `spin oci pull`, and `spin oci run` commands are currently
implemented in [the prototype](https://github.com/fermyon/spin/pull/1014).
The implementation uses the[`oci-distribution`](https://github.com/krustlet/oci-distribution)
crate from Krustlet to interact with a container registry (and currently uses a fork that
should be patched upstream).

The internals of the loaders need additional work before being merged, and the
migration tool from Bindle to OCI has not been started. The `spin oci login`
command has not been implemented.

## FAQ

### Q: How does this effort relate to the [Bytecode Alliance registry project](https://warg.io/)

A: The maintainers of Spin are some of the creators of the registry effort in
the Bytecode Alliance - one of the project's goals is to be able to use existing
storage mechanisms as the storage backends, including OCI registries. Once the
Bytecode Alliance registry project is mature, the Spin project plans to support
it as a distribution mechanism.

1 change: 1 addition & 0 deletions docs/content/sips/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ Here is a list of accepted SIPs:
- [Application Manifest Redesign](../sips/005-manifest-redesign.md)
- [Spin Plugins](../sips/006-spin-plugins.md)
- [Deployment Authentication and Authorisation](../sips/007-deployment-auth.md)
- [Distributing Spin applications using OCI registries](../sips/008-using-oci-registries.md)