yispによるkubernetesマニフェスト管理


yispはほとんどyamlの構文を維持したまま記述できる、yaml用のテンプレートエンジンです。
lispをベースにしており扱う構文はシンプルながら強力な表現力を持っているという特徴があります。

yispはKubernetesのマニフェストの生成に特に便利です。
本記事では、yispを使ってKubernetesのマニフェストをテンプレート化し、管理する方法を紹介します。

## yispの基本

yispは以下のコマンドでインストールできます
```bash
go install github.com/totegamma/yisp@latest
```

yispはyaml中にコードを埋め込みます。たとえば、文字列を結合する`concat`関数を使って"hello"と"world"を結合する例は以下の通りです。

```yaml
mymessage: !yisp
  - concat
  - "hello "
  - "world"
```

これをyispで実行します。

```bash
yisp build helloworld.yaml
```

すると、以下のような出力が得られます。

```yaml
mymessage: hello world
```

ここでのポイントは以下の2つです
- !yispタグを使って、arrayをyispコードであると示すことができる
- !yispタグの外(mymessage)はそのまま出力されている

## deploymentをテンプレートで作成する

では早速Kubernetesマニフェストのテンプレートを作成してみましょう。
テンプレートとは、つまり関数のことです。特定の引数を受け取って、その引数に基づいてマニフェストを生成します。

yispでは関数はラムダ式として記述し、アンカーでそれに名前をつけます。

```yaml
# deployment.yaml
!yisp &main # アンカー記法&mainでこの値に名前を付ける
- lambda # lambda関数を使ってlambda式を生成する
- [props] # 引数のリストを宣言する
- !quote # !quoteタグ以下の値は変数展開以外そのまま出力される
  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: *props.name # アンカー記法*props.nameでpropsのnameを参照
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: *props.name
    template:
      metadata:
        labels:
          name: *props.name
      spec:
        containers:
          - name: *props.name
            image: *props.image
            ports:
              - containerPort: *props.port
---
!yisp
- *main # mainテンプレートを呼び出す
- !quote
  name: myapp
  image: myimage:latest
  port:  8080
```

これを評価すると、以下のマニフェストが出力されます。

```bash
yisp build deployment.yaml
```

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        name: myapp
    spec:
      containers:
        - name: myapp
          image: myimage:latest
          ports:
            - containerPort: 8080
```

これをこのままkubectlコマンドに渡すことで、Kubernetesにデプロイできます。

```bash
yisp build deployment.yaml | kubectl apply -f -
```

## 別ファイルからテンプレートを読み込む

テンプレートをより効率的に運用するために、別ファイルから呼びだすようにしましょう。index.yamlを新しく作成します。

```yaml
!yisp
- import
- [deployment, deployment.yaml] #名前付きimport

---
!yisp
- *deployment.main # importで名前つけたdeploymentモジュールのmainを呼び出す
- !quote
  name: myapp
  image: myimage:latest
  port:  8080
```

index.yamlはディレクトリ名を代表して読み込まれるので、次のコマンドでビルドできるようになります。

```bash
yisp build .
```

## serviceを追加する

次にDeploymentに対応するServiceを追加してみましょう。
このとき、Serviceのtypeにデフォルト値を設定して、指定がなかった場合は`LoadBalancer`を使うようにします。

default関数を使うことで、第二引数が`null`の場合に第三引数が返ります。

また、yispでは`.`を使った変数参照において、参照した変数が存在しなかった場合例外が発生します。変数名に`?`を付けることで、変数が存在しなかった場合に`null`を返すようになります。

```yaml
!yisp &main
- lambda
- [props]
- !quote
  apiVersion: v1
  kind: Service
  metadata:
    name: *props.name
  spec:
    selector:
      name: *props.name
    ports:
      - port: *props.port
        targetPort: *props.port
    type: !yisp [default, *props.lbtype?, LoadBalancer]
```

## 複数のテンプレートをまとめる

deploymentとserviceをまとめて、一つのテンプレート関数としてまとめましょう。

引数は同じpropsを使い、deploymentとserviceの両方に渡します。

また、ドキュメントをまとめるために`as-document-root`関数を使います。これにより、評価したドキュメントの配列が、出力するyaml上ではドキュメントとして扱われます。

```yaml
!yisp
- import
- [deployment, deployment.yaml]
- [service, service.yaml]
---
!yisp &app
- lambda
- [props]
- - as-document-root
  - - *deployment.main
    - *props
  - - *service.main
    - *props
```

また、ディレクトリ構造を整理しましょう。
```plain
templates
  index.yaml
  deployment.yaml
  service.yaml
environments
  develop
    index.yaml
```

これからは`environments/develop/index.yaml`で作業を進めます。
environments/develop/index.yamlは以下のようになります。

```yaml
!yisp
- import
- [template, ../../templates]
---
!yisp
- *template.app
- !quote
  name: myapp
  image: myapp:latest
  port: 8080
```

## statefulsetとdeploymentを選べるようにする

アプリケーションをdeploymentとしてデプロイするか、statefulsetとしてデプロイするかを選べるようにしましょう。
まずは、statefulset.yamlを作成します。

```yaml
!yisp &main
- lambda
- [props]
- !quote
  apiVersion: apps/v1
  kind: StatefulSet
  metadata:
    name: *props.name
  spec:
    replicas: !yisp [default, *props.replicas?, 1]
    selector:
      matchLabels:
        app: *props.name
    template:
      metadata:
        labels:
          name: *props.name
      spec:
        containers:
          - name: *props.name
            image: *props.image
            ports:
              - containerPort: *props.port
```

つぎに、template/index.yamlを修正して、パラメーターのtypeによってどちらのテンプレートを使うか分岐するようにします。
これは、`if`関数を使って実現できます。

```yaml
!yisp
- import
- [deployment, deployment.yaml]
- [service, service.yaml]
- [statefulset, statefulset.yaml]
---
!yisp &app
- lambda
- [props]
- - as-document-root
  - - if
    - [eq, *props.type?, "statefulset"]
    - - *statefulset.main
      - *props
    - - *deployment.main
      - *props
  - - *service.main
    - *props
```

## configmapなど個別リソースを追加する

使っている環境ごとに、テンプレートとは別に独自のリソースを追加したい場合があると思います。
その場合は、include関数を使って外部のyamlファイルをそのまま読み込むことができます。

例えば、developディレクトリに新しくconfigmap.yamlを作成します。


```plain
environments
  develop
    index.yaml
    configmap.yaml
```

これをそのまま読み込むように、environments/develop/index.yamlに追記しましょう。

```yaml
!yisp
- import
- [template, ../../templates]
---
!yisp
- *template.app
- !quote
  name: myapp
  image: myapp:latest
  port: 8080
---
# ここを追記
!yisp
- include
- configmap.yaml
```

## configmap-generator相当の読み込みを行う

直前では、configmap.yamlを自前で作成し、直接includeしていました。
次はkustomizeのconfigmap-generatorのように、ディレクトリ内のファイルを読み込んで自動的にConfigMapを生成してみましょう。

まずはconfigmapとして生成するためのファイルを配置するディレクトリを作成し、適当にテキストデータを配置します
```plain
environments
  develop
    index.yaml
    configmap.yaml
    cm-files
      file1.txt
      file2.txt
      ...
```

template/index.yamlに以下のようなコードを追加します。

```yaml
!yisp &configmap-generator
- lambda
- [props]
- !quote
  apiVersion: v1
  kind: ConfigMap
  metadata:
    name: *props.name
  data: !yisp
    - from-entries
    - - map
      - - lambda
        - [file]
        - !quote
          - *file.name
          - *file.body
      - - read-files
        - *props.path
```

environments/develop/index.yamlに以下のようにconfigmap-generatorを呼び出すコードを追加します。

```yaml
!yisp
- *template.configmap-generator
- name: myapp-config
  path: ./cm-files/*
```

これで以下のようなconfigmapをマニフェストに含めることができるようになりました。

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  hoge.conf: |
    piyopiyio
```

## 最後に
本記事では、yispを使ってKubernetesのマニフェストをテンプレート化し、管理する方法を紹介しました。
yispは非常に柔軟で強力なツールであり、Kubernetesのマニフェストを効率的に管理するための強力な手段となります。テンプレート化することで、環境ごとの差異を簡単に管理できるようになり、再利用性も向上します。

また、本記事ではkubernetesマニフェストのみに焦点を当てましたが、yispはprometheusの設定やansibleのplaybookなどの他のyamlファイルにも、そしてgrafanaのダッシュボードなどjsonファイルにも利用できます。

yispはオープンソースで開発されています。ぜひGitHubのレポジトリをチェックしてみてください。

https://github.com/totegamma/yisp