helm Chartを書くときに先に知っておきたかった良い8つのこと

[Kubernetes3 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/kubernetes3)11日目の記事です。

自分がhelm Chartのテンプレートを書く前に知っておけば良かったなーというポイントを紹介します。

大体、[The Chart Template Developer’s Guide](https://docs.helm.sh/chart_template_guide/)に書いてあるので、ここをちゃんと読んでから始めれば良いのですが...

## 1. 複数環境ある場合は--valuesオプションで変数を環境別に指定するのがよさそう

[このスライド](https://speakerdeck.com/chokkoyamada/midoruuea-webapurimadequan-tewohelmhua-sitasabisufalseyun-yong-shi-li)のように環境別に変数ファイルを使うのがよさそうな感じです。(同社なので、マネしてます...)

## 2. --setオプションは--valuesで指定したファイルを上書きする
tagの指定だけ上書きする時などに使っています。

## 3. `-` は難しい

ifの`-`は前の空白と改行を削除してくれるのですが、elseだけ表示したい場合にコントロールが難しいです...
下のように書くと、

```yaml
  {{- if .Values.hpa.enabled -}}
  {{- else -}}
  replicas: {{ .Values.replicas | default "2" }}
  {{- end -}}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "my-chart.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
```

下のようになってしまいますが、yamlとして正しくないので、エラーになります...

```yaml
spec:replicas: 3selector:
    matchLabels:
      app.kubernetes.io/name: my-chart
      app.kubernetes.io/instance: my-chart
```

ドキュメントには `indent function ({{indent 2 "mug:true"}})`と書けとあるのですが、この方法だと`default`が使えない(?)ので、結局、下のようにしました。

```yaml
spec:
  {{- if .Values.hpa.enabled -}}
  {{- else }}
  replicas: {{ .Values.replicas | default "2" }}
  {{- end }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "my-chart.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
```

.Values.hpa.enabled　trueの時、

```yaml
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: my-chart
      app.kubernetes.io/instance: my-chart
```

.Values.hpa.enabled　falseの時、

```yaml
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: my-chart
      app.kubernetes.io/instance: my-chart
```

下のようにしてみると、

```yaml
  {{- if .Values.hpa.enabled -}}
  {{- else }}
  replicas: {{ .Values.replicas | default "2" }}
  {{ end -}}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "my-chart.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
```

trueの時ダメでした...

```yaml
spec:selector:
    matchLabels:
      app.kubernetes.io/name: my-chart
      app.kubernetes.io/instance: my-chart
  template:
    metadata:
      labels:
        app.kubernetes.io/name: my-chart
        app.kubernetes.io/instance: my-chart
```

## 4. templateよりもincludeとnindentを使った方がよさそう

templateはインデントが制御されないので、 `{{- include xxx . | nindent n }}` と書いた方が良いようです。
複数containerあるときなど便利です。

```yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: {{ include "my-chart.fullname" . }}
  labels:
    app.kubernetes.io/name: {{ include "my-chart.name" . }}
    helm.sh/chart: {{ include "my-chart.chart" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}
    app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
  {{- if .Values.hpa.enabled -}}
  {{- else }}
  replicas: {{ .Values.replicas | default "2" }}
  {{- end }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "my-chart.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ include "my-chart.name" . }}
        app.kubernetes.io/instance: {{ .Release.Name }}
    spec:
      containers:
      # ここ
      {{- include "my-chart.app" . | nindent 6 }}
      {{- include "my-chart.sidecar" . | nindent 6 }}
```

## 5. withは書く量が減るが諸刃の剣
`with`を使った場合、`end`までそのスコープの変数しか使えなくなるので、その階層とは別の変数を使いたい場合、`end`するか、再度定義するしかないです。
なので、さっき複数containerでテンプレートを分けると良いかもといいましたが、`with`を使うとスコープ毎に変数を定義しないといけない場合もあります。 

```yaml
{{- define "my-chart.sidecar" -}}
{{- with .Values.sidecar -}}
- image: "{{ .image.repository }}:{{ .image.tag | default "v1.0.2" }}"
  name: sidecar
  resources:
    requests:
      cpu: {{ .resources.requests.cpu | default "50m" }}
      memory: {{ .resources.requests.memory | default "50Mi" }}
    limits:
      cpu: {{ .resources.limits.cpu | default "100m" }}
      memory: {{ .resources.limits.memory | default "100Mi" }}
  ports:
  - containerPort: 24224
    name: sidecar
    protocol: "TCP"
  env:
  - name: SIDECAR_WEB_HOST
    value: "localhost:9990"
{{- end -}}
{{- end -}}
```

## 6. テンプレートのネストとwithのネスト

テンプレートのネストは普通に出来ます。インデントは継承されるので、下記の例だと6 + 2になります。
(上で例示したdeployment内の`{{- include "my-chart.app" . | nindent 6 }}`のため)

```yaml
{{- define "my-chart.app" -}}
{{- with .Values.app -}}
- name: app
  image: "{{ .image.repository }}:{{ .image.tag }}"
  ports:
    - name: http
      containerPort: 80
      protocol: TCP
  livenessProbe:
    httpGet:
      path: /
      port: http
  readinessProbe:
    httpGet:
      path: /
      port: http
  resources:
    requests:
      cpu: {{ .resources.requests.cpu | default "100m" }}
      memory: {{ .resources.requests.memory | default "500Mi" }}
    limits:
      cpu: {{ .resources.limits.cpu | default "100m" }}
      memory: {{ .resources.limits.memory | default "500Mi" }}
  env:
    {{- include "my-chart.app.env" . | nindent 2 }}
{{- end -}}
{{- end -}}
```

ただし、`with` のスコープは親のままなので、`.Values.app`の中のでテンプレートをincludeすると　`.Values.app`　スコープとなってしまいます。

```yaml
{{- define "my-chart.app.env" -}}
# .Values.app.envではない
{{- with .env -}}
env:
- name: APP_ENVIRONMENT
  value: {{ .APP_ENVIRONMENT | quote }}
- name: WRK_WARMUP_DURATION
  value: {{ .WRK_WARMUP_DURATION | default "10" | quote }}
{{- end -}}
{{- end -}}
```

## 8. Valuesのキーは必須

`cpu: {{ .resources.requests.cpu | default "100m" }}`とdefaultを設定しても、
変数ファイルには、必ず、下のように`requests`まで書かないといけません。


```yaml
resources:
  requests: {}
  limits: {}
```
なので、上のようにcontainersごとに分けて、構造化すると、デフォルト値で良いものでもキーを多めに書かないといけなくなります。

```yaml
environment: dev

replicas: 3

app:
  image:
    repository: nginx
    tag: stable
  resources:
    requests: {}
    limits: {}
  env:
    APP_ENVIRONMENT: dev

sidecar:
  image:
    repository: fluentd
    tag: stable
  resources:
    requests: {}
    limits: {}

hpa:
  enabled: false
```

## 最後に
変数とテンプレートの設計をどうするかが頭を悩ませる所です。まだ、模索中です...