[cdk8s] Define k8s yaml file in programming language
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.
# 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.