yispで関数型のパワーをkubernetesマニフェストに注入しよう
yaml、書いてますか?
AnsibleのPlaybookやDocker Compose、そして昨今は特に Kubernetes Manifest の記述でYAMLを使う機会はグッと増えていると思います。
とくに業務利用の現場では、Kubernetesマニフェストを環境(dev/staging/prod)ごとに少しずつ変えたいというケースが多く、バリエーションをどう管理するかが悩みのタネです。
その課題を解決するために、HelmやKustomizeなどのツールが登場し、広く使われるようになりました。
しかし、これらのツールにも限界があります。
Helmテンプレートを作成し、いざ適用しようと思ったらそもそもyamlとして無効なものが出力されていた。Kustomizeで少し凝ったパッチをしようと思ったら、パッチの評価順でパズルを解かされる苦労に突入した......。そんな経験ないでしょうか。
この記事ではYAMLにLisp、つまり関数型のチカラを注入してそうした苦労から解放されるツール、yispのコンセプトを紹介します。
既存ツールの限界
kubernetesのマニフェストの生成やバリエーションの管理では、現在helmとkustomizeが主によく使われていると思います。それぞれのツールの特徴と課題点を簡単にまとめてみます。
Helm
Helm は Kubernetes 向けに最も使われているテンプレートツールの1つです。
Go の text/template を使って、パラメーターファイルvaluesからYAMLを動的に生成します。
得意なこと
- 大規模なマニフェストを1から生成できる
- テンプレートの一部の機能を丸ごとON/OFFするなど、変更差分が大きくても問題なく記述できる
苦手なこと
- yamlをyamlとしてではなくただのプレーン文字列として扱う為、yamlの構造を簡単に破壊してしまう
- 組み込み関数に限界がある
Kustomize
kustomizeは主にベースとなるマニフェストに対してパッチを適用することで、環境ごとの差分を管理するツールです。
resource/generator/transformerの3つの概念を使って、宣言的にマニフェストを編集することができます。
得意なこと
- 小さな変更差分を明瞭に管理できる
- 宣言的なので、使いまわしてもマニフェストが壊れにくい
- yamlの構造を維持したまま、マニフェストを編集できる
苦手なこと
- 大規模なマニフェストを1から生成するのは難しい
- パッチの評価順が複雑なため、少しトリッキーなパッチを適用しようとした途端パズルが始まってしまう
- kubernetesだけを対象にしたツールなので、学習コストが高いわりに応用が利かない
ソリューションは?
つまり、私たちが求めているものは:
- 変数展開や構造のON/OFFができて
- 評価順が明確で
- そしてなにより、YAMLの構造を維持したままロジックを注入できる。
そんな、コードとデータを一体化できる仕組みです。そんな仕組み、存在するのでしょうか?
そう、それはLISPです!
LISPは1960年に開発されたプログラミング言語で、昨今だと「Lispエイリアン」などのミーム文脈で聞いたことがあるかと思います。
古臭いと侮るなかれ!LISPには「データとコードが同等」という性質があります。これこそ私たちが求めていたものです。
たとえば次のようなLISPのコードを見てください。
'(1 2 3) ; リスト。javascript風に書くと[1, 2, 3]
(add 1 2 3) ; 関数呼び出し。評価すると6になる。javascript風に書くとadd(1, 2, 3)
この「コードをデータとして扱う」というアイディアこそ今私たちが注目したいポイントです。
LISPのパワーをYAMLに注入しよう
こうして誕生したのがyispというツールです。
たとえば、あるキーの値に足し算の結果を入れたいとき、yispを使うと次のように書けます。
# input
myaddition: !yisp
- +
- 3
- 5
yispはyamlにもともとある「タグ」のsyntaxを使った!yisp
タグを使って、そこから以下がyisp式であることを表現できます。
これを評価すると、次のようになります。
# result
myaddition: 8
この例から2つのことが確認できますね。
- yisp範囲外の構造はそのまま出力されていること
- yispでの式が評価されて埋め込まれていること
もっと実用的な例を見ましょう。たとえば簡単にpodのマニフェストを生成したいとき、次のようなテンプレートを定義できます。
!yisp &mkpod #関数にmkpodという名前をつける
- lambda # 関数を定義!
- [name, image] # テンプレートの引数
- !quote # 以下は地の文であることを明示
apiVersion: v1
kind: Pod
metadata:
name: *name # 引数のnameをここに代入する
spec:
containers:
- name: *name
image: *image
少し見た目が複雑になりましたが怯える必要はありません!
まず、&と*はyamlのアンカー記法です。&でそのオブジェクトに名前をつけ、*でそのオブジェクトを呼び出します。yispの世界では、&はvarやlet、*は変数名と思っても問題ないでしょう。
テンプレートとはすなわち、関数のことです。あるパラメーターを渡すとマニフェスト全体が返ってくるというのは関数呼び出しととらえることができますね。
この関数はつぎのように呼び出せます
!yisp
- *mkpod
- mypod1
- myimage1
すると、つぎのようなマニフェストが得られます。
apiVersion: v1
kind: Pod
metadata:
name: mypod1
spec:
containers:
- name: mypod1
image: myimage1
これだけに留まらず、たとえば別のディレクトリにあるテキストファイルをすべてconfigmapとして読み込むことさえも出来ちゃいます。
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
data: !yisp
- from-entries
- - map
- - lambda
- [file]
- !quote
- *file.name
- *file.body
- - read-files
- ./cm-files/*
kustomizeのconfigMapGeneratorのような機能がyispで簡単に書けちゃいますね。
より複雑な処理を書きたいときは?
yispは非常に強力な表現を持っていますが、たしかにこれでkustomizeと100%互換があるように書くのはやりすぎでしょう。
技術的に可能であることと、それを運用できることは違います。なので、より複雑な処理は別のgoコードとして公開して、yispからそれを呼び出すのがオススメです。
つまりところ、yispを「柔軟な接着剤」として利用します。
たとえば既存のhelm chartを一旦展開してyispに読み込みたい場合はこう書けます。
!yisp &helm-chart
- lambda
- [!helm-chart-props props]
- - go-run
- pkg: github.com/totegamma/yisp-helm-adapter@v0.1.0
args:
- *props.repo
- *props.release
- *props.version
stdin:
- to-yaml
- *props.values
---
!yisp
- *helm-chart
- repo: "https://charts.concrnt.net/"
release: "concrnt"
version: "0.7.13"
values:
meilisearch:
enabled: true
まとめ
このように、yispはデータとコードを同じyaml上で表現するという発想から、yaml上だけで高度な処理を実行することも、既存のツールとスムーズに連携することも可能にしてくれます。
また、今後kubernetes以外の構成管理ツールが登場する場合でも、それがyamlをサポートしている場合、そのままyispの強力な表現力をあなたのツールにもたらすことができます。
terraformのように独自のDSLをわざわざ開発する必要はありません。
yispは現在も開発中のOSSです。
興味を持ってくれた方は、ぜひGitHubで試してみて、気づいた点・要望・バグなどフィードバックをいただけるととても嬉しいです。
Discussion