5 min read

Cdk8s vs Helm for Kubernetes

Cdk8s vs Helm for Kubernetes

What is Cdk8s?

Cdk8s is an open-source software development framework for defining Kubernetes applications and reusable abstractions using familiar programming languages and rich object-oriented APIs. cdk8s apps synthesize into standard Kubernetes manifests which can be applied to any Kubernetes cluster.

Cdk8s definitions are written as software in one of the supported programming languages. Definitions of k8s objects are structured as a tree of constructs.

The root construct is the App which is consisted of Charts where every chart defines the group of Kubernetes objects. The app can have an arbitrary number of charts - whereas a chart can have an arbitrary number of constructs ~ Kubernetes objects bound to it.

Charts are synthesized in the YAML after cdk8s does its magic and you get YAML files per chart - ready for applying on the Kubernetes.

Cdk8s is originally written in the typescript but wrappers are available for the next languages:

  • Typescript
  • Python
  • Java
  • Go

Cdk8s does have a CLI tool for usage in the terminal. On the other hand, programmatical usage is supported but cdk8s needs to be installed on the environment.

This article will focus on the GO and It will be shown how to write K8s resources as code, what benefits that brings, and how to set up the environment.

Arguments for cdk8s

There are various tools already trying to solve the problem of defining dynamic YAML files using various approaches. You've probably heard of Helm, Kustomize, and others. They tried to provide the ability to template/update YAML files and define a set of objects for end usage. These tools are great in scoped usage but when scalability, maintainability, and testability are in place - challenges arise.

To overcome this set of problems, anti-patterns get employed and the abusage of the tools is surfacing. I wouldn't go deep into the analysis of the tools mentioned here but instead, I would point out the main advantage of the cdk8s:

The ability to write software that will handle the K8s objects in the language your team of developers is already familiar with and all aspects of good software design that can be used in the process. It enables developers to embrace DevOps culture more easily since the orchestration is brought closer to them.

How to install cdk8s?

I will use Ubuntu as my machine and a few requirements for the installation are:

  • Install Go version >=1.18
  • Install cdk8s using brew, npm, or yarn (I will use npm)

You can check out https://cdk8s.io/docs/latest/getting-started/ for instructions on how to get started with cdk8s.

How to start a cdk8 project?

Following the above link on getting-started shows how to start a project using cdk8s CLI in different languages. We will focus on the GO.

~ cdk8s init go-app
Initializing a project from the go-app template
Importing k8s v1.25.0...
Importing resources, this may take a few moments...
========================================================================================================

 Your cdk8s Go project is ready!

   cat help      Prints this message  
   cdk8s synth   Synthesize k8s manifests to dist/
   cdk8s import  Imports k8s API objects to "imports/k8s"

  Deploy:
   kubectl apply -f dist/

========================================================================================================

This will give you a basic directory structure and you are ready to create your k8s objects straightaway.

Directory structure

The created directory structure looks like this.

.
├── cdk8s.yaml
├── dist
│   └── cdk8s.k8s.yaml
├── go.mod
├── go.sum
├── help
├── imports
│   └── k8s
└── main.go

The created directory structure for the cdk8s project using GO.

Imports are pulled from Kubernetes directly and you can use them in the code. It's also possible to import CRDs to use CR, using cdk8s CLI.

~ cdk8s import --help
Examples:
  cdk8s import k8s                                  Imports Kubernetes API objects to imports/k8s.ts. Defaults to 1.25.0
  cdk8s import k8s --no-class-prefix                Imports Kubernetes API objects without the "Kube" prefix
  cdk8s import k8s@1.13.0                           Imports a specific version of the Kubernetes API
  cdk8s import jenkins.io_jenkins_crd.yaml          Imports constructs for the Jenkins custom resource definition from a file
  cdk8s import mattermost:=mattermost_crd.yaml      Imports constructs for the mattermost cluster custom resource definition using a custom module name
  cdk8s import github:crossplane/crossplane@0.14.0  Imports constructs for a GitHub repo using doc.crds.dev

Importing CRDs.

Cdk8s visualized

Below you can find the idea behind the cdk8s visualized.

The cdk8s architecture.

As you can see one app can contain multiple charts where every chart can hold multiple definitions of k8s objects. It is also possible to define dependencies between charts or k8s objects.

Running the program will synthesize the code in the YAML - every chart will be synthesized to a YAML file containing all k8s objects under that chart.

Creating cdk8s constructs

Let's create the first Deployment using GO and cdk8s. In the main.go file you need to add a new object.

package main

import (
	"example.com/cdk8sgo/imports/k8s"
	"fmt"
	"github.com/aws/constructs-go/constructs/v10"
	"github.com/aws/jsii-runtime-go"
	"github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2"
)

type MyChartProps struct {
	cdk8s.ChartProps
}

func NewMyChart(scope constructs.Construct, id string, props *MyChartProps) cdk8s.Chart {
	var cprops cdk8s.ChartProps
	if props != nil {
		cprops = props.ChartProps
	}
	chart := cdk8s.NewChart(scope, jsii.String("deployment"), &cprops)

	label := map[string]*string{"app": jsii.String("hello-k8s")}

	obj := &k8s.KubeDeploymentProps{
		Spec: &k8s.DeploymentSpec{
			Replicas: jsii.Number(2),
			Selector: &k8s.LabelSelector{
				MatchLabels: &label,
			},
			Template: &k8s.PodTemplateSpec{
				Metadata: &k8s.ObjectMeta{
					Labels: &label,
				},
				Spec: &k8s.PodSpec{
					Containers: &[]*k8s.Container{{
						Name:  jsii.String("GoDeployment"),
						Image: jsii.String("GoDeployment:1.0.0"),
						Ports: &[]*k8s.ContainerPort{{ContainerPort: jsii.Number(8080)}},
					}},
				},
			},
		},
	}

	k8s.NewKubeDeployment(chart, jsii.String("goDeployment"), obj)

	fmt.Println(*(*(obj.Spec.Template.Spec.Containers))[0].Image)

	return chart
}

func main() {
	app := cdk8s.NewApp(nil)
	NewMyChart(app, "cdk8sgo", nil)
	app.Synth()
}
cdk8sgo go run main.go
GoDeployment:1.0.0

After building and running, in the dist directory we will next YAML: deployment.k8s.yaml.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-c84676d6
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-k8s
  template:
    metadata:
      labels:
        app: hello-k8s
    spec:
      containers:
        - image: GoDeployment:1.0.0
          name: GoDeployment
          ports:
            - containerPort: 8080

It's pretty straightforward to define new resources and create new objects in the chart.

Templating k8s objects using cdk8s

As can be seen, it's pretty easy to template the objects using variables instead of literals. Variables can be filled using any type of external configuration file. I recommend using Viper in Go for configuration management and creating CLI flags for adding flags to the CLI program to choose the different configuration files.

Accessing k8s objects fields

Another great aspect is the possibility to access the k8s object fields directly in the code. If you refer to the code snippet above you will see that it's displaying the deployment container image name and tag.

fmt.Println(*(*(obj.Spec.Template.Spec.Containers))[0].Image)

We are directly accessing pods spec and we have full control over the information in it. This can be very valuable for testing purposes of your YAML k8s definitions, propagating information from one object to the others, etc.

Conclusion

Cdk8s is currently sandboxed project in the CNCF. It has a lot of benefits over standard tools but the learning curve is a little bit steeper for newcomers.

If you want to have full control over the creation of the YAML object definitions, add logic to it, implement clean reusability, and/or add any more complex procedure - cdk8s gives you the ability to do just that.