kubernetes

[cdk8s] Define k8s yaml file in programming language

misankim 2023. 9. 8. 22:08

cdk8s - A framework that allows defining/creating yaml files that define k8s resources in programming languages such as python, typescript, and java.(official site - https://cdk8s.io/)

-> In order to configure a yaml file for k8s resources, understanding the yaml file is necessary, and cdk8s provides an alternative to understanding yaml files by abstracting k8s resources.

 

Image source - cdk8s official docs(https://cdk8s.io/docs/latest/ )

 

# install cli

brew install cdk8s

 

# Create and initialize sample directory

mkdir hello
cd hello

 

initialize

cdk8s init python-app


generate and inspect the manifest

cdk8s synth


output

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

 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/

========================================================================================================
➜  [hello] ls -l
total 48
-rw-r--r--  1 user  staff   226B  9  6 16:38 Pipfile
-rw-r--r--  1 user  staff   4.5K  9  6 16:38 Pipfile.lock
-rw-r--r--  1 user  staff    65B  9  6 16:38 cdk8s.yaml
drwxr-xr-x  3 user  staff    96B  9  6 16:38 dist
-rw-r--r--  1 user  staff   435B  9  6 16:38 help
drwxr-xr-x  3 user  staff    96B  9  6 16:38 imports
-rwx------  1 user  staff   277B  9  6 16:38 main.py

➜  [hello] ls -l dist imports
dist:
total 0
-rw-r--r--  1 user  staff     0B  9  6 16:38 hello.k8s.yaml

imports:
total 0
drwxr-xr-x  5 user  staff   160B  9  6 16:38 k8s

 

(Note) The default main.py created during initialization

#!/usr/bin/env python
from constructs import Construct
from cdk8s import App, Chart


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

        # define resources here


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

app.synth()

 

 

# Write a sample app

vim main.py

#!/usr/bin/env python
from constructs import Construct
from cdk8s import App, Chart

from imports import k8s

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

        # define resources here
        label = {"app": "hello-k8s"}

        # notice that there is no assignment necessary when creating resources.
        # simply instantiating the resource is enough because it adds it to the construct tree via
        # the first argument, which is always the parent construct.
        # its a little confusing at first glance, but this is an inherent aspect of the constructs
        # programming model, and you will encounter it many times.
        # you can still perform an assignment of course, if you need to access
        # attributes of the resource in other parts of the code.

        k8s.KubeService(self, 'service',
                    spec=k8s.ServiceSpec(
                    type='LoadBalancer',
                    ports=[k8s.ServicePort(port=80, target_port=k8s.IntOrString.from_number(8080))],
                    selector=label))

        k8s.KubeDeployment(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-kubernetes',
                            image='paulbouwer/hello-kubernetes:1.7',
                            ports=[k8s.ContainerPort(container_port=8080)])]))))


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

app.synth()

 

 

# Create and deploy manifest

generate and inspect the manifest

cdk8s synth

 

 

deploy

kubectl apply -f dist/hello.k8s.yaml

 

 

(Note) Generated sample manifest

➜  [hello] cat dist/hello.k8s.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-service-c8c17160
spec:
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: hello-k8s
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment-c8c7fda7
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-k8s
  template:
    metadata:
      labels:
        app: hello-k8s
    spec:
      containers:
        - image: paulbouwer/hello-kubernetes:1.7
          name: hello-kubernetes
          ports:
            - containerPort: 8080

 

 

# Web service sample

vim webservice.py

from constructs import Construct, Node

from imports import k8s


class WebService(Construct):
    def __init__(self, scope: Construct, id: str, *,
                image: str,
                replicas: int = 1,
                port: int = 80,
                container_port: int = 8080):
        super().__init__(scope, id)

        label = {'app': Node.of(self).id} # only unique in current scope, for app-unique id use Node.of(self).addr

        k8s.KubeService(self, 'service',
                        spec=k8s.ServiceSpec(
                          type='LoadBalancer',
                          ports=[k8s.ServicePort(port=port, target_port=k8s.IntOrString.from_number(container_port))],
                          selector=label))

        k8s.KubeDeployment(self, 'deployment',
                          spec=k8s.DeploymentSpec(
                              replicas=replicas,
                              selector=k8s.LabelSelector(match_labels=label),
                              template=k8s.PodTemplateSpec(
                              metadata=k8s.ObjectMeta(labels=label),
                              spec=k8s.PodSpec(
                                containers=[
                                    k8s.Container(
                                        name='app',
                                        image=image,
                                        ports=[k8s.ContainerPort(container_port=container_port)])]))))

 

vim main.py

#!/usr/bin/env python
from constructs import Construct
from cdk8s import App, Chart

from webservice import WebService

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

        WebService(self, 'hello', image='paulbouwer/hello-kubernetes:1.7', replicas=2)
        WebService(self, 'ghost', image='ghost', container_port=2368)


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

app.synth()

 

 

(Note) Generated manifest

➜  [hello] cat dist/hello.k8s.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-service-c8685ad9
spec:
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: hello
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deployment-c8aab50d
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
        - image: paulbouwer/hello-kubernetes:1.7
          name: app
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: hello-ghost-service-c8a3d43b
spec:
  ports:
    - port: 80
      targetPort: 2368
  selector:
    app: ghost
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-ghost-deployment-c8940fdc
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ghost
  template:
    metadata:
      labels:
        app: ghost
    spec:
      containers:
        - image: ghost
          name: app
          ports:
            - containerPort: 2368

 

# personal thoughts

I think the biggest barrier when first getting into k8s is understanding and configuring the k8s manifest (yaml file). In that respect, I quite agree with the concept of cdk8s, which abstracts the work of configuring yaml files, and think it is a good attempt.

 

However, when Googling, there are not many references to the framework called cdk8s, and if you want to interpret various CRDs (Custom Resource Definitions) in addition to the basic API resources of k8s using the syntax of cdk8s, is it really possible to use them without any understanding of yaml files? I think so.

 

One of the strengths of k8s is that it has a wide community pool, and this community provides many open sources and tools that can be utilized. The yaml file has been used as a standard for a long time that k8s has existed, and many documents, samples, and references have been written based on the yaml file. Therefore, I think that interpreting this with cdk8s is unnecessary time consuming.

 

Rather, it occurred to me that I might need to understand something more complex to avoid the complexity of understanding and configuring yaml files. Currently, I have a personal idea that if I were to create a set of K8s resources in the form of a reproducible template, it would be better to use the Helm chart.