YAMLだっていいじゃないか、JSONだもの
2021年08月31日 火曜日
CONTENTS
突然ですが、私はHelmがあまり好きではありません。
さしたる工夫もなくYAMLのテンプレートをアプライする仕組みが嫌いです。
単なるパッケージマネージャではなくインストーラでもあるくせに、環境にアジャストする機能が何もないところも嫌いです。
履歴の管理にSecretリソースを使うところも嫌いです。巨大なマニフェスト(たいていConfigMapだ)をインストールしようとしてetcdがエラーを吐くと泣きたくなります。
Helmのマニュアルにある「templates should follow a YAML-like syntax unless the JSON syntax substantially reduces the risk of a formatting issue.」という文章が嫌いです。どうしても気になるなら、TOMLでも使ってはどうでしょう?
ですが、他に選択肢もないのでしぶしぶHelmを使っています。
マニフェストのパースが簡単
そんなHelmの話はさておき、マニフェストをテンプレートから生成したいなら、どう考えてもJSONの方が向いています。そして、KubernetesではマニフェストをJSONで書いて、kubectlコマンドで普通に入出力できることをご存知でしょうか?それどころか、そもそもKubernetes APIの入出力は多くの場合JSONで行われています。マニフェストがYAMLで作成されることが多いのは、手作業で作るときに楽だからにすぎません。
例えば、こんな風にJSONファイルでマニフェストを作成し、Podを起動することができます。
$ cat pod.json { "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "nginx-json", "namespace": "default" }, "spec": { "containers": [ { "image": "nginx:1.18", "name": "nginx" } ] } } $ kubectl apply -f pod.json pod/hoge created
作成時にJSONを使わずYAMLで作成したマニフェストでも、JSONで参照することが可能です。また、jsonpathを使ってマニフェストをパースし、必要な情報だけ簡単に出力することもできます。こちらは改めて説明するまでもなく、普段からJSONを利用している人が少なくないのではないでしょうか。
$ kubectl get -f pod.json -o json <jsonフォーマットのマニフェスト> $ kubeclt get -f pod.json -o jsonpath='{.status.podIP}' 192.168.76.145
Helmテンプレートがきれい
冒頭でさんざんダメ出ししたHelmですら、実はテンプレートをJSONで書くことができます。これといった工夫をする必要もなく、単にtemplatesディレクトリに配置するファイルをYAMLではなくJSONにするだけです。もちろん、values.yamlも機能します。これで「{{ toYaml .Values.podAnnotations | indent 8 }}」みたいなくそったれな慣用句ともお別れというわけです。
$ cat templates/pod.json { "apiVersion": "v1", "kind": "Pod", "metadata": { "name": "{{ .Values.pod.name }}", "namespace": "default" }, "spec": { "containers": [ { "image": "nginx:1.18", "name": "nginx" } ] } } $ helm install test . --set pod.name=test
テストコードがきれい
さらに、Kubernetesのコントローラなどを開発するときもJSONの方がスマートです。例えば、テストコードのためにこうやってテキストからオブジェクトを作成することがあります。
func GetPodObject(resource string) *v1.Pod { return getResourceObject(resource).(*v1.Pod) } func getResourceObject(json string) runtime.Object { decode := serializer.NewCodecFactory(sch).UniversalDeserializer().Decode obj, _, err := decode([]byte(json), nil, nil) if err != nil { panic(err) } return obj }
このときGetPodObjectに渡す値がYAMLの場合、ソースコードに何行にも渡るマニフェストを記載しなければなりませんが、
pod := GetPodObject(` apiVersion: v1 kind: Pod metadata: name: test namespace: default spec: containers: - name: nginx image: nginx:1.18 `)
JSONならこうです。
pod := GetPodObject(`{"apiVersion":"v1", "kind": "Pod", "metadata":{"name":"test", "namespace":"default"}, "spec": {"containers":[{"image":"nginx:1.18","name":"nginx"}]}}`)
YAMLの場合でもリテラルを外出しすればいいのですが、ソースコードを見て処理内容がわかるほうがうれしいことも多いでしょう。こんなコードが何行も続くようなら、JSONの方が見通しの良いテストが書けそうですよね。それに、go言語はJSONの扱いが得意ですから、テンプレートを使ったり、マーシャリングしたり、動的に生成するのも簡単です。
JSONはもれなくYAMLってどういうこと?
以上で、YAMLは人が読み書きするには便利ですが機械処理するならJSONがよい、という論調でこのブログを締める予定だったのですが、同僚からこんなコメントをもらいました。
「JSONはもれなくYAMLでもあるんですよ」
ちょっと意味がよくわかりませんでしたが、YAMLの仕様を見てみたところ確かに
「every JSON file is also a valid YAML file.」
などと書かれています。
引用:https://yaml.org/spec/1.2/spec.html#id2759572
言われてみれば、マニフェストをYAMLで書くときでもリストをブラケットで囲って表現してみたり、
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: test namespace: default rules: - apiGroups: ['*'] resources: ['*'] verbs: [ "get", "list", "watch" ]
空のオブジェクトを表現するためにカーリーブレイスを使ったりしていた気がします。
kind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: name: deny-from-other-namespaces spec: podSelector: {} ingress: - from: - namespaceSelector: matchLabels: name: kube-system - podSelector: {}
あまり疑問を持たずに使っていましたが、あれはYAMLのJSON互換記法(?)だったのですね。ということは、YAMLはいいとこどりしたフォーマットであって、KubernetesのマニフェストをYAMLでもJSONでも書けるのは当然だったということのようです。参りましたね。
混在して書いてみる
疑うわけではありませんが、最後に冒頭のJSONファイルとして示したマニフェストを混在フォーマットで書き直してみましょう。
$ pod.yaml apiVersion: v1 kind: Pod metadata: {"name":"test", "namespace":"default"} spec: containers: [{ "name": "nginx", "image":"nginx:1.18"}] $ kubectl apply -f pod.yaml pod/test created
通りますね。
なんだか、少しYAMLが好きになってきませんか?