Kubernetes流YAML職人になるために

この記事は [Kubernetes2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/kubernetes2) 6日目の記事です。

---

## はじめに

Kubernetes にリソースを追加するときは YAML を使って記述したマニフェストをクラスタに適用します。
YAMLが書ける = Kubernetesの運用ができる と言っても過言ではありません。

この記事では Kubernetes のマニフェストを YAML で書くためのノウハウを紹介します。

対象者: これからKubernetesを触ろうとしている人

## YAMLに慣れる

まずはYAMLの構造を体で覚えます。

デフォルトのkubernetesにはnamespaceなどのリソースがいくつか入っているので、これをYAMLやJSONで出力して比較してみましょう。

```console
$ kubectl get ns -o yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Namespace
  metadata:
︙
$ kubectl get ns -o json
{
    "apiVersion": "v1",
    "items": [
        {
            "apiVersion": "v1",
            "kind": "Namespace",
            "metadata": {
︙
```

Kubernetes本体のテスト用マニフェストを見るのもおすすめです。
https://github.com/kubernetes/kubernetes/tree/master/hack/testdata

## YAMLの文法を知る

Kubernetesのマニフェストを書くときにハマりやすい仕様や知っておくと便利な機能を抜粋して紹介します。

### 型

YAMLで使える型はいろいろあるのですが、基本的には下のものだけ覚えておけばKubernetesの運用には十分です。

- null
    - `~`, `null`, `Null`, `NULL`
- bool
    - `true` => `true`, `True`, `TRUE`, `yes`, `Yes`, `YES`, `on`, `On`, `ON`
    - `false` => `false`, `False`, `FALSE`, `no`, `No`, `NO`, `off`, `Off`, `OFF`
- int, float
    -  数字っぽいやつ
- string
    - ↑のパターンに当てはまらないもの
    - `"` や `'` で囲った文字列

数字や特殊な単語以外は基本文字列と思って大丈夫です。

文字列しか受け付けない場所に `true` を入れたりしてエラーになることが多いので注意しましょう。
(回避するには `"true"` とする必要があります)
文字列にしたい場合はとりあえず `"` で囲うのもアリだと思います。

### 改行

基本的に文字列の中の改行はスペースとして扱われます。

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: hoge
  annotations:
    fuga: a
      b
      c
```

こう書くと fuga は 

```
a b c
```

と評価されます。
改行は1段下げて揃える必要があるので注意しましょう。

改行文字を入れたい場合は `\n` を使うか `|` を使って

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: hoge
  annotations:
    fuga: |
      a
      b
      c
```

のように書きます。

```
a
b
c
```

こうすることで複数行の文字列として評価されるようになります。
`ConfigMap` などファイルを埋め込みたいときによく使うので覚えておきましょう。

### JSON

YAMLにはJSONも書けます。
そのままコピペするときや、コンパクトに記述したいときに便利です。

コンテナを起動するときのコマンド配列などでよく見かけます。

```yaml
containers:
- command: ["tail", "-f", "/dev/null"]
```

### ドキュメント分割

`---` だけの行を書くことで、同じファイルの中でドキュメントを分割することができます。

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: hoge
---
apiVersion: v1
kind: Namespace
metadata:
  name: fuga
```

## 書き方を知る

YAML自体の書き方はだいたい理解した(?)と思うので、Kubernetesのマニフェストの書くときにどう調べればいいかを紹介していきます。

### ベースになるものを探す

世界は広いので同じようなことをしようとする人は必ずいるはずです。
ググるなりGitHubで検索するなりして叩き台を入手しましょう。

### わからない機能を調べる

わからない機能が出てきたらリファレンスを見て調べましょう。

`kubectl explain` に見たいリソースとフィールド名を渡すと、使えるフィールドとその型、解説を表示してくれます。

```console
$ kubectl explain deployment
KIND:     Deployment
VERSION:  extensions/v1beta1

DESCRIPTION:
     DEPRECATED - This group version of Deployment is deprecated by
     apps/v1beta2/Deployment. See the release notes for more information.
     Deployment enables declarative updates for Pods and ReplicaSets.

FIELDS:
   apiVersion	<string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

   kind	<string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

   metadata	<Object>
     Standard object metadata.

   spec	<Object>
     Specification of the desired behavior of the Deployment.

   status	<Object>
     Most recently observed status of the Deployment.

$ kubectl explain deployment.spec
KIND:     Deployment
VERSION:  extensions/v1beta1

RESOURCE: spec <Object>

DESCRIPTION:
     Specification of the desired behavior of the Deployment.

     DeploymentSpec is the specification of the desired behavior of the
     Deployment.

FIELDS:
   minReadySeconds	<integer>
     Minimum number of seconds for which a newly created pod should be ready
     without any of its container crashing, for it to be considered available.
     Defaults to 0 (pod will be considered available as soon as it is ready)

   paused	<boolean>
     Indicates that the deployment is paused and will not be processed by the
     deployment controller.

   progressDeadlineSeconds	<integer>
     The maximum time in seconds for a deployment to make progress before it is
     considered to be failed. The deployment controller will continue to process
     failed deployments and a condition with a ProgressDeadlineExceeded reason
     will be surfaced in the deployment status. Note that progress will not be
     estimated during the time a deployment is paused. This is not set by
     default.

︙
```

Webでも同じものが見れます。
https://kubernetes.io/docs/reference/

## あっているか確かめる

マニフェストが書けたらKubernetesにapplyしてみて問題がないか確認してみましょう。
その場でapplyされると困る場合が多いと思うので、 `--dry-run` を付けて実行します。
また、　`-o yaml` を付けると標準的なフォーマットに直してくれるのでエラーにならない間違いがあれば気付くこともできます。

```console
$ kubectl apply -f - --dry-run -o yaml << 'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: "hoge"
EOF
apiVersion: v1
kind: Namespace
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"hoge","namespace":""}}
  name: hoge
  namespace: ""
```

`name` の `"` が外れました。
`last-applied-configuration` という余分なannotationが付いていますが、これは `kubectl apply` コマンドが付けるものなので最終成果物には含めないようにしましょう。

エラーが出る例も紹介しておきます。

```console
kubectl apply -f - --dry-run -o yaml << 'EOF'
apiVersion: v1
kind: Namespace
metadeta:
  name: "hoge"
EOF
error: error validating "STDIN": error validating data: ValidationError(Namespace): unknown field "metadeta" in io.k8s.api.core.v1.Namespace; if you choose to ignore these errors, turn validation off with --validate=false
```

`metadeta` というフィールドがおかしいと教えてくれています。
また、必要なフィールドがないときも教えてくれます。

```console
$ kubectl apply -f - --dry-run -o yaml << 'EOF'
apiVersion: v1
kind: Namespace
EOF
error: error when retrieving current configuration of:
Resource: "/v1, Resource=namespaces", GroupVersionKind: "/v1, Kind=Namespace"
Name: "", Namespace: ""
Object: &{map["apiVersion":"v1" "kind":"Namespace" "metadata":map["namespace":"" "annotations":map["kubectl.kubernetes.io/last-applied-configuration":""]]]}
from server for: "STDIN": resource name may not be empty
```

文法チェッカーとして使うこともできますが、requiredなフィールドがなくてもエラーになってしまうので注意しましょう。

## まとめ

`kubectl explain` を紹介するだけの記事を書くつもりでしたが、どうせならということで普段YAMLを書くときのやり方みたいなものを少し混ぜてみました。

コマンドの中にリファレンスが入っていると調べやすいので重宝しています。
ぜひ使ってみてください。

よいYAMLライフを！！