Deploying Kubernetes Resources with CDK8s

Jimmy Ray
ITNEXT
Published in
5 min readJul 13, 2021

--

In previous posts, I discussed:

In this post I will show how to use the Cloud Development Kit for Kubernetes (CDK8s) to generate Kubernetes resources (YAML) using Java.

CDK8s

According to the project documentation:

CDK8s is a software development framework for defining Kubernetes applications and reusable abstractions using familiar programming languages and rich object-oriented APIs. CDK8s generates pure Kubernetes YAML — you can use CDK8s to define applications for any Kubernetes cluster running anywhere.

CDK8s is similar to AWS CDK. It is uses constructs that represent Kubernetes resources and properties thereof.

CDK8s apps are programs written in one of the supported programming languages. They are structured as a tree of constructs.

At the time of this writing, CDK8s supports TypeScript, Python, and Java. As before with the AWS CDK, I am using Java with CDK8s.

Polyglotism with jsii

Like the AWS CDK, CDK8s is built on top of TypeScript and the jsii. With jsii developers create type-annotated bundles from typescript modules which can be used to auto-generate idiomatic packages in a variety of target languages. Generated types proxy calls to an embedded javascript VM, effectively allowing jsii modules to be “written once and run everywhere”.

Installation

To use CDK8s, I installed it with the following command:

$ npm install -g cdk8s-cli

That same command can be used to upgrade the CDK8s CLI.

Once installed, I created a new CDK8s project in an empty directory with the following CDK8s CLI command:

$ cdk8s init java-app

The following output indicates that the project was initialized for Java:

cdk8s --version
1.0.0-beta.26
Initializing a project from the java-app template
====================================================================
Your cdk8s Java project is ready!cat help Prints this message
cdk8s synth Synthesize k8s manifests to dist/
cdk8s import Imports k8s API objects to "src/main/java/imports/"
mvn compile Compiles your java packages
Deploy:
kubectl apply -f dist/*.k8s.yaml
====================================================================

CDK8s Code

My CDK8s Java code is a single Java class that extends the org.cdk8s.Chart class.

package io.jimmyray.app;import imports.k8s.*;
import software.constructs.Construct;
import org.cdk8s.App;
import org.cdk8s.Chart;
import org.cdk8s.ChartProps;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main extends Chart {public Main(final Construct scope, final String id) {
this(scope, id, null);
}
public Main(final Construct scope, final String id, final ChartProps options) {
super(scope, id, options);
Map<String,String> labelsMap = Map.of("app","cdk8s-read-only","env","dev", "owner", "jimmy");ObjectMeta nsMeta = ObjectMeta.builder()
.name("cdk8s-read-only")
.labels(labelsMap)
.build();
KubeNamespace.Builder.create(this,"cdk8s-ns")
.metadata(nsMeta)
.build();
// LoadBalancer Service
final String serviceType = "LoadBalancer";
final Map<String, String> selector = new HashMap<>();
selector.put("app", "cdk8s-read-only");
final List<ServicePort> servicePorts = new ArrayList<>();
final ServicePort servicePort = new ServicePort.Builder()
.port(80)
.targetPort(IntOrString.fromNumber(8080))
.build();
servicePorts.add(servicePort);
final ServiceSpec serviceSpec = new ServiceSpec.Builder()
.type(serviceType)
.selector(selector)
.ports(servicePorts)
.build();
final KubeServiceProps serviceProps = new KubeServiceProps.Builder()
.metadata(
ObjectMeta.builder().name("cdk8s-read-only")
.namespace("cdk8s-read-only")
.labels(labelsMap)
.build()
)
.spec(serviceSpec)
.build();
new KubeService(this, "service", serviceProps);// Deployment
final LabelSelector labelSelector = new LabelSelector.Builder().matchLabels(selector).build();
final ObjectMeta templateMeta = new ObjectMeta.Builder()
.labels(labelsMap).build();
final ObjectMeta deployMeta = new ObjectMeta.Builder().namespace("cdk8s-read-only")
.labels(labelsMap).name("cdk8s-read-only").build();
final List<ContainerPort> containerPorts = new ArrayList<>();
final ContainerPort containerPort = new ContainerPort.Builder()
.containerPort(8080)
.build();
containerPorts.add(containerPort);
final List<Container> containers = new ArrayList<>();
final Container container = new Container.Builder()
.name("cdk8s-read-only")
.image("public.ecr.aws/r2l1x4g2/go-http-server:v0.1.0-23ffe0a715")
.ports(containerPorts)
.build();
containers.add(container);
final PodSpec podSpec = new PodSpec.Builder()
.containers(containers)
.build();
final PodTemplateSpec podTemplateSpec = new PodTemplateSpec.Builder()
.metadata(templateMeta)
.spec(podSpec)
.build();
final DeploymentSpec deploymentSpec = new DeploymentSpec.Builder()
.replicas(1)
.selector(labelSelector)
.template(podTemplateSpec)
.revisionHistoryLimit(3)
.build();
final KubeDeploymentProps deploymentProps = new KubeDeploymentProps.Builder()
.metadata(deployMeta)
.spec(deploymentSpec)
.build();
new KubeDeployment(this, "deployment", deploymentProps);
}
public static void main(String[] args) {
final App app = new App();
new Main(app, "tmp");
app.synth();
}
}

In the previous code I created three Kubernetes resources (Namespace, Service, Deployment) using constructs from the CDK8s API.

Namespace

The namespace was the simplest. First, I created a map object for the common labels I wanted to include on the all resources. I then created a metadata object (ObjectMeta) to house the labels map. Finally, I used the fluent interface of the KubeNamespace.Builder to build the Kubernetes namespace via composition, similar to the AWS CDK.

Service

The service resource, type LoadBalancer, was a little more involved, but I also used the prescribed approach of composition to build-out an object graph that represents the service specification and associated properties. In the process, I created the ServiceSpec that was included in the KubeServiceProps, that gets added to the KubeService top-level object.

It’s important to point out that I specified a reusable map object (selector) that is used to define how the service selects pods. This map is reused in the deployment spec, up next.

Deployment

The deployment resource build process is very similar to the service, albeit more involved. I first built out the parts of the deployment, such as the ObjectMeta, Container, PodSpec, PodTemplateSpec, DeploymentSpec, and KubeDeploymentProps objects, using the fluent interfaces and composition. I used these to compose the KubeDeployment object.

Generating YAML

Once the code was written, I generated the YAML file by using the CDK8s CLI synth command. In order for the cdk8s synth command to work, the generated Java classes must exist in the target folder tree. I opted to run the Maven command to clean and compile the Java classes and then run the CDK8s CLI command:

mvn clean compile && cdk8s synth

This approach also ensures that I am always working with the latest version of my Java source code.

The resultant YAML file was generated at the following path:

dist/tmp.k8s.yaml

The following resources were defined in the YAML file:

apiVersion: v1
kind: Namespace
metadata:
labels:
app: cdk8s-read-only
env: dev
owner: jimmy
name: cdk8s-read-only
---
apiVersion: v1
kind: Service
metadata:
labels:
app: cdk8s-read-only
env: dev
owner: jimmy
name: cdk8s-read-only
namespace: cdk8s-read-only
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: cdk8s-read-only
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: cdk8s-read-only
env: dev
owner: jimmy
name: cdk8s-read-only
namespace: cdk8s-read-only
spec:
replicas: 1
revisionHistoryLimit: 3
selector:
matchLabels:
app: cdk8s-read-only
template:
metadata:
labels:
app: cdk8s-read-only
env: dev
owner: jimmy
spec:
containers:
- image: public.ecr.aws/r2l1x4g2/go-http-server:v0.1.0-23ffe0a715
name: cdk8s-read-only
ports:
- containerPort: 8080

Applying to Kubernetes

With the previous YAML now generated, I can use the following kubectl command to apply the cluster changes defined in the YAML:

kubectl apply -f dist/tmp.k8s.yaml

The result is a namespace, deployment, and service in the target Kubernetes cluster.

Summary

Like the AWS CDK, CDK8s enables developers to use programming languages to generate declarative configurations. In the case of CDK8s, YAML files, containing resource manifests, are created to be applied to Kubernetes in a separate call to kubectl.

Supporting GitHub Repository: https://github.com/jimmyraywv/cdk8s-java

--

--

Cloud Native, PaC, and Cybersecurity SME - Opinionated Writer - My opinions are just that, mine; I own them.