deislabs / containerd-wasm-shims

containerd shims for running WebAssembly workloads in Kubernetes
Apache License 2.0
303 stars 47 forks source link

feat(spin): conditionally load runtime config from root #179

Closed kate-goldenring closed 10 months ago

kate-goldenring commented 10 months ago

Adds support for Runtime Config in the shim by expecting it to be loaded into the root of a container. This is fairly prescriptive. We may want to support detecting/finding the runtime config in Spin runtime instead of passing it as a flag to the Spin CLI. If we come up with a Spin solution to this, we may need to update the shim to be aligned with whatever approach we choose in Spin. But this unlocks a lot of use cases with the Spin shim now.

Test it out

To test it app, you can use a runtime config to use a non-default KV store. A build example can be pulled from here (http://ghcr.io/kate-goldenring/keyvalue:latest). It is a Spin 1.x app but should still run on the 2.0 shim. I included a section with the app contents below. The Dockerfile for the app looks like:

FROM scratch
COPY ./spin.toml ./spin.toml
COPY ./target/wasm32-wasi/release/keyvalue.wasm ./target/wasm32-wasi/release/keyvalue.wasm
COPY ./runtime-config.toml ./runtime-config.toml

And the runtime config (runtime-config.toml) defines a new store named foo. The app allows this store in the Spin.toml (key_value_stores = ["foo"]).

# This defines a new store named user_data
[key_value_store.foo]
type = "spin" 
path = "user_data.db"

Download the shim from the action of this PR (or can use a 1.0 shim with k3d:v0.9.2 with these changes here https://github.com/kate-goldenring/containerd-wasm-shims/suites/17958017983/artifacts/1032496968).

k3d cluster create wasm-cluster --image ghcr.io/deislabs/containerd-wasm-shims/examples/k3d:v0.9.3 -p "8081:80@loadbalancer" --agents 1
# Apply the wasmtime-spin runtime class
cat <<EOF | kubectl create -f -
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: "wasmtime-spin"
handler: "spin"
EOF
# Get the container ID for both the server and agent containers
docker container ls | grep deis | awk '{print $1}'
# Copy the shim into the agent and server containers
docker cp ~/Downloads/containerd-shim-spin $CONTAINER_ID_SRV:/bin/containerd-shim-spin
docker cp ~/Downloads/containerd-shim-spin $CONTAINER_ID_AGENT:/bin/containerd-shim-spin
# get the Spin app
docker pull ghcr.io/kate-goldenring/keyvalue:latest

Create a deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: keyvalue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: keyvalue
  template:
    metadata:
      labels:
        app: keyvalue
    spec:
      runtimeClassName: wasmtime-spin
      containers:
        - name: keyvalue
          image: ghcr.io/kate-goldenring/keyvalue:latest
          command: ["/"]
          imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
  name: keyvalue
spec:
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: keyvalue
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: keyvalue
  annotations:
    ingress.kubernetes.io/ssl-redirect: "false"
    kubernetes.io/ingress.class: traefik
spec:
  rules:
    - http:
        paths:
          - path: /keyvalue
            pathType: Prefix
            backend:
              service:
                name: keyvalue
                port:
                  number: 80

Apply toml and ping the app:

$ curl -v http://0.0.0.0:8081/keyvalue
wow

Contents of the Spin App

Example Spin manifest (notice key_value_stores = ["foo"]). This is a 1.0 manifest. Feel free to update to use 2.0

spin_manifest_version = "1"
authors = ["Kate Goldenring <kate.goldenring@fermyon.com>"]
description = "hello"
name = "keyvalue"
trigger = { type = "http", base = "/" }
version = "0.1.0"

[[component]]
id = "keyvalue"
source = "target/wasm32-wasi/release/keyvalue.wasm"
allowed_http_hosts = []
key_value_stores = ["foo"]
[component.trigger]
route = "/..."
[component.build]
command = "cargo build --target wasm32-wasi --release"
watch = ["src/**/*.rs", "Cargo.toml"]

Example implementation which gets and sets a value to the non default store:

use anyhow::Result;
use spin_sdk::{
    http::{Request, Response},
    http_component,
    key_value::Store,
};

/// A simple Spin HTTP component.
#[http_component]
fn handle_kv(_req: Request) -> Result<Response> {
    let store = Store::open("foo")?;
    store.set("mykey", "wow")?;
    let value = store.get("mykey").unwrap_or_else(|_| "not found".into());
    Ok(http::Response::builder()
    .status(200)
    .header("foo", "bar")
    .body(Some(value.into()))?)
}
jsturtevant commented 10 months ago

This is fairly prescriptive. We may want to support detecting/finding the runtime config in Spin runtime instead of passing it as a flag to the Spin CLI.

The same goes for the Spin.toml file as it is now, so this approach makes sense to me

LGTM

Mossaka commented 10 months ago

This is great, thanks!