Kubernetesのyamlを書くときにハイフンの有無で悩む人へ

## はじめに
2021年初夏頃からKubernetesの勉強を少しずつ始めて、2021年11月にCKAを取得しました。
Kubernetesの定義ファイルは基本的にyaml形式で記述することなります。私はyamlはKubernetesの勉強をするうえで初めて触れました。
そのため勉強し始めの頃は、yamlの読み方/書き方に慣れず、何度もエラーで怒られました。特にハイフンについて、付けるパターンと付けないパターンというのがよく分かりませんでした。
このハイフン問題含め、Kubernetes設定ファイルのyamlの読み書きについて自分なりにどう理解したのか記事にしてみようと思います。

この記事ではyamlの正しい文法やKubernetesの仕様については詳細に触れておりません。yamlもしくはkubernetesで検索すると他の素晴らしい記事がHITするかと思いますのでそちらをご参照いただければと。
本記事は、Kubernetesを利用(Kubernetes認定資格に向けて勉強)するうえでのyamlを書くときに少し楽になるテクニック(心構え)として、参考にいただければと思います。

## TL;DR
[API Reference Docs](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/)が便利

## yamlで扱うデータ形式(スカラーとハッシュ)

本記事では以下のyamlファイルを例にしようと思います。Kubernetesの[Deploymentに関するドキュメントページ](https://kubernetes.io/ja/docs/concepts/workloads/controllers/deployment/#creating-a-deployment)から持ってきました。

```yaml:nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
```

[コチラ](https://magazine.rubyist.net/articles/0009/0009-YAML.html)のページにも記載がありますが、yamlでのデータ表現方法は、主に次の3つの組み合わせで記述することになります。

* 配列
* ハッシュ
* スカラー（文字列、数値、真偽値など）

そのうち、ハッシュ(マッピングともいう)は、「キー: 値」の形式で記載します。
なお、コロンの後ろには1つ以上のスペースが必要です。
上記のyaml内に記載の通り、Kubernetesの設定ファイルではマッピングを組み合わせることで各種設定を記述していきます。

`apiVersion: apps/v1`であれば、apiVersionキーの値は"apps/v1"である、ということ。
同様に`kind: Deployment`は、kindキーの値は"Deployment"である、となります。
これら２つは、値がスカラー形式で格納されているパターンです。

上記に書いたような、キーと値が1:1で対応する単純なものではなく
値に当たる部分に1つ以上のハッシュが格納されている、つまり入れ子となっているパターンもあります。

```yaml
metadata:
  name: nginx-deployment
  labels:
    app: nginx
```
metadataキーの値は、nameキーのハッシュとlabelsキーのハッシュで構成されており、

* nameキーには、nginx-deploymentという値が設定されている
* labelsキーにはさらにapp: nginxというハッシュが設定されている

となります。
これらのようにハッシュを入れ子させる場合には、半角スペースでインデントさせる必要があります。
また値として複数のハッシュを持つ場合、それらのインデントの数は揃えてあげる必要があります。インデントの数が違うと、入れ子の解釈が変わってしまうためです。

>表記方法
>インデントを使い階層構造を表現する。ただし、インデントにはタブが使えずスペースのみが使える。スペース2個単位でインデントすることが多い。
https://ja.wikipedia.org/wiki/YAML

## 配列形式でのデータ表現

yamlファイルの中に2か所、ハイフン-があったかと思います。
このハイフンこそが、配列であることを表すマークとなります。

![nginx-deployment3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47734/0643816e-090a-9e68-3f51-74ffec1aa48b.jpeg)

配列の書き方パターンをいくつか提示します。

①スカラーの配列

```yaml
array:
- one
- two
- three
```
②ハッシュの配列

```yaml
hokkaido:
- city: sapporo
- city: asahikawa
- city: hakodate 
```
③複数のハッシュを要素として構成する配列

```yaml
fruits:
- name: apple
  color: red
- name: banana
  color: yellow
- name: grape
  color: purple
```

上記③のように、複数のマッピングの集合体が配列の要素である場合、最初のマッピングの左にハイフン(と半角スペース)を付けます。
ちなみに③は以下のように記載しても同じ内容となります。要素内でのハッシュの順番は内容に影響しませんし、要素ごとにハッシュの順番が異なっても問題ありません(可読性の観点では順番は揃えた方がいいと思いますが)。

```yaml
fruits:
- name: apple
  color: red
- color: yellow
  name: banana
- name: grape
  color: purple
```

では今回のkubernetesのyamlファイルで、配列に当たる部分を見てみます。
![nginx-deployment4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47734/c27177d4-3cbb-5797-1174-0b3919c75105.jpeg)
ここでcontainersは先ほどの③のパターン、portsは②のパターンです。どちらも1つの要素を持つ配列として、設定されています。
※containers[0]やports[0]は正確な表現ではないかもしれないですが…

Podには複数のコンテナを含めることが可能なので、
![nginx-deployment6.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47734/296882c0-0b4f-759a-dd27-a4a9cdb6795a.jpeg)
のように書くとnameがnginxのコンテナと、nameがrediusのコンテナの2つを持つPodとして定義することができます。

## データ形式をどう区別するのか

先ほどのcontainersの部分ですが、コンテナが1つしかないからといって、

```yaml
    spec:
      containers:
        name: nginx   # ハイフンを付けていない
        image: nginx:1.14.2
        ports:
        - containerPort: 80
```
と記載すると、Kubernetesでの実行時にエラーとなってしまいます。

逆に、前述したmetadataについても、

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
- name: nginx-deployment   # ハイフンを付けている
  labels:
    app: nginx
```
のように、配列形式で記載してもエラーとなってしまいます。

これらの違い、つまり配列形式とするのか否かは、Kubernetesの[API Reference Docs](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/)で調べることができます。

今回のyamlファイルのうち、containersは配列で記載しなくてはならないということをドキュメントでも確認してみます。
kindがDeploymentですので、左側のメニューから[Deployment](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#deployment-v1-apps)を選んで開きます。

そしてspec->templete->specの順にリンクをたどっていきます。[コチラ](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podspec-v1-core)のページが表示されるかと思います。
※紛らわしいですが1つ目のspecはDeploymentSpec、2つ目のspecはPodSpecを指します。

containersフィールドを見ると、Container arrayと記載があるため、containersに設定する値は配列形式で記載する(＝ハイフンを付ける)必要があることが分かります。
値のフォーマットとして配列形式での記述が必要なため、たとえコンテナが1つだけの場合でも要素数1の配列として記載する必要があります。

![Screenshot 2022-02-05 at 10-48-05 Kubernetes API Reference Docs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47734/5bf73564-aa69-92e8-c0ee-164d5a8d451d.png)

さらに[Containerのリンク](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#container-v1-core)をクリックすると、containersに設定できる設定内容を見ることができます。

nameやimageは、stringつまり文字列をvalueとして設定していますが、
portsはContainerPort arrayとあり、配列形式での記載が必要となります。上記の例を見てもportsの値は配列を示すハイフンが付いています。

![Screenshot 2022-02-05 at 10-56-10 Kubernetes API Reference Docs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47734/36b4a54a-1a56-b073-27b5-0fae0d6df056.png)

もう一例、metadataの値が配列では無いことも確認してみます。
先ほどと同じ[リンク](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#deployment-v1-apps)先でDeploymentの項目を見てみると、

![Screenshot 2022-02-05 at 10-43-18 Kubernetes API Reference Docs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47734/bd41d3d7-7c01-6b6e-c15d-4ea653b3b553.png)

metadataの形式はObjectMetaとなっていることが分かります。
arrayと記載がないため、配列形式での記載は不要となります。

## 実例から見ていく

別のyamlファイルを例に、どのデータ形式で設定すべきかをAPI Reference Docsで調べていく流れを追ってみます。
以下のPodのテンプレートファイルのうち、nginxコンテナに設定を拡張していく際に、各パラメータにはどのような形式で値を設定する必要があるのかを考えていきます。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: label-demo
  labels:
    environment: production
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80
```

Pod->spec->cotainersの[リンク先](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#container-v1-core)を参照します。

### コンテナ内で環境変数を定義するためにenvパラメータを利用する場合
envフィールドにはEnv arrayとあるので配列形式で設定する必要があります。そのため配列を示すハイフンが必要です。
また[リンク先](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvar-v1-core)を見ると、各種設定内容はnameやvalueといったパラメータを文字列形式で指定すればよいことが分かります。
配列形式のため、複数の環境変数を設定することも可能です。

```yaml
    env:
    - name: DEMO_GREETING
      value: "Hello from the environment"
    - name: DEMO_FAREWELL
      value: "Such a sweet sorrow"
```

### コンテナ起動時に実行させるコマンドを指定するためにcommandパラメータを利用する場合
commandフィールドにはString arrayとあるため、文字列を配列形式で指定する必要があります。

```yaml
   command:
   - sleep
   - 100
```

なおyamlの世界でも括弧を使って配列を次のように表現することもできます。

```yaml
   command: ["sleep", 100]
```

### セキュリティ関連の設定をするsecurityContextパラメータを利用する場合
securityContextはPodSecurityContextとあるため、インデントを付けたうえで、[リンク先](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#securitycontext-v1-core)にあるようなrunAsUserやfsGroupパラメータを設定します。
こちらはarrayという記載が無いため、ハイフンは不要です。


```yaml
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    fsGroupChangePolicy: "OnRootMismatch"
```

上記をマージすると以下のような設定ファイルとなります。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: label-demo
  labels:
    environment: production
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80
    env:
    - name: DEMO_GREETING
      value: "Hello from the environment"
    - name: DEMO_FAREWELL
      value: "Such a sweet sorrow"
    command:
    - sleep
    - 100
    securityContext:
      runAsUser: 1000
      runAsGroup: 3000
      fsGroup: 2000
      fsGroupChangePolicy: "OnRootMismatch"
```
### ラベルについて
metadata->labelsについても触れておきたいと思います。
metadataの[リンク先](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#objectmeta-v1-meta)を見てみると、labelsパラメータには、Object形式とありそれ以上のハイパーリンクはありません。
labelsについては利用者が管理しやすいようにキー名称を指定する目的であるため、個別の設定項目は無いのだろうと理解しています("environment"や"tier"、"partition"など、キーに付けたい名称は利用者によって千差万別でしょうし)
形式がObjectとなっておりarrayでは無いため、複数のラベルを設定する場合も配列として記述するのではなく、ハッシュの集合体(という表現が適切なのか)で記述します。
※リストアップしていくイメージですので、つい配列で書きたくなってしまいますが…

```yaml
  # 以下の書き方はNG
  labels:
  - environment: production
  - app: nginx
```

## おわりに
Kubernetes勉強したての自分にこの記事を見せてあげたいと思い、書いてみました。
上記のようにAPI Reference Docsを読むようにしたことで、「サンプルでハイフンを付けていたから」、「ハイフンを付けないとエラーになるから」のように記述していたのが、配列(ハイフンを付ける)で記載しなくてはならない理由が腹落ちしたことで、結果的にyamlの書き方が分かってきたと思います。
Referenceを読むようになってからは、初めて扱うオブジェクトについても、文法エラーなどでハマってしまうことも減ったように感じます。
CKAなどのKubernetes認定試験はReference含めて公式ドキュメントは参照可能なので、日々の学習の時から分からないときは公式ドキュメントで調べる習慣を付けておくのが大切と思いました。

## 参考リンク
https://yaml.org/spec/1.2.2/
https://magazine.rubyist.net/articles/0009/0009-YAML.html
https://qiita.com/kwbt/items/c596e2cead2b33bc23e9