cdk8sとcdk8s+を使ってkubernetesマニフェストを生成する

# cdk8sとは
CDKでCloudFormationテンプレートファイルを生成するのと同様に、
cdk8sでKubernetesマニフェストファイルを生成する、というもの。

生成するのはあくまでyamlであって、
`kubectl apply`でファイルの内容をリソースに適用するので、
Kubernetesの宣言的な自律性はそのまま。

# cdk8s+とは
cdk8sに比べてより抽象度の高いライブラリを使える。
具体的にはcdk8sはLow Level API(Constructs)で、cdk8s+はHigh Level API(Constructs)

## Constructsとは
公式のドキュメントにはKubernetesの世界に例えた場合、
Helmのようなもの、という記載があった。
https://cdk8s.io/docs/latest/concepts/constructs/
ちなみにですがHelm Constructsもあるみたいです。
https://cdk8s.io/docs/latest/reference/cdk8s/typescript/#helm

また、それに加えてプログラマブルに記述できることのメリットとして、
例えば以下などのメリットがあるとのこと。

* 型があることによる抽象化やポリモーフィズムの導入
* メソッドやプロパティによる相互運用性

# cdk8sの実践
公式のGetting Startedに沿って実践していきます。
https://cdk8s.io/docs/latest/getting-started/

## CLIのインストール

```
$ npm install -g cdk8s-cli
```

確認

```
$ cdk8s --version
1.0.78
```

## プロジェクトの作成

```
$ cdk8s init typescript-app
```

一通りのKubernetesAPIがインポートされます。

## コードの作成
サービスとデプロイメントで構成されるリソースを作成します。
`Chart`が単一のKubernetesマニフェストとして生成されます。


```typescript:main.js
import { Construct } from 'constructs';
import { App, Chart, ChartProps } from 'cdk8s';

import { KubeDeployment, KubeService, IntOrString } from './imports/k8s';

export class MyChart extends Chart {
  constructor(scope: Construct, id: string, props: ChartProps = { }) {
    super(scope, id, props);

    const label = { app: 'hello-k8s' };

    new KubeService(this, 'service', {
      spec: {
        type: 'LoadBalancer',
        ports: [ { port: 80, targetPort: IntOrString.fromNumber(8080) } ],
        selector: label
      }
    });

    new KubeDeployment(this, 'deployment', {
      spec: {
        replicas: 2,
        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 MyChart(app, 'hello');
app.synth();
```

## マニフェストを生成する
以下コマンドでsynthします。

```
$ npm run compile && cdk8s synth
```

結果として以下のようなファイルが生成されます。

```yaml: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
```

## 実際にKubernetes環境に適用する

```
$ kubectl apply -f dist/hello.k8s.yaml 
service/hello-service-c8c17160 created
deployment.apps/hello-deployment-c8c7fda7 created
```

### deployment

```
$ kubectl get deployment
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
hello-deployment-c8c7fda7   2/2     2            2           2m
```

### pod

```
$ kubectl get pod
NAME                                         READY   STATUS    RESTARTS   AGE
hello-deployment-c8c7fda7-5474f75f59-plhjf   1/1     Running   0          2m8s
hello-deployment-c8c7fda7-5474f75f59-rzj2n   1/1     Running   0          2m8s
```

### service

```
$ kubectl get svc -o=jsonpath='{range .items[]}{.metadata.name}{"\t"}{.spec.type}{"\n"}{end}'
hello-service-c8c17160	LoadBalancer
```


# cdk8s+の実践
こちらも公式に沿ってやっていきます。
https://cdk8s.io/docs/latest/plus/

## コードの作成
replicasとimageの指定だけです。

```typescript:main.ts
import * as kplus from 'cdk8s-plus-22';
import * as cdk8s from 'cdk8s';

const app = new cdk8s.App();
const chart = new cdk8s.Chart(app, 'Chart');

new kplus.Deployment(chart, 'Deployment', {
  replicas: 3,
  containers: [{
    image: 'paulbouwer/hello-kubernetes:1.7',
  }],
});

app.synth();
```

## マニフェストを生成する
こちらも以下コマンドで生成します。

```
$ npm run compile && cdk8s synth
```

以下が生成されました。
こっちはservice作ってなかったです。

```yaml:dist/chart-c86185a7.k8s.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: chart-deployment-c8a3b439
spec:
  replicas: 3
  selector:
    matchLabels:
      cdk8s.deployment: Chart-Deployment-c822d80c
  template:
    metadata:
      labels:
        cdk8s.deployment: Chart-Deployment-c822d80c
    spec:
      containers:
        - env: []
          image: paulbouwer/hello-kubernetes:1.7
          imagePullPolicy: Always
          name: main
          ports: []
          volumeMounts: []
      volumes: []
```

## 実際にKubernetes環境に適用する
こちらもやってみます。

```
$ kubectl apply -f dist/chart-c86185a7.k8s.yaml
deployment.apps/chart-deployment-c8a3b439 created
```

### deployment

```
$ kubectl get deployment
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
chart-deployment-c8a3b439   3/3     3            3           2m4s
```

### pod

```
$ kubectl get pods
NAME                                         READY   STATUS    RESTARTS   AGE
chart-deployment-c8a3b439-5f646b8489-48svk   1/1     Running   0          2m
chart-deployment-c8a3b439-5f646b8489-d6pdz   1/1     Running   0          119s
chart-deployment-c8a3b439-5f646b8489-z2gfn   1/1     Running   0          119s
```

# 最後に
cdk8s/cdk8s+でマニフェスト生成する流れをやってみました。

マニフェストをプログラミング言語で記述できることで、
処理を共通化できたり、IDEのエコシステムに乗っかることができるなどメリットがあるかな、
といったところでしょうか。

AWSではCloudFormationを直接書くよりもCDKのほうが主流なのかと思いますが、
これと同じようなことがcdk8s/cdk8s+でも今後、起こっていくのか、
期待したいところですね。