Homelab 2 - Deno and cdk8s
cdk
and cdk8s
If you’ve used CloudFormation, then you know how much it sucks. You use a weird dialect of YAML to define your AWS resources. Back in 2017 AWS introduced the cdk library. It allows you to generate your CloudFormation YAML using a real language like Go, Python, Java, or TypeScript.
This idea turned out to be execellent, so they did the same thing for Kubernetes with cdk8s. cdk8s seems to be abandonded, but it still works quite well. Since the TypeScript defitions are generated from Kubernetes’ resources (including third-party custom resource definitions!),the library should continue to work for quite a while longer.
Here’s a “hello world” program from cdk8s’ documentation:
import { import Construct
Construct } from "constructs";
import { import App
App, import Chart
Chart } from "cdk8s";
import { import KubeDeployment
KubeDeployment } from "./imports/k8s";
class class MyChart
MyChart extends import Chart
Chart {
constructor(scope: Construct
scope: import Construct
Construct, ns: string
ns: string, appLabel: string
appLabel: string) {
super(scope: Construct
scope, ns: string
ns);
// Define a Kubernetes Deployment
new import KubeDeployment
KubeDeployment(this, "my-deployment", {
spec: {
replicas: number;
selector: {
matchLabels: {
app: string;
};
};
template: {
metadata: {
labels: {
app: string;
};
};
spec: {
containers: {
name: string;
image: string;
ports: {
containerPort: number;
}[];
}[];
};
};
}
spec: {
replicas: number
replicas: 3,
selector: {
matchLabels: {
app: string;
};
}
selector: { matchLabels: {
app: string;
}
matchLabels: { app: string
app: appLabel: string
appLabel } },
template: {
metadata: {
labels: {
app: string;
};
};
spec: {
containers: {
name: string;
image: string;
ports: {
containerPort: number;
}[];
}[];
};
}
template: {
metadata: {
labels: {
app: string;
};
}
metadata: { labels: {
app: string;
}
labels: { app: string
app: appLabel: string
appLabel } },
spec: {
containers: {
name: string;
image: string;
ports: {
containerPort: number;
}[];
}[];
}
spec: {
containers: {
name: string;
image: string;
ports: {
containerPort: number;
}[];
}[]
containers: [
{
name: string
name: "app-container",
image: string
image: "nginx:1.19.10",
ports: {
containerPort: number;
}[]
ports: [{ containerPort: number
containerPort: 80 }],
},
],
},
},
},
});
}
}
const const app: any
app = new import App
App();
new constructor MyChart(scope: Construct, ns: string, appLabel: string): MyChart
MyChart(const app: any
app, "getting-started", "my-app");
const app: any
app.synth();
The result of running this program is a Kubernetes YAML file that you can deploy using kubectl apply
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: getting-started-my-deployment-c85252a6
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- image: nginx:1.19.10
name: app-container
ports:
- containerPort: 80
Why is this useful? Static typing! cdk8s can inform guide you as you write your Kubernetes resources. For example, it can let you know what properties are valid when you’re creating a resource or let you know when you’ve specifiy an invalid property.
cdk8s has support for all of Kubernete’s resources. These definitions are generated by the cdk8s import
command, which generates types for every Kubernetes resource on your server including CRDs (custom resource definitions). Here’s an example of a generated definition for 1Password, which I use to handle all of the secrets in my Kubernetes cluster:
export class class OnePasswordItem
OnePasswordItem extends ApiObject {
public constructor(scope: Construct
scope: type Construct = /*unresolved*/ any
Construct, id: string
id: string, props: OnePasswordItemProps
props: OnePasswordItemProps = {}) {
super(scope: Construct
scope, id: string
id, {
...class OnePasswordItem
OnePasswordItem.GVK,
...props: OnePasswordItemProps
props,
});
}
}
export interface OnePasswordItemProps {
readonly OnePasswordItemProps.metadata?: any
metadata?: type ApiObjectMetadata = /*unresolved*/ any
ApiObjectMetadata;
readonly OnePasswordItemProps.spec?: OnePasswordItemSpec | undefined
spec?: OnePasswordItemSpec;
readonly OnePasswordItemProps.type?: string | undefined
type?: string;
}
export interface OnePasswordItemSpec {
readonly OnePasswordItemSpec.itemPath?: string | undefined
itemPath?: string;
}
Here’s how I use it to store my Tailscale key:
new OnePasswordItem(chart, "tailscale-operator-oauth-onepassword", {
spec: {
itemPath: string;
}
spec: {
itemPath: string
itemPath: "vaults/v64ocnykdqju4ui6j6pua56xw4/items/mboftvs4fyptyqvg3anrfjy6vu",
},
metadata: {
name: string;
namespace: string;
}
metadata: {
name: string
name: "operator-oauth",
namespace: string
namespace: "tailscale",
},
});
Takeaway: cdk8s supports all Kubernetes resources, including third-party resources from 1Password, Tailscale, Traefik, etc.
Deno
TODO: write about Deno