YAMLだっていいじゃないか、JSONだもの

2021年08月31日 火曜日


【この記事を書いた人】
田口 景介

社会人生活の半分をフリーランス、半分をIIJで過ごすエンジニア。元々はアプリケーション屋だったはずが、クラウドと出会ったばかりに半身をインフラ屋に売り渡す羽目に。現在はコンテナ技術に傾倒中だが語りだすと長いので割愛。タグをつけるならコンテナ、クラウド、ロードバイク、うどん。

「YAMLだっていいじゃないか、JSONだもの」のイメージ

突然ですが、私は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が好きになってきませんか?

 

田口 景介

2021年08月31日 火曜日

社会人生活の半分をフリーランス、半分をIIJで過ごすエンジニア。元々はアプリケーション屋だったはずが、クラウドと出会ったばかりに半身をインフラ屋に売り渡す羽目に。現在はコンテナ技術に傾倒中だが語りだすと長いので割愛。タグをつけるならコンテナ、クラウド、ロードバイク、うどん。

Related
関連記事