Extend cdk8s with custom Constructs
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 (as covered in an earlier chapter) 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)}}})
//...
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 chapter. 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()
}
NewWordpressStack
gives us a construct that represents an entireWordpress
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
}
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 {
...
}
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 eachcdk8s-plus
library is separately vended to target a specific Kubernetes version - the22
at the end signifies that this dependency will work with Kubernetes1.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)
//...
The container images for MySQL and Wordpress are referenced via their respective Deployment
s:
//...
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.
...
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.
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, kind, etc.
# 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):
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
.
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.
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.