Write your Kubernetes Infrastructure as Go code - Extend cdk8s with custom Constructs
Tags: kubernetes, go, tutorial, programming
Published: 2022-08-11 04:16:22

*Build a Wordpress deployment as a cdk8s construct*

**Constructs** are the fundamental building block of [cdk8s](https://cdk8s.io/docs/latest/) (Cloud Development Kit for Kubernetes) - an open-source framework (part of CNCF) with which you can define your Kubernetes applications using regular programming languages (instead of `yaml`). In [Getting started with cdk8s](https://dev.to/abhirockzz/write-your-kubernetes-infrastructure-as-go-code-getting-started-with-cdk8s-37n), you saw how to use the core `cdk8s` library.


You can also use the `cdk8s-plus` library ([also covered this a previous blog](https://dev.to/abhirockzz/write-your-kubernetes-infrastructure-as-go-code-cdk8s-plus-in-action-58m2)) to reduce the amount of boilerplate code you need to write. With `cdk8s-plus`, creating a Kubernetes `Deployment`, specifying it's container (and other properties) and exposing it via a `Service` is three function calls away. 


For example, to setup and access `Nginx`, you simply need this:

```go
//...
deployment := cdk8splus22.NewDeployment(chart, jsii.String("deployment"), &cdk8splus22.DeploymentProps{Metadata: &cdk8s.ApiObjectMetadata{Name: jsii.String("nginx-deployment-cdk8s-plus")}})

deployment.AddContainer(&cdk8splus22.ContainerProps{
		Name:  jsii.String("nginx-container"),
		Image: jsii.String("nginx"),
		Port:  jsii.Number(80)})

deployment.ExposeViaService(&cdk8splus22.DeploymentExposeViaServiceOptions{
		Name:        jsii.String("nginx-container-service"),
		ServiceType: cdk8splus22.ServiceType_LOAD_BALANCER,
		Ports:       &[]*cdk8splus22.ServicePort{{Port: jsii.Number(9090), TargetPort: jsii.Number(80)}}})
//...
```

But things can get even better! 

Instead of writing the same logic over and over, you can package it in the form of a reusable component that can be invoked just like other built-in `cdk8s` functions (e.g. [NewDeployment](https://pkg.go.dev/github.com/cdk8s-team/cdk8s-plus-go/cdk8splus22/v2#NewDeployment), `NewService` etc.). Although it might not sound as useful for the simple application(s), this approach is invaluable for a large project, team or organisation who want to scale their engineering efforts. In fact, there is already a pool of ready-to-use components available at [constructs.dev](https://constructs.dev/). These include constructs contributed by the community, AWS and others as well, across multiple programming languages.

### To better understand how this might look in practice

... let's look at the code. I will continue to use `Wordpress` as an example, like I did in the previous blog post. Here is a code snippet that shows how everything is wired together (with implementation walk-through in the next section):

> You can refer to the [complete code on Github](https://github.com/abhirockzz/cdk8s-for-go-developers/tree/master/part4-custom-construct)

```go
//...

func NewMyChart(scope constructs.Construct, id string, props *MyChartProps) cdk8s.Chart {
	//....
	NewWordpressStack(chart, jsii.String("wordpress-stack"), &WordpressProps{//....)
	return chart
}

func main() {
	app := cdk8s.NewApp(nil)
	NewMyChart(app, "wordpress-custom-stack", nil)
	app.Synth()
}
```

- `NewWordpressStack` gives us a construct that represents an entire `Wordpress` installation (single line of code!)
- We simply configure it as per our requirements (with `WordpressProps`)
- Include this as part of a [cdk8s.Chart](https://pkg.go.dev/github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2#Chart) which is then included in the [cdk8s.App](https://pkg.go.dev/github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2#App) (as with any other `cdk8s` application)

There is lot of flexibility in terms of how you want to build a custom construct, depending on your requirements. But, at its very core, the basic concept is to define a way to create a new [construct.Construct](https://pkg.go.dev/github.com/aws/constructs-go/constructs/v10#Construct). You would want to provide a way to add metadata to further configure/refine your `Construct` - typically, thats done through properties ([cdk8s.ChartProps](https://pkg.go.dev/github.com/cdk8s-team/cdk8s-core-go/cdk8s/v2#ChartProps)).

First we define `WordpressProps` - this encapsulates/externalises the attributes of the Wordpress installation. Since this is just an example, I have provided limited attributes such as MySQL/Wordpress Docker images,  MySQL password, and required storage.

```go
type WordpressProps struct {
	MySQLImage    *string
	MySQLPassword *string
	MySQLStorage  *float64

	WordpressImage   *string
	WordpressStorage *float64
}
```

Then we have a function that will allow *other* charts/constructs to instantiate Wordpress. This is where the entire implementation resides.

```go
func NewWordpressStack(scope constructs.Construct, id *string, props *WordpressProps) constructs.Construct {
    ...
}
```

The `props *WordpressProps` parameter allows other constructs to influence the Wordpress stack creation e.g. you can define how much storage you need, maybe use a different Docker image for Wordpress/MySQL.  The actual code for this function is similar to the one you [saw here](https://github.com/abhirockzz/cdk8s-for-go-developers/blob/master/part2-cdk8s-plus-in-action/wordpress/main.go) (with required adjustments), so there is no point repeating it here. I will simply highlight the important bits - specifically the ones that use the `props` to configure the required components.

> This sample construct used `cdk8splus22` library. The reason for this naming convention is because  each `cdk8s-plus` library is separately vended to target a specific Kubernetes version - the `22` at the end signifies that this dependency will work with Kubernetes `1.22`. You can use the library corresponding to your Kubernetes version and [refer to the FAQs](https://cdk8s.io/docs/latest/plus/#whats-the-difference-between-cdk8s-plus-20-cdk8s-plus-21-and-cdk8s-plus-22) for more info.


We use the MySQL password from `props` and use that to create the `Secret`.

```go
    //...
	password := props.MySQLPassword
    mysqlSecret := cdk8splus22.NewSecret(wordpressConstruct, jsii.String("mysql-secret"),
		&cdk8splus22.SecretProps{
			Metadata: &cdk8s.ApiObjectMetadata{Name: jsii.String(secretName)}}) 
	secretKey := "password"
	mysqlSecret.AddStringData(jsii.String(secretKey), password)
	//...
```

The container images for MySQL and Wordpress are referenced via their respective `Deployment`s:

```go
//...
	containerImage := props.MySQLImage

	mysqlContainer := dep.AddContainer(&cdk8splus22.ContainerProps{
		Name:  jsii.String("mysql-container"),
		Image: containerImage,
		Port:  jsii.Number(3306),
	})
//...

	wordpressContainer := wordPressDeployment.AddContainer(&cdk8splus22.ContainerProps{
		Name:  jsii.String("wordpress-container"),
		Image: props.WordpressImage,
		Port:  jsii.Number(80),
	})
```

We also use the passed in storage as well - this is used to configure the `PersistentVolumeClaim` request.

```go
...
	mysqlPVC := cdk8splus22.NewPersistentVolumeClaim(wordpressConstruct, jsii.String("mysql-pvc"), &cdk8splus22.PersistentVolumeClaimProps{
		AccessModes: &[]cdk8splus22.PersistentVolumeAccessMode{cdk8splus22.PersistentVolumeAccessMode_READ_WRITE_ONCE},
		Storage:     cdk8s.Size_Gibibytes(props.MySQLStorage)})
...

	wordpressPVC := cdk8splus22.NewPersistentVolumeClaim(wordpressConstruct, jsii.String("wordpress-pvc"), &cdk8splus22.PersistentVolumeClaimProps{
		AccessModes: &[]cdk8splus22.PersistentVolumeAccessMode{cdk8splus22.PersistentVolumeAccessMode_READ_WRITE_ONCE},
		Storage:     cdk8s.Size_Gibibytes(props.WordpressStorage)})
```

Finally, we call `NewWordpressStack` from another `cdk8s.Chart` and pass in the attributes we want to configure.

```go
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(id), &cprops)

	NewWordpressStack(chart, jsii.String("wordpress-stack"), &WordpressProps{
		MySQLImage:       jsii.String("mysql"),
		MySQLPassword:    jsii.String("Password123"),
		MySQLStorage:     jsii.Number(3),
		WordpressImage:   jsii.String("wordpress:4.8-apache"),
		WordpressStorage: jsii.Number(2)})

	return chart
}
```

### Use this to install Wordpress 

**To test it locally...**

... you can use [minikube](https://minikube.sigs.k8s.io/docs/start/), [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation), etc.


```bash
# make sure cluster is running
minikube start

git clone https://github.com/abhirockzz/cdk8s-for-go-developers
cd part4-custom-construct
```

Create manifest and inspect all the resources (see `dist` directory):

```bash
cdk8s synth
```

To deploy them:

```
kubectl apply -f dist/

# output (might differ in your case)

secret/mysql-pass created
deployment.apps/mysql-mysql-deployment-cdk8splus-c83762d9 created
persistentvolumeclaim/mysql-mysql-pvc-c8799bba created
service/mysql-service created
deployment.apps/wordpress-wordpress-deployment-cdk8splus-c8252da7 created
service/wordpress-service created
persistentvolumeclaim/wordpress-wordpress-pvc-c8334a29 created
```

Check the Kubernetes `Service` (called `wordpress-service`) which exposes the wordpress `Deployment`.

```bash
kubectl get svc wordpress-service
```

If you're using `minikube`, in a different terminal run (if not already running):

```
minikube tunnel
```

Use your browser to navigate to http://localhost:80. You should see the familiar Wordpress installation screen.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2npiok9mpye1bjcuq1n.png)

Go ahead, finish the installation and log into your Wordpress instance. Feel free to experiment with it. 

## Conclusion

`cdk8s` is a powerful tool itself but it also provides you the ability to extend and build other abstraction on top of it. You saw how to write a custom construct in `Go` and used it deploy Wordpress on Kubernetes. This can be further used as a foundation for other re-usable components.

Happy coding!