💡

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

に公開

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

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

yispの基本

yispは以下のコマンドでインストールできます

go install github.com/totegamma/yisp@latest

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

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

これをyispで実行します。

yisp build helloworld.yaml

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

mymessage: hello world

ここでのポイントは以下の2つです

  • !yispタグを使って、arrayをyispコードであると示すことができる
  • !yispタグの外(mymessage)はそのまま出力されている

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

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

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

# 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

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

yisp build deployment.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にデプロイできます。

yisp build deployment.yaml | kubectl apply -f -

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

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

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

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

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

yisp build .

serviceを追加する

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

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

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

!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上ではドキュメントとして扱われます。

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

また、ディレクトリ構造を整理しましょう。

templates
  index.yaml
  deployment.yaml
  service.yaml
environments
  develop
    index.yaml

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

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

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

アプリケーションをdeploymentとしてデプロイするか、statefulsetとしてデプロイするかを選べるようにしましょう。
まずは、statefulset.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関数を使って実現できます。

!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を作成します。

environments
  develop
    index.yaml
    configmap.yaml

これをそのまま読み込むように、environments/develop/index.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として生成するためのファイルを配置するディレクトリを作成し、適当にテキストデータを配置します

environments
  develop
    index.yaml
    configmap.yaml
    cm-files
      file1.txt
      file2.txt
      ...

template/index.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を呼び出すコードを追加します。

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

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

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

Discussion