This blog post details how you can manage your Kubernetes infrastructure using familiar programming languages through cdk8s.
Cdk8s is based on the principles of the AWS CDK. CDK allows you to provision AWS infrastructure using familiar programming languages which are 'synthesized' into CloudFormation templates which can then be deployed to AWS. Cdk8s uses this model to allow you to define Kubernetes resources in familiar programming languages, then synthesize the resources you have defined into Kubernetes Yaml mainfests which can then be applied.
The point where cdk8s differs from AWS CDK is that cdk8s does not provide a mechanism for deploying/applying your K8s manifests as CDK does for CloudFormation templates. Instead cdk8s merely offers the ability to create the manifests, which can then be applied manually or via a CI/CD pipeline.
Prerequisites:
Node with a version of 16.20.0 or higher
cdk8s CLI - to install this, run npm install -g cdk8s-cli
Anyone who has spent time provisioning and managing infrastructure through configuration files defined in Yaml will know that this can be a complex and laborious process to work with. Having the ability to define resources in a programming language can save engineers time and prevent context shifting when moving between infrastructure and application code.
Once you have the prerequisites installed, you can bootstrap your cdk8s app by running cdk8s init typescript-app
. I am using Typescript in this example but cdk8s also supports Python, Java and Go.
The example of cdk8s core we will use can be found here. This example creates a simple hello-world deployment with load balancer service.
In the main.ts file, paste the following code:
import { Construct } from 'constructs';
import { App, Chart } from 'cdk8s';
import { KubeDeployment, KubeService, IntOrString } from './imports/k8s';
export class Core extends Chart {
constructor(scope: Construct, id: string, props: any) {
super(scope, id, props);
const label = { app: props.test };
new KubeService(this, 'service', {
spec: {
type: 'LoadBalancer',
ports: [ { port: 80, targetPort: IntOrString.fromNumber(8080) } ],
selector: label
}
});
new KubeDeployment(this, 'deployment', {
spec: {
replicas: 1,
selector: {
matchLabels: label
},
template: {
metadata: { labels: label },
spec: {
containers: [
{
name: 'hello-kubernetes',
image: 'paulbouwer/hello-kubernetes:1.7',
ports: [ { containerPort: 8080 } ]
}
]
}
}
}
});
}
}
const app = new App();
new Core(app, 'core', {test: 'hello'});
app.synth();
Our resources have been defined in the 'Core' class we have defined above. We then create a new cdk8s app and instantiate our 'Core' class within the scope of the app. We are able to pass in props to the constructor of our Core class as props when we instantiate the class, as seen in the test: 'hello'
prop above. Once we have this code, we can synthesize our code to a Kubernetes manifest by running cdk8s synth
in the root of the project.
Once this has run, you should be able to see your resources have been converted to a K8s manifest in the /dist directory. This file is now ready to be applied.
This demonstrates the most basic use cases of CDK8S, similar to using L1 constructs in AWS CDK. However, the real benefits of cdk8s come from using the cdk8s+ APIs, which abstract away a lot of boiler plate code and verbose configuration into higher powered Constructs which we can build upon, in the same way as using higher level Constructs in CDK.
The plus API also provides us with methods that you can use on resources, for example with container.mount()
and deployment.exposeViaService()
, which gives much greater flexibility in provisioning resources.
For example, to generate an Ubuntu Deployment with 3 replicas using cdk8s+, all we have to write is:
new kplus.Deployment(chart, 'Deployment', {
replicas: 3,
containers: [{
image: 'ubuntu',
}],
});
If we synthesize this single resource, we can see how much configuration the higher level API has abstracted away and automatically configured using the more powerful construct:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-chart-deployment-c8c354dd
spec:
minReadySeconds: 0
progressDeadlineSeconds: 600
replicas: 3
selector:
matchLabels:
cdk8s.io/metadata.addr: my-chart-Deployment-c881021c
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
cdk8s.io/metadata.addr: my-chart-Deployment-c881021c
spec:
automountServiceAccountToken: false
containers:
- image: ubuntu
imagePullPolicy: Always
name: main
resources:
limits:
cpu: 1500m
memory: 2048Mi
requests:
cpu: 1000m
memory: 512Mi
securityContext:
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: true
runAsNonRoot: true
dnsPolicy: ClusterFirst
hostNetwork: false
restartPolicy: Always
securityContext:
fsGroupChangePolicy: Always
runAsNonRoot: true
setHostnameAsFQDN: false
terminationGracePeriodSeconds: 30
The cdk8s+ API provides optional props to customize these default values, as well as escape hatches to use functionality not available within the higher level constructs.
Another strength of cdk8s is that it includes a Helm
construct which allows you to include Helm charts. This means that if you have existing K8s infrastructure defined using Helm, you don't end up with silos between cdk8s and Helm code.
In conclusion, this is a very brief example of some of the ways cdk8s can be powerful when managing Kubernetes infrastructure. It helps teams and organizations define reusable Constructs that can be extended to make the process of managing Kubernetes resources simpler and more manageable.
Be sure to take a look at the cdk8s docs for more detailed examples.