Getting started
Disclaimer
- the following examples are written in Go but any language that compiles to wasm is a first class citizen for Yoke. Writing it in Go allows us to take advantage of Kubernete’s core api packages. Languages that do not compile to wasm can still be used but not benefit from Yoke’s asset management. For more information see Why Wasm.
- These examples assume some familiarity with the Go toolchain, and that yoke has already been installed.
Writing your first Flight
The most simple implementation of a Flight, is to simply write the resources you want to standard out:
package main
import "fmt"
var deployment = `apiVersion: apps/v1kind: Deploymentmetadata: name: example-app labels: app: example-appspec: replicas: 2 selector: matchLabels: app: example-app template: metadata: labels: app: example-app spec: containers: - name: example-app-container image: nginx:latest # Replace with your actual container image ports: - containerPort: 80`
func main() { fmt.Println(deployment)}
Compile it:
GOOS=wasip1 GOARCH=wasm go build -o example.wasm ./example.go
Deploy it:
yoke takeoff example ./example.wasm
And we’re done! With this example, you have defined a valid Flight, compiled it to wasm, and had yoke deploy the first revision of a release named example.
However, although illustrative as a first example, we haven’t gained any of the advantages of using code to describe our k8s packages. Indeed the previous example is equivalent to feeding the raw text directly to yoke:
yoke takeoff example < example.yaml
Using Typed Code
Now that we understand that Flights are simply programs that output the resources as JSON/YAML, we can start representing our resources as Go values utilizing our own types and abstractions, or those provided for us by other libraries.
As it happens k8s.io/api is the canonical location of the Kubernetes API definition. Let’s use it to redefine our deployment from the earlier example, and to add a service for it. All this whilst maintaining declarative yet strongly typed code.
The previous example was as unidiomatic as possible. This time we can breakdown the construction of resources into their own functions and create any signature or contract that we want.
package main
import ( "encoding/json" "fmt" "os"
"github.com/yokecd/yoke/pkg/flight"
appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr")
func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) }}
func run() error { var ( release = flight.Release() // the first argument passed to yoke takeoff; ie: yoke takeoff RELEASE foo namespace = flight.Namespace() // the value of the flag namespace during takeoff; ie: yoke takeoff -namespace NAMESPACE ... labels = map[string]string{"app": release} )
resources := []flight.Resource{ CreateDeployment(DeploymentConfig{ Name: release, Namespace: namespace, Labels: labels, Replicas: 2, }), CreateService(ServiceConfig{ Name: release, Namespace: namespace, Labels: labels, Port: 80, TargetPort: 80, }), }
return json.NewEncoder(os.Stdout).Encode(resources)}
type DeploymentConfig struct { Name string Namespace string Labels map[string]string Replicas int32}
func CreateDeployment(cfg DeploymentConfig) *appsv1.Deployment { return &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ APIVersion: appsv1.SchemeGroupVersion.Identifier(), Kind: "Deployment", }, ObjectMeta: metav1.ObjectMeta{ Name: cfg.Name, Namespace: cfg.Namespace, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ MatchLabels: cfg.Labels, }, Replicas: ptr.To(cfg.Replicas), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: cfg.Labels, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: cfg.Name, Image: "alpine:latest", Command: []string{"watch", "echo", "hello world"}, }, }, }, }, }, }}
type ServiceConfig struct { Name string Namespace string Labels map[string]string Port int32 TargetPort int}
func CreateService(cfg ServiceConfig) *corev1.Service { return &corev1.Service{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Service", }, ObjectMeta: metav1.ObjectMeta{ Name: cfg.Name, Namespace: cfg.Namespace, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeClusterIP, Selector: cfg.Labels, Ports: []corev1.ServicePort{ { Protocol: corev1.ProtocolTCP, Port: cfg.Port, TargetPort: intstr.FromInt(cfg.TargetPort), }, }, }, }}
Repeating the steps as in the first example, we:
Compile it:
GOOS=wasip1 GOARCH=wasm go build -o example.wasm ./example.go
Deploy it:
yoke takeoff example ./example.wasm
Take-aways
We have created a second revision of our release called example. It contains both a deployment and a service. Furthermore, it comes with all the advantages we expect from code. It has type-safety, IntelliSense, and documentation is a click away. We may break things out as a reusable functions, create our own contracts, and write tests. The world is your oyster.
In this example, we used the the k8s api packages to build our resource values. However you may use the types resource types directly, or create your own simplified representations.