Run WebAssembly on DigitalOcean Kubernetes with SpinKube - In 4 Easy Steps
Tags: webassembly, kubernetes, cloud, containers
Published: 2024-03-27 14:37:39

[DigitalOcean](https://www.digitalocean.com/) is a cloud infrastructure provider catering to developers, offering scalable virtual servers, storage solutions, networking services, and managed Kubernetes clusters. It simplifies application deployment, management, and scaling through its intuitive user interface and CLI (`doctl`), allowing developers to efficiently utilize cloud resources for their projects. 

As part of this article, you'll learn how to

1. Provision a new Kubernetes cluster on DigitalOcean
2. Deploy [SpinKube](https://www.spinkube.dev/)
3. Create a simple WebAssembly app using Spin 💫 and Rust 🦀
4. Deploy the Spin App to Kubernetes using an OCI compliant registry

## What is SpinKube

SpinKube is an open-source project that allows you to run WebAssembly workloads on top of Kubernetes. Essentially, SpinKube is a stack that consists of the following components:

- [Spin Operator](https://github.com/spinkube/spin-operator)
- [containerd-shim-spin](https://github.com/spinkube/containerd-shim-spin)
- [Runtime Class Manager](https://github.com/spinkube/runtime-class-manager) (formerly [KWasm](https://kwasm.sh/))

In addition to running WebAssembly workloads side-by-side with containers, you can run workloads more efficiently and with higher density. Because of WebAssembly’s portability, you can run the same WebAssembly workload on different architectures to reduce cloud spendings even more by using - for example - arm64 worker nodes 🚀.

> At KubeCon EU 2024 - The application for contributing SpinKube to the Cloud Native Computing Foundation (CNCF) has been filed. [Click here to watch the keynote recording](https://www.youtube.com/watch?v=tu8a-GefJL8).

## Prerequisites

To follow along this article, the following software must be installed on your local machine: 

- The Spin CLI (`spin`)
    - See [installation instructions](https://developer.fermyon.com/spin/v2/install)
    - The `kube` plugin for `spin`
        - Install it via `spin plugins install kube`
- Rust 
    - See [installation instructions](https://www.rust-lang.org/tools/install)
    - The `wasm32-wasi` target
        - Install it via `rustup target add wasm32-wasi`
- A DigitalOcean Account & the DigitalOcean CLI (`doctl`)
    - See [installation instructions]( https://docs.digitalocean.com/reference/doctl/how-to/install/)
- The Kubernetes CLI (`kubectl`)
    - See [installation instructions](https://kubernetes.io/docs/tasks/tools/#kubectl)
- The Helm CLI (`helm`)
    - See [installation instructions](https://helm.sh/docs/intro/install/)

> Although the DigitalOcean account itself is free, you have to provide payment information and you will be charged for allocating resources and renting services - such as the  Managed Kubernetes Cluster we will create as part of this article.

## 1. Provision a Managed Kubernetes Cluster in DigitalOcean

Although the DigitalOcean Control Panel is an easy to grasp UI for provisioning cloud resources, we will use `doctl` CLI and its `doctl kubernetes cluster create` command to provision the managed Kubernetes cluster. Execute the following command, to provision a cluster consisting of two worker nodes:

```bash
# Variables
location=fra1
node_size=s-4vcpu-8gb-intel

# Create a new Kubernetes cluster
doctl kubernetes cluster create my-spinkube-cluster \
  --region $location \
  --count 2 \  
  --size $node_size \
  --set-current-context \
  --wait
```

> DigitalOcean provides different regions and machine sizes for managed Kubernetes clusters. Use `doctl kubernetes options regions` to get a list of all regions. By running
`doctl kubernetes options sizes` you get a list of all supported machine sizes for Kubernetes worker nodes.

Once the managed Kubernetes cluster is provisioned, we can double-check if the corresponding context has been added to `kubectl` and is marked as the current context:

```bash
# List all contexts of kubectlkubectl config get-contexts
CURRENT   NAME                  CLUSTER 
*         my-spinkube-cluster   my-spinkube-cluster 
          k3d-wasm-cluster      k3d-wasm-cluster    

# Set current context to my-spinkube-cluster (if not already)
kubectl config use-context my-spinkube-cluster
Switched to context "my-spinkube-cluster".
```

## 2. Install SpinKube

On top of its core components, SpinKube depends on [cert-manager](https://cert-manager.io). `cert-Manager` is responsible for provisioning and managing TLS certificates that are used by the admission webhook system of the Spin Operator. Let’s install `cert-manager` and `KWasm` using the commands shown here:

```bash
# Install cert-manager CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.crds.yaml

# Add Helm repositories jetstack and KWasm
helm repo add jetstack https://charts.jetstack.io
helm repo add kwasm http://kwasm.sh/kwasm-operator

# Update Helm repositories
helm repo update

# Install cert-manager using Helm
helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.14.4
  
# Install KWasm operator
helm install \
  kwasm-operator kwasm/kwasm-operator \
  --namespace kwasm \
  --create-namespace \
  --set kwasmOperator.installerImage=ghcr.io/spinkube/containerd-shim-spin/node-installer:v0.13.1
```

KWasm will install `containerd-shim-spin` on Kubernetes nodes annotated with `kwasm.sh/kwasm-node=true`. For the sake of this article, we will annotate all nodes of our cluster using the following command:

```bash
# Annotate all Kubernetes nodes with kwasm.sh/kwasm-node=true
kubectl annotate node --all kwasm.sh/kwasm-node=true
```

Finally, we can install the Spin Operator on the Kubernetes cluster along with necessary Custom Resource Definitions (CRDs), the `RuntimeClass` and the `SpinAppExecutor`:

```bash
# Install SpinKube CRDs
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.1.0/spin-operator.crds.yaml

# Install a RuntimeClass for wasmtime-spin-v2
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.1.0/spin-operator.runtime-class.yaml

# Install the containerd-spin-shim SpinAppExecutor
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.1.0/spin-operator.shim-executor.yaml

# Install Spin Operator with Helm
helm install spin-operator \
  --namespace spin-operator \
  --create-namespace \
  --version 0.1.0 \
  --wait \
  oci://ghcr.io/spinkube/charts/spin-operator
```

## 3. Create a Spin App

Having SpinKube installed on our managed Kubernetes cluster, we can build a simple Spin App for verification purposes. We will use the `http-rust` template and apply some small changes to the app generated by `spin`:

```bash
# Create a new Spin App
spin new -t http-rust --accept-defaults hello-do-k8s

# Move into the Spin App
cd hello-do-k8s
```

Modify the implementation ( in `./src/lib.rs`) to match the following:

```rust
use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;

#[http_component]
fn handle_hello_do_k8s(req: Request) -> anyhow::Result<impl IntoResponse> {
    println!("Handling request to {:?}", req.header("spin-full-url"));
    
    Ok(Response::builder()
        .status(200)
        .header("content-type", "text/plain")
        .body("Hello from DigitalOcean Kubernetes")
        .build())
}
```

With this, we expect our Spin App to respond on incoming HTTP requests with an HTTP `200`, a Content-Type of `text/plain` and payload of `Hello from DigitalOcean Kubernetes`.

Compile the Spin App using `spin build` and use `spin up` to test your app locally:

```bash
# Build the Spin App
spin build

## snip 
Finished release [optimized] target(s) in 8.92s
Finished building all Spin components

# Run the Spin App locally
spin up
Logging component stdio to ".spin/logs/"Serving http://127.0.0.1:3000
Available Routes:
  hello-do-k8s: http://127.0.0.1:3000 (wildcard)

# You can terminate the app at any time using CTRL+C
```

You can test the Spin App locally, by sending an HTTP request to `localhost:3000` from within a new terminal instance:

```bash
# Send a HTTP GET request to localhost:3000
curl -iX GET http://localhost:3000
HTTP/1.1 200 OK
content-type: text/plain
transfer-encoding: chunked
date: Tue, 25 Mar 2024 11:40:28 GMT
Hello from DigitalOcean Kubernetes
```

## 4. Deploy the Spin App via OCI compliant registry

Spin Apps are distributed as OCI artifacts, meaning we can choose from many different container registries for distributing them. For demonstration purposes, we will use [ttl.sh](https://ttl.sh/) (an anonymous and ephemeral Docker image registry) in which the tag of an artifact defines how long the artifact remains available (TTL).

We use the `spin registry push` command for distributing Spin Apps through OCI compliant registries.

> You can also use the `spin registry login` command to authenticate with private registries and distribute your Spin Apps without making them publicly available at all.

```bash
# Build the Spin App
spin build

# Publish the Spin App as OCI artifact
# The artifact will remain accessible for 24 hours (24h tag)
spin registry push ttl.sh/hello-do-k8s:24h
Pushing app to the
Pushed with digest sha256:b77b7cd7644be0b32613cbec1be5049eb96c7e536377165c4c08e1467c4087b2
```

Finally, we deploy our Spin App to the managed Kubernetes cluster running on DigitalOcean. Again, we use the `spin` CLI to scaffold the necessary Kubernetes manifests and pipe them to `kubectl` which will interact with Kubernetes API server and provision the `SpinApp` custom resource (CR):

```bash
# Deploy Spin App to Kubernetes
spin kube scaffold -f ttl.sh/hello-do-k8s:24h | kubectl apply -f -
spinapp.core.spinoperator.dev/hello-do-k8s created
```

Let’s use `kubectl` and take a closer look at what we got:

```bash
# List Spin Apps in the default namespacekubectl get spinapps
NAME            READY   DESIRED   EXECUTOR
hello-do-k8s    2       2         containerd-shim-spin

# List Deploymentskubectl get deployments
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
hello-do-k8s    2/2     2            2           103s

# List Pods (wide)kubectl get pods -o wide
NAME                             READY   STATUS     AGE     IP
hello-do-k8s-665676d5bd-9gt4s    1/1     Running    2m14s   10.42.0.7
hello-do-k8s-665676d5bd-5fjpm    1/1     Running    2m14s   10.42.1.7

# List all Services and their Endpointskubectl get services,endpoints
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/hello-do-k8s    ClusterIP   10.43.75.164    <none>        80/TCP    4m7s

NAME                      ENDPOINTS                   AGE
endpoints/hello-do-k8s    10.42.0.7:80,10.42.1.7:80   4m7s
```

As you can see, the Spin Operator takes care of all the Kubernetes primitives for running and accessing our Spin App.

## Testing the Spin App running on Kubernetes

Let’s configure port forwarding, to access the Spin App using `curl` from our local machine:

```bash
# Start port-forwarding
kubectl port-forward services/hello-do-k8s 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
```

Use a new terminal instance and send an HTTP request to `locahost:8080` and verify receiving the expected response back:

```bash
# Send an HTTP GET request to localhost:8080
curl -iX GET http://localhost:8080
HTTP/1.1 200 OK
content-type: text/plain
transfer-encoding: chunked
date: Tue, 25 Mar 2024 11:48:21 GMT
Hello from DigitalOcean Kubernetes
```

## Cleaning up the Cloud Resources

To remove the cloud infrastructure we created as part of this article from your DigitalOcean account, run the following command:

```bash
# Delete the DigitalOcean managed Kubernetes cluster
doctl kubernetes cluster delete my-spinkube-cluster --force
```

## Conclusion

By deploying SpinKube onto your managed Kubernetes clusters in DigitalOcean, you can run WebAssembly workloads right beside containers. As WebAssembly apps are way smaller than containers, you can run more workloads on the same amount of compute resources. As you’ve learned in this article, the deployment of SpinKube is simple and requires only a couple of steps.

If you want to learn more about SpinKube, consider reading the [SpinKube documentation](https://www.spinkube.dev/docs/overview/). There are plenty of tutorials, like, for example:

- [Scaling Spin App With Horizontal Pod Autoscaling (HPA)](https://www.spinkube.dev/docs/spin-operator/tutorials/scaling-with-hpa/)
- [Scaling Spin App With Kubernetes Event-Driven Autoscaling (KEDA)](https://www.spinkube.dev/docs/spin-operator/tutorials/scaling-with-keda/)