This is the multi-page printable view of this section. Click here to print.
Using SpinKube
- 1: Selective Deployments in Spin
- 2: Packaging and deploying apps
- 3: Making HTTPS Requests
- 4: Assigning variables
- 5: External Variable Providers
- 6: Connecting to your app
- 7: Monitoring your app
- 8: Using a key value store
- 9: Connecting to a SQLite database
- 10: Autoscaling your apps
- 10.1: Using the `spin kube` plugin
- 10.2: Scaling Spin App With Horizontal Pod Autoscaling (HPA)
- 10.3: Scaling Spin App With Kubernetes Event-Driven Autoscaling (KEDA)
- 11: SpinKube at a glance
1 - Selective Deployments in Spin
This article explains how to selectively deploy a subset of components from your Spin App using Selective Deployments. You will learn how to:
- Scaffold a Specific Component from a Spin Application into a Custom Resource
- Run a Selective Deployment
Selective Deployments allow you to control which components within a Spin app are active for a specific instance of the app. With Component Selectors, Spin and SpinKube can declare at runtime which components should be activated, letting you deploy a single, versioned artifact while choosing which parts to enable at startup. This approach separates developer goals (building a well-architected app) from operational needs (optimizing for specific infrastructure).
Prerequisites
For this tutorial, you’ll need:
- kubectl - the Kubernetes CLI
- Kubernetes cluster with the Spin Operator v0.4 and Containerd Spin Shim v0.17 - follow the Quickstart if needed
spin kube
plugin v0.3 - follow Installing thespin kube
plugin if needed
Scaffold a Specific Component from a Spin Application into a Custom Resource
We’ll use a sample application called “Salutations”, which demonstrates greetings via two components, each responding to a unique HTTP route. If we take a look at the application manifest, we’ll see that this Spin application is comprised of two components:
Hello
component triggered by the/hi
routeGoodbye
component triggered by the/bye
route
spin_manifest_version = 2
[application]
name = "salutations"
version = "0.1.0"
authors = ["Kate Goldenring <kate.goldenring@fermyon.com>"]
description = "An app that gives salutations"
[[trigger.http]]
route = "/hi"
component = "hello"
[component.hello]
source = "../hello-world/main.wasm"
allowed_outbound_hosts = []
[component.hello.build]
command = "cd ../hello-world && tinygo build -target=wasi -gc=leaking -no-debug -o main.wasm main.go"
watch = ["**/*.go", "go.mod"]
[[trigger.http]]
route = "/bye"
component = "goodbye"
[component.goodbye]
source = "main.wasm"
allowed_outbound_hosts = []
[component.goodbye.build]
command = "tinygo build -target=wasi -gc=leaking -no-debug -o main.wasm main.go"
watch = ["**/*.go", "go.mod"]
With Selective Deployments, you can choose to deploy only specific components without modifying the source code. For this example, we’ll deploy just the hello
component.
Note that if you had an Spin application with more than two components, you could choose to deploy multiple components selectively.
To Selectively Deploy, we first need to turn our application into a SpinApp Custom Resource with the spin kube scaffold
command, using the optional --component
field to specify which component we’d like to deploy:
spin kube scaffold --from ghcr.io/spinkube/spin-operator/salutations:20241105-223428-g4da3171 --component hello --replicas 1 --out spinapp.yaml
Now if we take a look at our spinapp.yaml
, we should see that only the hello component will be deployed via Selective Deployments:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: salutations
spec:
image: "ghcr.io/spinkube/spin-operator/salutations:20241105-223428-g4da3171"
executor: containerd-shim-spin
replicas: 1
components:
- hello
Run a Selective Deployment
Now you can deploy your app using kubectl
as you normally would:
# Deploy the spinapp.yaml using kubectl
kubectl apply -f spinapp.yaml
spinapp.core.spinkube.dev/salutations created
We can test that only our hello
component is running by port-forwarding its service.
kubectl port-forward svc/salutations 8083:80
Now let’s call the /hi
route in a seperate terminal:
curl localhost:8083/hi
If the hello component is running correctly, we should see a response of “Hello Fermyon!”:
Hello Fermyon!
Next, let’s try the /bye
route. This should return nothing, confirming that only the hello
component was deployed:
curl localhost:8083/bye
There you have it! You selectively deployed a subset of your Spin application to SpinKube with no modifications to your source code. This approach lets you easily deploy only the components you need, which can improve efficiency in environments where only specific services are required.
2 - Packaging and deploying apps
This article explains how Spin Apps are packaged and distributed via both public and private registries. You will learn how to:
- Package and distribute Spin Apps
- Deploy Spin Apps
- Scaffold Kubernetes Manifests for Spin Apps
- Use private registries that require authentication
Prerequisites
For this tutorial in particular, you need
- TinyGo - for building the Spin app
- kubectl - the Kubernetes CLI
- spin - the Spin CLI
- spin kube - the Kubernetes plugin for
spin
Creating a new Spin App
You use the spin
CLI, to create a new Spin App. The spin
CLI provides different templates, which
you can use to quickly create different kinds of Spin Apps. For demonstration purposes, you will use
the http-go
template to create a simple Spin App.
# Create a new Spin App using the http-go template
spin new --accept-defaults -t http-go hello-spin
# Navigate into the hello-spin directory
cd hello-spin
The spin
CLI created all necessary files within hello-spin
. Besides the Spin Manifest
(spin.toml
), you can find the actual implementation of the app in main.go
:
package main
import (
"fmt"
"net/http"
spinhttp "github.com/fermyon/spin/sdk/go/v2/http"
)
func init() {
spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintln(w, "Hello Fermyon!")
})
}
func main() {}
This implementation will respond to any incoming HTTP request, and return an HTTP response with a
status code of 200 (Ok
) and send Hello Fermyon
as the response body.
You can test the app on your local machine by invoking the spin up
command from within the
hello-spin
folder.
Packaging and Distributing Spin Apps
Spin Apps are packaged and distributed as OCI artifacts. By leveraging OCI artifacts, Spin Apps can be distributed using any registry that implements the Open Container Initiative Distribution Specification (a.k.a. “OCI Distribution Spec”).
The spin
CLI simplifies packaging and distribution of Spin Apps and provides an atomic command for
this (spin registry push
). You can package and distribute the hello-spin
app that you created as
part of the previous section like this:
# Package and Distribute the hello-spin app
spin registry push --build ttl.sh/hello-spin:24h
It is a good practice to add the
--build
flag tospin registry push
. It prevents you from accidentally pushing an outdated version of your Spin App to your registry of choice.
Deploying Spin Apps
To deploy Spin Apps to a Kubernetes cluster which has Spin Operator running, you use the kube
plugin for spin
. Use the spin kube deploy
command as shown here to deploy the hello-spin
app
to your Kubernetes cluster:
# Deploy the hello-spin app to your Kubernetes Cluster
spin kube deploy --from ttl.sh/hello-spin:24h
spinapp.core.spinkube.dev/hello-spin created
You can deploy a subset of components in your Spin Application using Selective Deployments.
Scaffolding Spin Apps
In the previous section, you deployed the hello-spin
app using the spin kube deploy
command.
Although this is handy, you may want to inspect, or alter the Kubernetes manifests before applying
them. You use the spin kube scaffold
command to generate Kubernetes manifests:
spin kube scaffold --from ttl.sh/hello-spin:24h
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: hello-spin
spec:
image: "ttl.sh/hello-spin:24h"
replicas: 2
By default, the command will print all Kubernetes manifests to STDOUT
. Alternatively, you can
specify the out
argument to store the manifests to a file:
# Scaffold manifests to spinapp.yaml
spin kube scaffold --from ttl.sh/hello-spin:24h \
--out spinapp.yaml
# Print contents of spinapp.yaml
cat spinapp.yaml
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: hello-spin
spec:
image: "ttl.sh/hello-spin:24h"
replicas: 2
You can then deploy the Spin App by applying the manifest with the kubectl
CLI:
kubectl apply -f spinapp.yaml
Distributing and Deploying Spin Apps via private registries
It is quite common to distribute Spin Apps through private registries that require some sort of
authentication. To publish a Spin App to a private registry, you have to authenticate using the
spin registry login
command.
For demonstration purposes, you will now distribute the Spin App via GitHub Container Registry (GHCR). You can follow this guide by GitHub to create a new personal access token (PAT), which is required for authentication.
# Store PAT and GitHub username as environment variables
export GH_PAT=YOUR_TOKEN
export GH_USER=YOUR_GITHUB_USERNAME
# Authenticate spin CLI with GHCR
echo $GH_PAT | spin registry login ghcr.io -u $GH_USER --password-stdin
Successfully logged in as YOUR_GITHUB_USERNAME to registry ghcr.io
Once authentication succeeded, you can use spin registry push
to push your Spin App to GHCR:
# Push hello-spin to GHCR
spin registry push --build ghcr.io/$GH_USER/hello-spin:0.0.1
Pushing app to the Registry...
Pushed with digest sha256:1611d51b296574f74b99df1391e2dc65f210e9ea695fbbce34d770ecfcfba581
In Kubernetes you store authentication information as secret of type docker-registry
. The
following snippet shows how to create such a secret with kubectl
leveraging the environment
variables, you specified in the previous section:
# Create Secret in Kubernetes
kubectl create secret docker-registry ghcr \
--docker-server ghcr.io \
--docker-username $GH_USER \
--docker-password $CR_PAT
secret/ghcr created
Scaffold the necessary SpinApp
Custom Resource (CR) using spin kube scaffold
:
# Scaffold the SpinApp manifest
spin kube scaffold --from ghcr.io/$GH_USER/hello-spin:0.0.1 \
--out spinapp.yaml
Before deploying the manifest with kubectl
, update spinapp.yaml
and link the ghcr
secret you
previously created using the imagePullSecrets
property. Your SpinApp
manifest should look like
this:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: hello-spin
spec:
image: ghcr.io/$GH_USER/hello-spin:0.0.1
imagePullSecrets:
- name: ghcr
replicas: 2
executor: containerd-shim-spin
$GH_USER
should match the actual username provided while running through the previous sections of this article
Finally, you can deploy the app using kubectl apply
:
# Deploy the spinapp.yaml using kubectl
kubectl apply -f spinapp.yaml
spinapp.core.spinkube.dev/hello-spin created
3 - Making HTTPS Requests
To enable HTTPS requests, the executor must be configured to use certificates. SpinKube can be configured to use either default or custom certificates.
If you make a request without properly configured certificates, you’ll encounter an error message that reads: error trying to connect: unexpected EOF (unable to get local issuer certificate)
.
Using default certificates
SpinKube can generate a default CA certificate bundle by setting installDefaultCACerts
to true
. This creates a secret named spin-ca
populated with curl’s default bundle. You can specify a custom secret name by setting caCertSecret
.
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinAppExecutor
metadata:
name: containerd-shim-spin
spec:
createDeployment: true
deploymentConfig:
runtimeClassName: wasmtime-spin-v2
installDefaultCACerts: true
Apply the executor using kubectl:
kubectl apply -f myexecutor.yaml
Using custom certificates
Create a secret from your certificate file:
kubectl create secret generic my-custom-ca --from-file=ca-certificates.crt
Configure the executor to use the custom certificate secret:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinAppExecutor
metadata:
name: containerd-shim-spin
spec:
createDeployment: true
deploymentConfig:
runtimeClassName: wasmtime-spin-v2
caCertSecret: my-custom-ca
Apply the executor using kubectl:
kubectl apply -f myexecutor.yaml
4 - Assigning variables
By using variables, you can alter application behavior without recompiling your SpinApp. When
running in Kubernetes, you can either provide constant values for variables, or reference them from
Kubernetes primitives such as ConfigMaps
and Secrets
. This tutorial guides your through the
process of assigning variables to your SpinApp
.
Note: If you’d like to learn how to configure your application with an external variable provider like Vault or Azure Key Vault, see the External Variable Provider guide
Build and Store SpinApp in an OCI Registry
We’re going to build the SpinApp and store it inside of a ttl.sh registry. Move into the apps/variable-explorer directory and build the SpinApp we’ve provided:
# Build and publish the sample app
cd apps/variable-explorer
spin build
spin registry push ttl.sh/variable-explorer:1h
Note that the tag at the end of ttl.sh/variable-explorer:1h
indicates how long the image will last e.g. 1h
(1 hour). The maximum is 24h
and you will need to
repush if ttl exceeds 24 hours.
For demonstration purposes, we use the variable
explorer sample app. It
reads three different variables (log_level
, platform_name
and db_password
) and prints their
values to the STDOUT
stream as shown in the following snippet:
let log_level = variables::get("log_level")?;
let platform_name = variables::get("platform_name")?;
let db_password = variables::get("db_password")?;
println!("# Log Level: {}", log_level);
println!("# Platform name: {}", platform_name);
println!("# DB Password: {}", db_password);
Those variables are defined as part of the Spin manifest (spin.toml
), and access to them is
granted to the variable-explorer
component:
[variables]
log_level = { default = "WARN" }
platform_name = { default = "Fermyon Cloud" }
db_password = { required = true }
[component.variable-explorer.variables]
log_level = "{{ log_level }}"
platform_name = "{{ platform_name }}"
db_password = "{{ db_password }}"
For further reading on defining variables in the Spin manifest, see the Spin Application Manifest Reference.
Configuration data in Kubernetes
In Kubernetes, you use ConfigMaps
for storing non-sensitive, and Secrets
for storing sensitive
configuration data. The deployment manifest (config/samples/variable-explorer.yaml
) contains
specifications for both a ConfigMap
and a Secret
:
kind: ConfigMap
apiVersion: v1
metadata:
name: spinapp-cfg
data:
logLevel: INFO
---
kind: Secret
apiVersion: v1
metadata:
name: spinapp-secret
data:
password: c2VjcmV0X3NhdWNlCg==
Assigning variables to a SpinApp
When creating a SpinApp
, you can choose from different approaches for specifying variables:
- Providing constant values
- Loading configuration values from ConfigMaps
- Loading configuration values from Secrets
The SpinApp
specification contains the variables
array, that you use for specifying variables
(See kubectl explain spinapp.spec.variables
).
The deployment manifest (config/samples/variable-explorer.yaml
) specifies a static value for
platform_name
. The value of log_level
is read from the ConfigMap
called spinapp-cfg
, and the
db_password
is read from the Secret
called spinapp-secret
:
kind: SpinApp
apiVersion: core.spinkube.dev/v1alpha1
metadata:
name: variable-explorer
spec:
replicas: 1
image: ttl.sh/variable-explorer:1h
executor: containerd-shim-spin
variables:
- name: platform_name
value: Kubernetes
- name: log_level
valueFrom:
configMapKeyRef:
name: spinapp-cfg
key: logLevel
optional: true
- name: db_password
valueFrom:
secretKeyRef:
name: spinapp-secret
key: password
optional: false
As the deployment manifest outlines, you can use the optional
property - as you would do when
specifying environment variables for a regular Kubernetes Pod
- to control if Kubernetes should
prevent starting the SpinApp, if the referenced configuration source does not exist.
You can deploy all resources by executing the following command:
kubectl apply -f config/samples/variable-explorer.yaml
configmap/spinapp-cfg created
secret/spinapp-secret created
spinapp.core.spinkube.dev/variable-explorer created
Inspecting runtime logs of your SpinApp
To verify that all variables are passed correctly to the SpinApp, you can configure port forwarding
from your local machine to the corresponding Kubernetes Service
:
kubectl port-forward services/variable-explorer 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
When port forwarding is established, you can send an HTTP request to the variable-explorer from within an additional terminal session:
curl http://localhost:8080
Hello from Kubernetes
Finally, you can use kubectl logs
to see all logs produced by the variable-explorer at runtime:
kubectl logs -l core.spinkube.dev/app-name=variable-explorer
# Log Level: INFO
# Platform Name: Kubernetes
# DB Password: secret_sauce
5 - External Variable Providers
In the Assigning Variables guide, you learned how to configure variables on the SpinApp via its variables section, either by supplying values in-line or via a Kubernetes ConfigMap or Secret.
You can also utilize an external service like Vault or Azure Key Vault to provide variable values for your application. This guide will show you how to use and configure both services in tandem with corresponding sample applications.
Prerequisites
To follow along with this tutorial, you’ll need:
- A Kubernetes cluster running SpinKube. See the Installation guides for more information.
- The kubectl CLI
- The spin CLI
- The kube plugin for Spin
Supported providers
Spin currently supports Vault and Azure Key Vault as external variable providers. Configuration is supplied to the application via a Runtime Configuration file.
In SpinKube, this configuration file can be supplied in the form of a Kubernetes secret and linked to a SpinApp via its runtimeConfig.loadFromSecret section.
Note:
loadFromSecret
takes precedence over any otherruntimeConfig
configuration. Thus, all runtime configuration must be contained in the Kubernetes secret, including SQLite, Key Value and LLM options that might otherwise be specified via their dedicated specs.
Let’s look at examples utilizing specific provider configuration next.
Vault provider
Vault is a popular choice for storing secrets and serving as a secure key-value store.
This guide assumes you have:
- A Vault cluster
- The vault CLI
Build and publish the Spin application
We’ll use the variable explorer app to test this integration.
First, clone the repository locally and navigate to the variable-explorer
directory:
git clone git@github.com:spinkube/spin-operator.git
cd apps/variable-explorer
Now, build and push the application to a registry you have access to. Here we’ll use ttl.sh:
spin build
spin registry push ttl.sh/variable-explorer:1h
Create the runtime-config.toml
file
Here’s a sample runtime-config.toml
file containing Vault provider configuration:
[[config_provider]]
type = "vault"
url = "https://my-vault-server:8200"
token = "my_token"
mount = "admin/secret"
To use this sample, you’ll want to update the url
and token
fields with values applicable to
your Vault cluster. The mount
value will depend on the Vault namespace and kv-v2
secrets engine
name. In this sample, the namespace is admin
and the engine is named secret
, eg by running
vault secrets enable --path=secret kv-v2
.
Create the secrets in Vault
Create the log_level
, platform_name
and db_password
secrets used by the variable-explorer
application in Vault:
vault kv put secret/log_level value=INFO
vault kv put secret/platform_name value=Kubernetes
vault kv put secret/db_password value=secret_sauce
Create the SpinApp and Secret
Next, scaffold the SpinApp and Secret resource (containing the runtime-config.toml
data) together
in one go via the kube
plugin:
spin kube scaffold -f ttl.sh/variable-explorer:1h -c runtime-config.toml -o scaffold.yaml
Deploy the application
kubectl apply -f scaffold.yaml
Test the application
You are now ready to test the application and verify that all variables are passed correctly to the SpinApp from the Vault provider.
Configure port forwarding from your local machine to the corresponding Kubernetes Service
:
kubectl port-forward services/variable-explorer 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
When port forwarding is established, you can send an HTTP request to the variable-explorer from within an additional terminal session:
curl http://localhost:8080
Hello from Kubernetes
Finally, you can use kubectl logs
to see all logs produced by the variable-explorer at runtime:
kubectl logs -l core.spinkube.dev/app-name=variable-explorer
# Log Level: INFO
# Platform Name: Kubernetes
# DB Password: secret_sauce
Azure Key Vault provider
Azure Key Vault is a secure secret store for distributed applications hosted on the Azure platform.
This guide assumes you have:
- An Azure account
- The az CLI
Build and publish the Spin application
We’ll use the Azure Key Vault Provider sample application for this exercise.
First, clone the repository locally and navigate to the azure-key-vault-provider
directory:
git clone git@github.com:fermyon/enterprise-architectures-and-patterns.git
cd enterprise-architectures-and-patterns/application-variable-providers/azure-key-vault-provider
Now, build and push the application to a registry you have access to. Here we’ll use ttl.sh:
spin build
spin registry push ttl.sh/azure-key-vault-provider:1h
The next steps will guide you in creating and configuring an Azure Key Vault and populating the runtime configuration file with connection credentials.
Deploy Azure Key Vault
# Variable Definition
KV_NAME=spinkube-keyvault
LOCATION=westus2
RG_NAME=rg-spinkube-keyvault
# Create Azure Resource Group and Azure Key Vault
az group create -n $RG_NAME -l $LOCATION
az keyvault create -n $KV_NAME \
-g $RG_NAME \
-l $LOCATION \
--enable-rbac-authorization true
# Grab the Azure Resource Identifier of the Azure Key Vault instance
KV_SCOPE=$(az keyvault show -n $KV_NAME -g $RG_NAME -otsv --query "id")
Add a Secret to the Azure Key Vault instance
# Grab the ID of the currently signed in user in Azure CLI
CURRENT_USER_ID=$(az ad signed-in-user show -otsv --query "id")
# Make the currently signed in user a Key Vault Secrets Officer
# on the scope of the new Azure Key Vault instance
az role assignment create --assignee $CURRENT_USER_ID \
--role "Key Vault Secrets Officer" \
--scope $KV_SCOPE
# Create a test secret called 'secret` in the Azure Key Vault instance
az keyvault secret set -n secret --vault-name $KV_NAME --value secret_value -o none
Create a Service Principal and Role Assignment for Spin
SP_NAME=sp-spinkube-keyvault
SP=$(az ad sp create-for-rbac -n $SP_NAME -ojson)
CLIENT_ID=$(echo $SP | jq -r '.appId')
CLIENT_SECRET=$(echo $SP | jq -r '.password')
TENANT_ID=$(echo $SP | jq -r '.tenant')
az role assignment create --assignee $CLIENT_ID \
--role "Key Vault Secrets User" \
--scope $KV_SCOPE
Create the runtime-config.toml
file
Create a runtime-config.toml
file with the following contents, substituting in the values for
KV_NAME
, CLIENT_ID
, CLIENT_SECRET
and TENANT_ID
from the previous steps.
[[config_provider]]
type = "azure_key_vault"
vault_url = "https://<$KV_NAME>.vault.azure.net/"
client_id = "<$CLIENT_ID>"
client_secret = "<$CLIENT_SECRET>"
tenant_id = "<$TENANT_ID>"
authority_host = "AzurePublicCloud"
Create the SpinApp and Secret
Scaffold the SpinApp and Secret resource (containing the runtime-config.toml
data) together in one
go via the kube
plugin:
spin kube scaffold -f ttl.sh/azure-key-vault-provider:1h -c runtime-config.toml -o scaffold.yaml
Deploy the application
kubectl apply -f scaffold.yaml
Test the application
Now you are ready to test the application and verify that the secret resolves its value from Azure Key Vault.
Configure port forwarding from your local machine to the corresponding Kubernetes Service
:
kubectl port-forward services/azure-key-vault-provider 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
When port forwarding is established, you can send an HTTP request to the azure-key-vault-provider app from within an additional terminal session:
curl http://localhost:8080
Loaded secret from Azure Key Vault: secret_value
6 - Connecting to your app
This topic guide shows you how to connect to your application deployed to SpinKube, including how to use port-forwarding for local development, or Ingress rules for a production setup.
Run the sample application
Let’s deploy a sample application to your Kubernetes cluster. We will use this application throughout the tutorial to demonstrate how to connect to it.
Refer to the quickstart guide if you haven’t set up a Kubernetes cluster yet.
kubectl apply -f https://raw.githubusercontent.com/spinkube/spin-operator/main/config/samples/simple.yaml
When SpinKube deploys the application, it creates a Kubernetes Service that exposes the application to the cluster. You can check the status of the deployment with the following command:
kubectl get services
You should see a service named simple-spinapp
with a type of ClusterIP
. This means that the
service is only accessible from within the cluster.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
simple-spinapp ClusterIP 10.43.152.184 <none> 80/TCP 1m
We will use this service to connect to your application.
Port forwarding
This option is useful for debugging and development. It allows you to forward a local port to the service.
Forward port 8083 to the service so that it can be reached from your computer:
kubectl port-forward svc/simple-spinapp 8083:80
You should be able to reach it from your browser at http://localhost:8083:
curl http://localhost:8083
You should see a message like “Hello world from Spin!”.
This is one of the simplest ways to test your application. However, it is not suitable for production use. The next section will show you how to expose your application to the internet using an Ingress controller.
Ingress
Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster. Traffic routing is controlled by rules defined on the Ingress resource.
Here is a simple example where an Ingress sends all its traffic to one Service:
(source: Kubernetes documentation)
An Ingress may be configured to give applications externally-reachable URLs, load balance traffic, terminate SSL / TLS, and offer name-based virtual hosting. An Ingress controller is responsible for fulfilling the Ingress, usually with a load balancer, though it may also configure your edge router or additional frontends to help handle the traffic.
Prerequisites
You must have an Ingress controller to satisfy an Ingress rule. Creating an Ingress rule without a controller has no effect.
Ideally, all Ingress controllers should fit the reference specification. In reality, the various Ingress controllers operate slightly differently. Make sure you review your Ingress controller’s documentation to understand the specifics of how it works.
ingress-nginx is a popular Ingress controller, so we will use it in this tutorial:
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
Wait for the ingress controller to be ready:
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120s
Check the Ingress controller’s external IP address
If your Kubernetes cluster is a “real” cluster that supports services of type LoadBalancer
, it
will have allocated an external IP address or FQDN to the ingress controller.
Check the IP address or FQDN with the following command:
kubectl get service ingress-nginx-controller --namespace=ingress-nginx
It will be the EXTERNAL-IP
field. If that field shows <pending>
, this means that your Kubernetes
cluster wasn’t able to provision the load balancer. Generally, this is because it doesn’t support
services of type LoadBalancer
.
Once you have the external IP address (or FQDN), set up a DNS record pointing to it. Refer to your DNS provider’s documentation on how to add a new DNS record to your domain.
You will want to create an A record that points to the external IP address. If your external IP
address is <EXTERNAL-IP>
, you would create a record like this:
A myapp.spinkube.local <EXTERNAL-IP>
Once you’ve added a DNS record to your domain and it has propagated, proceed to create an ingress resource.
Create an Ingress resource
Create an Ingress resource that routes traffic to the simple-spinapp
service. The following
example assumes that you have set up a DNS record for myapp.spinkube.local
:
kubectl create ingress simple-spinapp --class=nginx --rule="myapp.spinkube.local/*=simple-spinapp:80"
A couple notes about the above command:
simple-spinapp
is the name of the Ingress resource.myapp.spinkube.local
is the hostname that the Ingress will route traffic to. This is the DNS record you set up earlier.simple-spinapp:80
is the Service that SpinKube created for us. The application listens for requests on port 80.
Assuming DNS has propagated correctly, you should see a message like “Hello world from Spin!” when you connect to http://myapp.spinkube.local/.
Congratulations, you are serving a public website hosted on a Kubernetes cluster! 🎉
Connecting with kubectl port-forward
This is a quick way to test your Ingress setup without setting up DNS records or on clusters without
support for services of type LoadBalancer
.
Open a new terminal and forward a port from localhost port 8080 to the Ingress controller:
kubectl port-forward --namespace=ingress-nginx service/ingress-nginx-controller 8080:80
Then, in another terminal, test the Ingress setup:
curl --resolve myapp.spinkube.local:8080:127.0.0.1 http://myapp.spinkube.local:8080/hello
You should see a message like “Hello world from Spin!”.
If you want to see your app running in the browser, update your /etc/hosts
file to resolve
requests from myapp.spinkube.local
to the ingress controller:
127.0.0.1 myapp.spinkube.local
7 - Monitoring your app
This topic guide shows you how to configure SpinKube so your Spin apps export observability data. This data will export to an OpenTelemetry collector which will send it to Jaeger.
Prerequisites
Please ensure you have the following tools installed before continuing:
- A Kubernetes cluster running SpinKube. See the installation guides for more information
- The kubectl CLI
- The Helm CLI
About OpenTelemetry Collector
From the OpenTelemetry documentation:
The OpenTelemetry Collector offers a vendor-agnostic implementation of how to receive, process and export telemetry data. It removes the need to run, operate, and maintain multiple agents/collectors. This works with improved scalability and supports open source observability data formats (e.g. Jaeger, Prometheus, Fluent Bit, etc.) sending to one or more open source or commercial backends.
In our case, the OpenTelemetry collector serves as a single endpoint to receive and route telemetry data, letting us to monitor metrics, traces, and logs via our preferred UIs.
About Jaeger
From the Jaeger documentation:
Jaeger is a distributed tracing platform released as open source by Uber Technologies. With Jaeger you can: Monitor and troubleshoot distributed workflows, Identify performance bottlenecks, Track down root causes, Analyze service dependencies
Here, we have the OpenTelemetry collector send the trace data to Jaeger.
Deploy OpenTelemetry Collector
First, add the OpenTelemetry collector Helm repository:
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
Next, deploy the OpenTelemetry collector to your cluster:
helm upgrade --install otel-collector open-telemetry/opentelemetry-collector \
--set image.repository="otel/opentelemetry-collector-k8s" \
--set nameOverride=otel-collector \
--set mode=deployment \
--set config.exporters.otlp.endpoint=http://jaeger-collector.default.svc.cluster.local:4317 \
--set config.exporters.otlp.tls.insecure=true \
--set config.service.pipelines.traces.exporters\[0\]=otlp \
--set config.service.pipelines.traces.processors\[0\]=batch \
--set config.service.pipelines.traces.receivers\[0\]=otlp \
--set config.service.pipelines.traces.receivers\[1\]=jaeger
Deploy Jaeger
Next, add the Jaeger Helm repository:
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm repo update
Then, deploy Jaeger to your cluster:
helm upgrade --install jaeger jaegertracing/jaeger \
--set provisionDataStore.cassandra=false \
--set allInOne.enabled=true \
--set agent.enabled=false \
--set collector.enabled=false \
--set query.enabled=false \
--set storage.type=memory
Configure the SpinAppExecutor
The SpinAppExecutor
resource determines how Spin applications are deployed in the cluster. The following configuration will ensure that any SpinApp
resource using this executor will send telemetry data to the OpenTelemetry collector. To see a comprehensive list of OTel options for the SpinAppExecutor
, see the API reference.
Create a file called executor.yaml
with the following content:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinAppExecutor
metadata:
name: otel-shim-executor
spec:
createDeployment: true
deploymentConfig:
runtimeClassName: wasmtime-spin-v2
installDefaultCACerts: true
otel:
exporter_otlp_endpoint: http://otel-collector.default.svc.cluster.local:4318
To deploy the executor, run:
kubectl apply -f executor.yaml
Deploy a Spin app to observe
With everything in place, we can now deploy a SpinApp
resource that uses the executor otel-shim-executor
.
Create a file called app.yaml
with the following content:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: otel-spinapp
spec:
image: ghcr.io/spinkube/spin-operator/cpu-load-gen:20240311-163328-g1121986
executor: otel-shim-executor
replicas: 1
Deploy the app by running:
kubectl apply -f app.yaml
Congratulations! You now have a Spin app exporting telemetry data.
Next, we need to generate telemetry data for the Spin app to export. Use the below command to port-forward the Spin app:
kubectl port-forward svc/otel-spinapp 3000:80
In a new terminal window, execute a curl
request:
curl localhost:3000
The request will take a couple of moments to run, but once it’s done, you should see an output similar to this:
fib(43) = 433494437
Interact with Jaeger
To view the traces in Jaeger, use the following port-forward command:
kubectl port-forward svc/jaeger-query 16686:16686
Then, open your browser and navigate to localhost:16686
to interact with Jaeger’s UI.
8 - Using a key value store
Spin applications can utilize a standardized API for persisting data in a key value store. The default key value store in Spin is an SQLite database, which is great for quickly utilizing non-relational local storage without any infrastructure set-up. However, this solution may not be preferable for an app running in the context of SpinKube, where apps are often scaled beyond just one replica.
Thankfully, Spin supports configuring an application with an external key value provider. External providers include Redis or Valkey and Azure Cosmos DB.
Prerequisites
To follow along with this tutorial, you’ll need:
- A Kubernetes cluster running SpinKube. See the Installation guides for more information.
- The kubectl CLI
- The spin CLI
Build and publish the Spin application
For this tutorial, we’ll use a Spin key/value application written with the Go SDK. The application serves a CRUD (Create, Read, Update, Delete) API for managing key/value pairs.
First, clone the repository locally and navigate to the examples/key-value
directory:
git clone git@github.com:fermyon/spin-go-sdk.git
cd examples/key-value
Now, build and push the application to a registry you have access to. Here we’ll use ttl.sh:
export IMAGE_NAME=ttl.sh/$(uuidgen):1h
spin build
spin registry push ${IMAGE_NAME}
Configure an external key value provider
Since we have access to a Kubernetes cluster already running SpinKube, we’ll choose
Valkey for our key value provider and install this provider via Bitnami’s
Valkey Helm chart. Valkey is swappable
for Redis in Spin, though note we do need to supply a URL using the redis://
protocol rather than
valkey://
.
helm install valkey --namespace valkey --create-namespace oci://registry-1.docker.io/bitnamicharts/valkey
As mentioned in the notes shown after successful installation, be sure to capture the valkey password for use later:
export VALKEY_PASSWORD=$(kubectl get secret --namespace valkey valkey -o jsonpath="{.data.valkey-password}" | base64 -d)
Create a Kubernetes Secret for the Valkey URL
The runtime configuration will require the Valkey URL so that it can connect to this provider. As this URL contains the sensitive password string, we will create it as a Secret resource in Kubernetes:
kubectl create secret generic kv-secret --from-literal=valkey-url="redis://:${VALKEY_PASSWORD}@valkey-master.valkey.svc.cluster.local:6379"
Prepare the SpinApp manifest
You’re now ready to assemble the SpinApp custom resource manifest for this application.
- All of the key value config is set under
spec.runtimeConfig.keyValueStores
. See the keyValueStores reference guide for more details. - Here we configure the
default
store to use theredis
provider type and underoptions
supply the Valkey URL (via its Kubernetes secret)
Plug the $IMAGE_NAME
and $DB_URL
values into the manifest below and save as spinapp.yaml
:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: kv-app
spec:
image: "$IMAGE_NAME"
replicas: 1
executor: containerd-shim-spin
runtimeConfig:
keyValueStores:
- name: "default"
type: "redis"
options:
- name: "url"
valueFrom:
secretKeyRef:
name: "kv-secret"
key: "valkey-url"
Create the SpinApp
Apply the resource manifest to your Kubernetes cluster:
kubectl apply -f spinapp.yaml
The Spin Operator will handle the creation of the underlying Kubernetes resources on your behalf.
Test the application
Now you are ready to test the application and verify connectivity and key value storage to the configured provider.
Configure port forwarding from your local machine to the corresponding Kubernetes Service
:
kubectl port-forward services/kv-app 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
When port forwarding is established, you can send HTTP requests to the application from within an additional terminal session. Here are a few examples to get you started.
Create a test
key with value ok!
:
$ curl -i -X POST -d "ok!" localhost:8080/test
HTTP/1.1 200 OK
content-length: 0
date: Mon, 29 Jul 2024 19:58:14 GMT
Get the value for the test
key:
$ curl -i -X GET localhost:8080/test
HTTP/1.1 200 OK
content-length: 3
date: Mon, 29 Jul 2024 19:58:39 GMT
ok!
Delete the value for the test
key:
$ curl -i -X DELETE localhost:8080/test
HTTP/1.1 200 OK
content-length: 0
date: Mon, 29 Jul 2024 19:59:18 GMT
Attempt to get the value for the test
key:
$ curl -i -X GET localhost:8080/test
HTTP/1.1 500 Internal Server Error
content-type: text/plain; charset=utf-8
x-content-type-options: nosniff
content-length: 12
date: Mon, 29 Jul 2024 19:59:44 GMT
no such key
9 - Connecting to a SQLite database
Spin applications can utilize a standardized API for persisting data in a SQLite database. A default database is created by the Spin runtime on the local filesystem, which is great for getting an application up and running. However, this on-disk solution may not be preferable for an app running in the context of SpinKube, where apps are often scaled beyond just one replica.
Thankfully, Spin supports configuring an application with an external SQLite database provider via runtime configuration. External providers include any libSQL databases that can be accessed over HTTPS.
Prerequisites
To follow along with this tutorial, you’ll need:
- A Kubernetes cluster running SpinKube. See the Installation guides for more information.
- The kubectl CLI
- The spin CLI
Build and publish the Spin application
For this tutorial, we’ll use the HTTP CRUD Go SQLite sample application. It is a Go-based app implementing CRUD (Create, Read, Update, Delete) operations via the SQLite API.
First, clone the repository locally and navigate to the http-crud-go-sqlite
directory:
git clone git@github.com:fermyon/enterprise-architectures-and-patterns.git
cd enterprise-architectures-and-patterns/http-crud-go-sqlite
Now, build and push the application to a registry you have access to. Here we’ll use ttl.sh:
export IMAGE_NAME=ttl.sh/$(uuidgen):1h
spin build
spin registry push ${IMAGE_NAME}
Create a LibSQL database
If you don’t already have a LibSQL database that can be used over HTTPS, you can follow along as we set one up via Turso.
Before proceeding, install the turso CLI and sign up for an account, if you haven’t done so already.
Create a new database and save its HTTP URL:
turso db create spinkube
export DB_URL=$(turso db show spinkube --http-url)
Next, create an auth token for this database:
export DB_TOKEN=$(turso db tokens create spinkube)
Create a Kubernetes Secret for the database token
The database token is a sensitive value and thus should be created as a Secret resource in Kubernetes:
kubectl create secret generic turso-auth --from-literal=db-token="${DB_TOKEN}"
Prepare the SpinApp manifest
You’re now ready to assemble the SpinApp custom resource manifest.
- Note the
image
value uses the reference you published above. - All of the SQLite database config is set under
spec.runtimeConfig.sqliteDatabases
. See the sqliteDatabases reference guide for more details. - Here we configure the
default
database to use thelibsql
provider type and underoptions
supply the database URL and auth token (via its Kubernetes secret)
Plug the $IMAGE_NAME
and $DB_URL
values into the manifest below and save as spinapp.yaml
:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: http-crud-go-sqlite
spec:
image: "$IMAGE_NAME"
replicas: 1
executor: containerd-shim-spin
runtimeConfig:
sqliteDatabases:
- name: "default"
type: "libsql"
options:
- name: "url"
value: "$DB_URL"
- name: "token"
valueFrom:
secretKeyRef:
name: "turso-auth"
key: "db-token"
Create the SpinApp
Apply the resource manifest to your Kubernetes cluster:
kubectl apply -f spinapp.yaml
The Spin Operator will handle the creation of the underlying Kubernetes resources on your behalf.
Test the application
Now you are ready to test the application and verify connectivity and data storage to the configured SQLite database.
Configure port forwarding from your local machine to the corresponding Kubernetes Service
:
kubectl port-forward services/http-crud-go-sqlite 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
When port forwarding is established, you can send HTTP requests to the http-crud-go-sqlite app from within an additional terminal session. Here are a few examples to get you started.
Get current items:
$ curl -X GET http://localhost:8080/items
[
{
"id": "8b933c84-ee60-45a1-848d-428ad3259e2b",
"name": "Full Self Driving (FSD)",
"active": true
},
{
"id": "d660b9b2-0406-46d6-9efe-b40b4cca59fc",
"name": "Sentry Mode",
"active": true
}
]
Create a new item:
$ curl -X POST -d '{"name":"Engage Thrusters","active":true}' localhost:8080/items
{
"id": "a5efaa73-a4ac-4ffc-9c5c-61c5740e2d9f",
"name": "Engage Thrusters",
"active": true
}
Get items and see the newly added item:
$ curl -X GET http://localhost:8080/items
[
{
"id": "8b933c84-ee60-45a1-848d-428ad3259e2b",
"name": "Full Self Driving (FSD)",
"active": true
},
{
"id": "d660b9b2-0406-46d6-9efe-b40b4cca59fc",
"name": "Sentry Mode",
"active": true
},
{
"id": "a5efaa73-a4ac-4ffc-9c5c-61c5740e2d9f",
"name": "Engage Thrusters",
"active": true
}
]
10 - Autoscaling your apps
10.1 - Using the `spin kube` plugin
spin kube
command.Horizontal autoscaling support
In Kubernetes, a horizontal autoscaler automatically updates a workload resource (such as a Deployment or StatefulSet) with the aim of automatically scaling the workload to match demand.
Horizontal scaling means that the response to increased load is to deploy more resources. This is different from vertical scaling, which for Kubernetes would mean assigning more memory or CPU to the resources that are already running for the workload.
If the load decreases, and the number of resources is above the configured minimum, a horizontal autoscaler would instruct the workload resource (the Deployment, StatefulSet, or other similar resource) to scale back down.
The Kubernetes plugin for Spin includes autoscaler support, which allows you to tell Kubernetes when
to scale your Spin application up or down based on demand. This tutorial will show you how to enable
autoscaler support via the spin kube scaffold
command.
Prerequisites
Regardless of what type of autoscaling is used, you must determine how you want your application to scale by answering the following questions:
- Do you want your application to scale based upon system metrics (CPU and memory utilization) or based upon events (like messages in a queue or rows in a database)?
- If you application scales based on system metrics, how much CPU and memory each instance does your application need to operate?
Choosing an autoscaler
The Kubernetes plugin for Spin supports two types of autoscalers: Horizontal Pod Autoscaler (HPA) and Kubernetes Event-driven Autoscaling (KEDA). The choice of autoscaler depends on the requirements of your application.
Horizontal Pod Autoscaling (HPA)
Horizontal Pod Autoscaler (HPA) scales Kubernetes pods based on CPU or memory utilization. This HPA
scaling can be implemented via the Kubernetes plugin for Spin by setting the --autoscaler hpa
option. This page deals exclusively with autoscaling via the Kubernetes plugin for Spin.
spin kube scaffold --from user-name/app-name:latest --autoscaler hpa --cpu-limit 100m --memory-limit 128Mi
Horizontal Pod Autoscaling is built-in to Kubernetes and does not require the installation of a third-party runtime. For more general information about scaling with HPA, please see the Spin Operator’s Scaling with HPA section
Kubernetes Event-driven Autoscaling (KEDA)
Kubernetes Event-driven Autoscaling (KEDA) is an extension of Horizontal Pod Autoscaling (HPA). On top of allowing to scale based on CPU or memory utilization, KEDA allows for scaling based on events from various sources like messages in a queue, or the number of rows in a database.
KEDA can be enabled by setting the --autoscaler keda
option:
spin kube scaffold --from user-name/app-name:latest --autoscaler keda --cpu-limit 100m --memory-limit 128Mi -replicas 1 --max-replicas 10
Using KEDA to autoscale your Spin applications requires the installation of the KEDA runtime into your Kubernetes cluster. For more information about scaling with KEDA in general, please see the Spin Operator’s Scaling with KEDA section
Setting min/max replicas
The --replicas
and --max-replicas
options can be used to set the minimum and maximum number of
replicas for your application. The --replicas
option defaults to 2 and the --max-replicas
option
defaults to 3.
spin kube scaffold --from user-name/app-name:latest --autoscaler hpa --cpu-limit 100m --memory-limit 128Mi -replicas 1 --max-replicas 10
Setting CPU/memory limits and CPU/memory requests
If the node where an application is running has enough of a resource available, it’s possible (and allowed) for that application to use more resource than its resource request for that resource specifies. However, an application is not allowed to use more than its resource limit.
For example, if you set a memory request of 256 MiB, and that application is scheduled to a node with 8GiB of memory and no other appplications, then the application can try to use more RAM.
If you set a memory limit of 4GiB for that application, the webassembly runtime will enforce that limit. The runtime prevents the application from using more than the configured resource limit. For example: when a process in the application tries to consume more than the allowed amount of memory, the webassembly runtime terminates the process that attempted the allocation with an out of memory (OOM) error.
The --cpu-limit
, --memory-limit
, --cpu-request
, and --memory-request
options can be used to
set the CPU and memory limits and requests for your application. The --cpu-limit
and
--memory-limit
options are required, while the --cpu-request
and --memory-request
options are
optional.
It is important to note the following:
- CPU/memory requests are optional and will default to the CPU/memory limit if not set.
- CPU/memory requests must be lower than their respective CPU/memory limit.
- If you specify a limit for a resource, but do not specify any request, and no admission-time mechanism has applied a default request for that resource, then Kubernetes copies the limit you specified and uses it as the requested value for the resource.
spin kube scaffold --from user-name/app-name:latest --autoscaler hpa --cpu-limit 100m --memory-limit 128Mi --cpu-request 50m --memory-request 64Mi
Setting target utilization
Target utilization is the percentage of the resource that you want to be used before the autoscaler kicks in. The autoscaler will check the current resource utilization of your application against the target utilization and scale your application up or down based on the result.
Target utilization is based on the average resource utilization across all instances of your application. For example, if you have 3 instances of your application, the target CPU utilization is 50%, and each application is averaging 80% CPU utilization, the autoscaler will continue to increase the number of instances until all instances are averaging 50% CPU utilization.
To scale based on CPU utilization, use the --autoscaler-target-cpu-utilization
option:
spin kube scaffold --from user-name/app-name:latest --autoscaler hpa --cpu-limit 100m --memory-limit 128Mi --autoscaler-target-cpu-utilization 50
To scale based on memory utilization, use the --autoscaler-target-memory-utilization
option:
spin kube scaffold --from user-name/app-name:latest --autoscaler hpa --cpu-limit 100m --memory-limit 128Mi --autoscaler-target-memory-utilization 50
10.2 - Scaling Spin App With Horizontal Pod Autoscaling (HPA)
Horizontal scaling, in the Kubernetes sense, means deploying more pods to meet demand (different from vertical scaling whereby more memory and CPU resources are assigned to already running pods). In this tutorial, we configure HPA to dynamically scale the instance count of our SpinApps to meet the demand.
Prerequisites
Ensure you have the following tools installed:
- Docker - for running k3d
- kubectl - the Kubernetes CLI
- k3d - a lightweight Kubernetes distribution that runs on Docker
- Helm - the package manager for Kubernetes
- Bombardier - cross-platform HTTP benchmarking CLI
We use k3d to run a Kubernetes cluster locally as part of this tutorial, but you can follow these steps to configure HPA autoscaling on your desired Kubernetes environment.
Setting Up Kubernetes Cluster
Run the following command to create a Kubernetes cluster that has the containerd-shim-spin pre-requisites installed: If you have a Kubernetes cluster already, please feel free to use it:
k3d cluster create wasm-cluster-scale \
--image ghcr.io/spinkube/containerd-shim-spin/k3d:v0.17.0 \
-p "8081:80@loadbalancer" \
--agents 2
Deploying Spin Operator and its dependencies
First, you have to install cert-manager to automatically provision and manage TLS certificates (used by Spin Operator’s admission webhook system). For detailed installation instructions see the cert-manager documentation.
# Install cert-manager CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.3/cert-manager.crds.yaml
# Add and update Jetstack repository
helm repo add jetstack https://charts.jetstack.io
helm repo update
# Install the cert-manager Helm chart
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.14.3
Next, run the following commands to install the Spin Runtime Class and Spin Operator Custom Resource Definitions (CRDs):
Note: In a production cluster you likely want to customize the Runtime Class with a
nodeSelector
that matches nodes that have the shim installed. However, in the K3d example, they’re installed on every node.
# Install the RuntimeClass
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.4.0/spin-operator.runtime-class.yaml
# Install the CRDs
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.4.0/spin-operator.crds.yaml
Lastly, install Spin Operator using helm
and the shim executor with the following commands:
# Install Spin Operator
helm install spin-operator \
--namespace spin-operator \
--create-namespace \
--version 0.4.0 \
--wait \
oci://ghcr.io/spinkube/charts/spin-operator
# Install the shim executor
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.4.0/spin-operator.shim-executor.yaml
Great, now you have Spin Operator up and running on your cluster. This means you’re set to create and deploy SpinApps later on in the tutorial.
Set Up Ingress
Use the following command to set up ingress on your Kubernetes cluster. This ensures traffic can reach your SpinApp once we’ve created it in future steps:
# Setup ingress following this tutorial https://k3d.io/v5.4.6/usage/exposing_services/
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hpa-spinapp
port:
number: 80
EOF
Hit enter to create the ingress resource.
Deploy Spin App and HorizontalPodAutoscaler (HPA)
Next up we’re going to deploy the Spin App we will be scaling. You can find the source code of the Spin App in the apps/cpu-load-gen folder of the Spin Operator repository.
We can take a look at the SpinApp and HPA definitions in our deployment file below/. As you can see,
we have set our resources
-> limits
to 500m
of cpu
and 500Mi
of memory
per Spin
application and we will scale the instance count when we’ve reached a 50% utilization in cpu
and
memory
. We’ve also defined support a maximum
replica count of
10 and a minimum replica count of 1:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: hpa-spinapp
spec:
image: ghcr.io/spinkube/spin-operator/cpu-load-gen:20240311-163328-g1121986
enableAutoscaling: true
resources:
limits:
cpu: 500m
memory: 500Mi
requests:
cpu: 100m
memory: 400Mi
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: spinapp-autoscaler
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hpa-spinapp
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
For more information about HPA, please visit the following links:
- Kubernetes Horizontal Pod Autoscaling
- Kubernetes HorizontalPodAutoscaler Walkthrough
- HPA Container Resource Metrics
Below is an example of the configuration to scale resources:
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: hpa-spinapp
spec:
image: ghcr.io/spinkube/spin-operator/cpu-load-gen:20240311-163328-g1121986
executor: containerd-shim-spin
enableAutoscaling: true
resources:
limits:
cpu: 500m
memory: 500Mi
requests:
cpu: 100m
memory: 400Mi
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: spinapp-autoscaler
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hpa-spinapp
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
Let’s deploy the SpinApp and the HPA instance onto our cluster (using the above .yaml
configuration). To apply the above configuration we use the following kubectl apply
command:
# Install SpinApp and HPA
kubectl apply -f https://raw.githubusercontent.com/spinkube/spin-operator/main/config/samples/hpa.yaml
You can see your running Spin application by running the following command:
kubectl get spinapps
NAME AGE
hpa-spinapp 92m
You can also see your HPA instance with the following command:
kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
spinapp-autoscaler Deployment/hpa-spinapp 6%/50% 1 10 1 97m
Please note: The Kubernetes Plugin for Spin is a tool designed for Kubernetes integration with the Spin command-line interface. The Kubernetes Plugin for Spin has a scaling tutorial that demonstrates how to use the
spin kube
command to tell Kubernetes when to scale your Spin application up or down based on demand).
Generate Load to Test Autoscale
Now let’s use Bombardier to generate traffic to test how well HPA scales our SpinApp. The following Bombardier command will attempt to establish 40 connections during a period of 3 minutes (or less). If a request is not responded to within 5 seconds that request will timeout:
# Generate a bunch of load
bombardier -c 40 -t 5s -d 3m http://localhost:8081
To watch the load, we can run the following command to get the status of our deployment:
kubectl describe deploy hpa-spinapp
...
---
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: hpa-spinapp-544c649cf4 (1/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set hpa-spinapp-544c649cf4 to 1
Normal ScalingReplicaSet 9m45s deployment-controller Scaled up replica set hpa-spinapp-544c649cf4 to 4
Normal ScalingReplicaSet 9m30s deployment-controller Scaled up replica set hpa-spinapp-544c649cf4 to 8
Normal ScalingReplicaSet 9m15s deployment-controller Scaled up replica set hpa-spinapp-544c649cf4 to 10
10.3 - Scaling Spin App With Kubernetes Event-Driven Autoscaling (KEDA)
KEDA extends Kubernetes to provide event-driven scaling capabilities, allowing it
to react to events from Kubernetes internal and external sources using KEDA
scalers. KEDA provides a wide variety of scalers to define
scaling behavior base on sources like CPU, Memory, Azure Event Hubs, Kafka, RabbitMQ, and more. We
use a ScaledObject
to dynamically scale the instance count of our SpinApp to meet the demand.
Prerequisites
Please ensure the following tools are installed on your local machine:
- kubectl - the Kubernetes CLI
- Helm - the package manager for Kubernetes
- Docker - for running k3d
- k3d - a lightweight Kubernetes distribution that runs on Docker
- Bombardier - cross-platform HTTP benchmarking CLI
We use k3d to run a Kubernetes cluster locally as part of this tutorial, but you can follow these steps to configure KEDA autoscaling on your desired Kubernetes environment.
Setting Up Kubernetes Cluster
Run the following command to create a Kubernetes cluster that has the containerd-shim-spin pre-requisites installed: If you have a Kubernetes cluster already, please feel free to use it:
k3d cluster create wasm-cluster-scale \
--image ghcr.io/spinkube/containerd-shim-spin/k3d:v0.17.0 \
-p "8081:80@loadbalancer" \
--agents 2
Deploying Spin Operator and its dependencies
First, you have to install cert-manager to automatically provision and manage TLS certificates (used by Spin Operator’s admission webhook system). For detailed installation instructions see the cert-manager documentation.
# Install cert-manager CRDs
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.3/cert-manager.crds.yaml
# Add and update Jetstack repository
helm repo add jetstack https://charts.jetstack.io
helm repo update
# Install the cert-manager Helm chart
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--version v1.14.3
Next, run the following commands to install the Spin Runtime Class and Spin Operator Custom Resource Definitions (CRDs):
Note: In a production cluster you likely want to customize the Runtime Class with a
nodeSelector
that matches nodes that have the shim installed. However, in the K3d example, they’re installed on every node.
# Install the RuntimeClass
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.4.0/spin-operator.runtime-class.yaml
# Install the CRDs
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.4.0/spin-operator.crds.yaml
Lastly, install Spin Operator using helm
and the shim executor with the following commands:
# Install Spin Operator
helm install spin-operator \
--namespace spin-operator \
--create-namespace \
--version 0.4.0 \
--wait \
oci://ghcr.io/spinkube/charts/spin-operator
# Install the shim executor
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.4.0/spin-operator.shim-executor.yaml
Great, now you have Spin Operator up and running on your cluster. This means you’re set to create and deploy SpinApps later on in the tutorial.
Set Up Ingress
Use the following command to set up ingress on your Kubernetes cluster. This ensures traffic can reach your Spin App once we’ve created it in future steps:
# Setup ingress following this tutorial https://k3d.io/v5.4.6/usage/exposing_services/
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: keda-spinapp
port:
number: 80
EOF
Hit enter to create the ingress resource.
Setting Up KEDA
Use the following command to setup KEDA on your Kubernetes cluster using Helm. Different deployment methods are described at Deploying KEDA on keda.sh:
# Add the Helm repository
helm repo add kedacore https://kedacore.github.io/charts
# Update your Helm repositories
helm repo update
# Install the keda Helm chart into the keda namespace
helm install keda kedacore/keda --namespace keda --create-namespace
Deploy Spin App and the KEDA ScaledObject
Next up we’re going to deploy the Spin App we will be scaling. You can find the source code of the Spin App in the apps/cpu-load-gen folder of the Spin Operator repository.
We can take a look at the SpinApp
and the KEDA ScaledObject
definitions in our deployment files
below. As you can see, we have explicitly specified resource limits to 500m
of cpu
(spec.resources.limits.cpu
) and 500Mi
of memory
(spec.resources.limits.memory
) per
SpinApp
:
# https://raw.githubusercontent.com/spinkube/spin-operator/main/config/samples/keda-app.yaml
apiVersion: core.spinkube.dev/v1alpha1
kind: SpinApp
metadata:
name: keda-spinapp
spec:
image: ghcr.io/spinkube/spin-operator/cpu-load-gen:20240311-163328-g1121986
executor: containerd-shim-spin
enableAutoscaling: true
resources:
limits:
cpu: 500m
memory: 500Mi
requests:
cpu: 100m
memory: 400Mi
---
We will scale the instance count when we’ve reached a 50% utilization in cpu
(spec.triggers[cpu].metadata.value
). We’ve also instructed KEDA to scale our SpinApp horizontally
within the range of 1 (spec.minReplicaCount
) and 20 (spec.maxReplicaCount
).:
# https://raw.githubusercontent.com/spinkube/spin-operator/main/config/samples/keda-scaledobject.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: cpu-scaling
spec:
scaleTargetRef:
name: keda-spinapp
minReplicaCount: 1
maxReplicaCount: 20
triggers:
- type: cpu
metricType: Utilization
metadata:
value: "50"
The Kubernetes documentation is the place to learn more about limits and requests. Consult the KEDA documentation to learn more about ScaledObject and KEDA’s built-in scalers.
Let’s deploy the SpinApp and the KEDA ScaledObject instance onto our cluster with the following command:
# Deploy the SpinApp
kubectl apply -f https://raw.githubusercontent.com/spinkube/spin-operator/main/config/samples/keda-app.yaml
spinapp.core.spinkube.dev/keda-spinapp created
# Deploy the ScaledObject
kubectl apply -f https://raw.githubusercontent.com/spinkube/spin-operator/main/config/samples/keda-scaledobject.yaml
scaledobject.keda.sh/cpu-scaling created
You can see your running Spin application by running the following command:
kubectl get spinapps
NAME READY REPLICAS EXECUTOR
keda-spinapp 1 containerd-shim-spin
You can also see your KEDA ScaledObject instance with the following command:
kubectl get scaledobject
NAME SCALETARGETKIND SCALETARGETNAME MIN MAX TRIGGERS READY ACTIVE AGE
cpu-scaling apps/v1.Deployment keda-spinapp 1 20 cpu True True 7m
Generate Load to Test Autoscale
Now let’s use Bombardier to generate traffic to test how well KEDA scales our SpinApp. The following Bombardier command will attempt to establish 40 connections during a period of 3 minutes (or less). If a request is not responded to within 5 seconds that request will timeout:
# Generate a bunch of load
bombardier -c 40 -t 5s -d 3m http://localhost:8081
To watch the load, we can run the following command to get the status of our deployment:
kubectl describe deploy keda-spinapp
...
---
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: keda-spinapp-76db5d7f9f (1/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 84s deployment-controller Scaled up replica set hpa-spinapp-76db5d7f9f to 2 from 1
Normal ScalingReplicaSet 69s deployment-controller Scaled up replica set hpa-spinapp-76db5d7f9f to 4 from 2
Normal ScalingReplicaSet 54s deployment-controller Scaled up replica set hpa-spinapp-76db5d7f9f to 8 from 4
Normal ScalingReplicaSet 39s deployment-controller Scaled up replica set hpa-spinapp-76db5d7f9f to 16 from 8
Normal ScalingReplicaSet 24s deployment-controller Scaled up replica set hpa-spinapp-76db5d7f9f to 20 from 16
11 - SpinKube at a glance
spin-operator
Spin Operator is a Kubernetes operator which empowers platform engineers to deploy Spin applications as custom resources to their Kubernetes clusters. Spin Operator provides an elegant solution for platform engineers looking to improve efficiency without compromising on performance while maintaining workload portability.
Why Spin Operator?
By bringing the power of the Spin framework to Kubernetes clusters, Spin Operator provides application developers and platform engineers with the best of both worlds. For developers, this means easily building portable serverless functions that leverage the power and performance of Wasm via the Spin developer tool. For platform engineers, this means using idiomatic Kubernetes primitives (secrets, autoscaling, etc.) and tooling to manage these workloads at scale in a production environment, improving their overall operational efficiency.
How Does Spin Operator Work?
Built with the kubebuilder framework, Spin Operator is a Kubernetes operator. Kubernetes operators are used to extend Kubernetes automation to new objects, defined as custom resources, without modifying the Kubernetes API. The Spin Operator is composed of two main components:
- A controller that defines and manages Wasm workloads on k8s.
- The “SpinApps” Custom Resource Definition (CRD).
SpinApps CRDs can be composed manually or
generated automatically from an existing Spin application using the spin kube scaffold
command. The former approach lends itself well to CI/CD systems,
whereas the latter is a better fit for local testing as part of a local developer workflow.
Once an application deployment begins, Spin Operator handles scheduling the workload on the
appropriate nodes (thanks to the Runtime Class Manager, previously known
as Kwasm) and managing the resource’s lifecycle. There is no need to fetch the
containerd-shim-spin
binary or mutate node labels. This is all managed
via the Runtime Class Manager, which you will install as a dependency when setting up Spin Operator.
containerd-shim-spin
The containerd-shim-spin
is a containerd
shim
implementation for Spin, which enables running Spin workloads
on Kubernetes via runwasi. This means that by installing this
shim onto Kubernetes nodes, we can add a runtime
class to Kubernetes and schedule
Spin workloads on those nodes. Your Spin apps can act just like container workloads!
The containerd-shim-spin
is specifically designed to execute applications built with
Spin (a developer tool for building and running serverless Wasm
applications). The shim ensures that Wasm workloads can be managed effectively within a Kubernetes
environment, leveraging containerd’s capabilities.
In a Kubernetes cluster, specific nodes can be bootstrapped with Wasm runtimes and labeled
accordingly to facilitate the scheduling of Wasm workloads. RuntimeClasses
in Kubernetes are used
to schedule Pods to specific nodes and target specific runtimes. By defining a RuntimeClass
with
the appropriate NodeSelector
and handler, Kubernetes can direct Wasm workloads to nodes equipped
with the necessary Wasm runtimes and ensure they are executed with the correct runtime handler.
Overall, the Containerd Shim Spin represents a significant advancement in integrating Wasm workloads into Kubernetes clusters, enhancing the versatility and capabilities of container orchestration.
runtime-class-manager
The Runtime Class Manager, also known as the Containerd Shim Lifecycle Operator, is designed to automate and manage the lifecycle of containerd shims in a Kubernetes environment. This includes tasks like installation, update, removal, and configuration of shims, reducing manual errors and improving reliability in managing WebAssembly (Wasm) workloads and other containerd extensions.
The Runtime Class Manager provides a robust and production-ready solution for installing, updating, and removing shims, as well as managing node labels and runtime classes in a Kubernetes environment.
By automating these processes, the runtime-class-manager enhances reliability, reduces human error, and simplifies the deployment and management of containerd shims in Kubernetes clusters.
spin-plugin-kube
The Kubernetes plugin for Spin is designed to enhance Spin by enabling the execution of Wasm modules directly within a Kubernetes cluster. Specifically a tool designed for Kubernetes integration with the Spin command-line interface. This plugin works by integrating with containerd shims, allowing Kubernetes to manage and run Wasm workloads in a way similar to traditional container workloads.
The Kubernetes plugin for Spin allows developers to use the Spin command-line interface for deploying Spin applications; it provides a seamless workflow for building, pushing, deploying, and managing Spin applications in a Kubernetes environment. It includes commands for scaffolding new components as Kubernetes manifests, and deploying and retrieving information about Spin applications running in Kubernetes. This plugin is an essential tool for developers looking to streamline their Spin application deployment on Kubernetes platforms.