cdk8s is a new framework released by AWS Labs written in TypeScript that allows us to define resource lists for Kubernetes using a number of object-oriented programming languages. cdk8s also ends up generating native Kubernetes YAML files, so we can use cdk8s to define running Kubernetes application resources from anywhere.

Introduction

The concept of constructs is provided in cdk8s, and they are abstractions of Kubernetes resource objects (Deployment, Service, Ingress, etc.). A defined Kubernetes application is a structure tree, with an App structure at the root of the tree. Within the application, we can define any number of charts (charts, similar to the Helm Chart template), each of which is merged into a separate Kubernetes resource manifest file, and the charts are composed of any number of constructs in turn, culminating in a Kubernetes resource objects.

Note, however, that cdk8s only defines the Kubernetes application and does not install the application into the cluster. When you execute an application with cdk8s, it merges all the charts defined in the application into the dist directory, and then we can use kubectl apply -f dist/chart.k8syaml or other GitOps tools to install these charts into the Kubernetes cluster.

Use

Currently cdk8s supports the use of both TypeScript and Python programming languages to define Kubernetes applications. Here we take the more familiar Python as an example to illustrate the basic use of cdk8s.

cdk8s has a relatively lightweight CLI tool that contains a number of useful commands. We can start by installing the cdk8s CLI globally, which can be done in two ways.

  • For Mac systems, the installation can be done directly using the Homebrew tool.
1
$ brew install cdk8s
  • Alternatively, you can use the npm tool to install (which relies on Node.js).
1
$ npm install -g cdk8s-cli

Once the installation is complete we can create a cdk8s application using the cdk8s command.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ mkdir hello && cd hello
# cdk8s init TYPE - TYPE 表示项目类型:python-app 或者 typescript-app
$ cdk8s init python-app   
Initializing a project from the python-app template
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (a65489)!
Installing dependencies from Pipfile.lock (a65489)  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
To activate this project's virtualenv, run the following:
 $ pipenv shell
Installing cdk8s~=0.19.0…
Requirement already satisfied: cdk8s~=0.19.0 in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (0.19.0)
Requirement already satisfied: constructs<3.0.0,>=2.0.1 in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (from cdk8s~=0.19.0) (2.0.1)
Requirement already satisfied: publication>=0.0.3 in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (from cdk8s~=0.19.0) (0.0.3)
Requirement already satisfied: jsii~=1.1.0 in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (from cdk8s~=0.19.0) (1.1.1)
Requirement already satisfied: cattrs>=1.0.0 in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (from jsii~=1.1.0->cdk8s~=0.19.0) (1.0.0)
Requirement already satisfied: typing-extensions>=3.6.4 in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (from jsii~=1.1.0->cdk8s~=0.19.0) (3.7.4.2)
Requirement already satisfied: python-dateutil in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (from jsii~=1.1.0->cdk8s~=0.19.0) (2.8.1)
Requirement already satisfied: attrs>=18.2 in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (from jsii~=1.1.0->cdk8s~=0.19.0) (19.3.0)
Requirement already satisfied: six>=1.5 in /Users/ych/.virtualenvs/hello-ndJXVB8W/lib/python3.7/site-packages (from python-dateutil->jsii~=1.1.0->cdk8s~=0.19.0) (1.14.0)

Adding cdk8s~=0.19.0 to Pipfile's [packages]Pipfile.lock (a65489) out of date, updating to (eea701)Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (eea701)!
Installing dependencies from Pipfile.lock (eea701)  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 9/9 — 00:00:02
To activate this project's virtualenv, run the following:
 $ pipenv shell
========================================================================================================

 Your cdk8s Python project is ready!

   cat help      Prints this message
   cdk8s synth   Synthesize k8s manifests to dist/
   cdk8s import  Imports k8s API objects to "imports/k8s"

  Deploy:
   kubectl apply -f dist/*.k8s.yaml

========================================================================================================

In the initialization process, we found that we need a higher Node version, I’m using v13.5.0 here, and we also need a global proxy because we need to get some K8S API objects during the initialization process.

After initialization, we can look at the basic structure of the project.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ tree .
.
├── Pipfile
├── Pipfile.lock
├── cdk8s.yaml
├── dist
│   └── hello.k8s.yaml
├── help
├── imports
│   └── k8s
│       ├── __init__.py
│       ├── _jsii
│       │   ├── __init__.py
│       │   └── k8s@0.0.0.jsii.tgz
│       └── py.typed
└── main.py

4 directories, 10 files

Now we can open the main.py file, which looks like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/usr/bin/env python
from constructs import Construct
from cdk8s import App, Chart


class MyChart(Chart):
    def __init__(self, scope: Construct, ns: str):
        super().__init__(scope, ns)

        # define resources here


app = App()
MyChart(app, "hello")

app.synth()

The above code shows that the application is composed in the form of a structure tree, where a structure is an abstract composable unit. The project we initialized above using the cdk8s init command defines a single empty chart.

When we run cdk8s synth, a Kubernetes resource list will be composed for each Chart in the application and written to the dist directory. We can test this with the command shown below.

1
2
3
4
$ cdk8s synth
dist/hello.k8s.yaml
$ cat dist/hello.k8s.yaml
$ # 空内容

Next we define some Kubernetes API objects in the diagram. Similar to charts and apps, Kubernetes API objects are represented as structures in cdk8s. These structures can be imported into the project using the cdk8s import command, and then we can find them in the project directory under the imports/k8s module.

When we initialize the project with cdk8s, we have actually performed the cdk8s import operation, so we can see some information under the imports directory, which we can commit to source to manage, or we can go and generate it during the build process.

Let’s define a simple Kubernetes application with Deployment and Service resource objects by modifying the main.py code to look like this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/env python
from constructs import Construct
from cdk8s import App, Chart

from imports import k8s  # 导入由 cdk8s import 命令生成的特定 k8s 版本的资源对象


class MyChart(Chart):
    def __init__(self, 
            scope: Construct,  # 我们的app实例
            ns: str):  # 需要注意的是这里并不是 k8s 的 namespace,只是我们资源的一个前缀而已
        super().__init__(scope, ns)

        # 定义一些变量
        label = {"app": "hello-k8s"}
        
        # 定义带有一个容器两个副本的Deployment资源对象
        k8s.Deployment(self, 'deployment',
                        spec=k8s.DeploymentSpec(
                            replicas=2,
                            selector=k8s.LabelSelector(match_labels=label),
                            template=k8s.PodTemplateSpec(
                                metadata=k8s.ObjectMeta(labels=label),
                                spec=k8s.PodSpec(containers=[
                                    k8s.Container(
                                        name='hello-k8s',
                                        image='paulbouwer/hello-kubernetes:1.7',
                                        ports=[k8s.ContainerPort(container_port=8080)]
                                    )
                                ])
                            )
                        ))
        # 定义一个关联上述 Pod 的 Service 对象
        k8s.Service(self, 'service', 
                    spec=k8s.ServiceSpec(
                        type='NodePort',
                        ports=[k8s.ServicePort(port=80, target_port=k8s.IntOrString.from_number(8080))],
                        selector=label
                        )
                    )


app = App()  # 创建一个 App 实例
MyChart(app, "hello") 

app.synth()  # 该方法负责生成生成k8s资源清单文件

Now we execute the cdk8s synth command, which will generate a list of resources in the dist directory as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ cdk8s synth
dist/hello.k8s.yaml
$ cat dist/hello.k8s.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment-c51e9e6b
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-k8s
  template:
    metadata:
      labels:
        app: hello-k8s
    spec:
      containers:
        - image: paulbouwer/hello-kubernetes:1.7
          name: hello-k8s
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: hello-service-9878228b
spec:
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: hello-k8s
  type: NodePort

Once the resource list has been generated, we can use the kubectl command to install the application directly:

1
2
3
$ kubectl apply -f dist/hello.k8s.yaml
deployment.apps/hello-deployment-c51e9e6b created
service/hello-service-9878228b created

At this point we are done defining a simple Kubernetes application using cdk8s.

Comparison

Some readers may feel that cdk8s does not compare well with tools like Helm or kustomize, and that these tools do not require us to write the actual code, but rather use a template language, which is true for current use. After all, it is more engineering to describe the application in a programming language, and there is more logic to implement. And I think the combination of cdk8s and DevOps has the advantage of allowing developers to take over Ops tasks, and as the infrastructure becomes more fluid and complex, the need for a more engineered deployment process for applications is clearly growing. However, the cdk8s project is still in its early days and is not recommended for production environments, but this idea of using a programming language to describe the application is still very interesting.

References