DEV Community

Cover image for Write your Kubernetes Infrastructure as Go code - Extend cdk8s with custom Constructs
Abhishek Gupta
Abhishek Gupta

Posted on • Originally published at abhishek1987.Medium

Write your Kubernetes Infrastructure as Go code - Extend cdk8s with custom Constructs

Build a Wordpress deployment as a cdk8s construct

Constructs are the fundamental building block of cdk8s (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, you saw how to use the core cdk8s library.

You can also use the cdk8s-plus library (also covered this a previous blog) 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:

//...
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)}}})
//...
Enter fullscreen mode Exit fullscreen mode

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, 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. 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

//...

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()
}
Enter fullscreen mode Exit fullscreen mode
  • 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 which is then included in the cdk8s.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. You would want to provide a way to add metadata to further configure/refine your Construct - typically, thats done through properties (cdk8s.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.

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

    WordpressImage   *string
    WordpressStorage *float64
}
Enter fullscreen mode Exit fullscreen mode

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

func NewWordpressStack(scope constructs.Construct, id *string, props *WordpressProps) constructs.Construct {
    ...
}
Enter fullscreen mode Exit fullscreen mode

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 (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 for more info.

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

    //...
    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)
    //...
Enter fullscreen mode Exit fullscreen mode

The container images for MySQL and Wordpress are referenced via their respective Deployments:

//...
    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),
    })
Enter fullscreen mode Exit fullscreen mode

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

...
    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)})
Enter fullscreen mode Exit fullscreen mode

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

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
}
Enter fullscreen mode Exit fullscreen mode

Use this to install Wordpress

To test it locally...

... you can use minikube, kind, etc.

# make sure cluster is running
minikube start

git clone https://github.com/abhirockzz/cdk8s-for-go-developers
cd part4-custom-construct
Enter fullscreen mode Exit fullscreen mode

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

cdk8s synth
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

kubectl get svc wordpress-service
Enter fullscreen mode Exit fullscreen mode

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

minikube tunnel
Enter fullscreen mode Exit fullscreen mode

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

Image description

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!

Top comments (0)