Rook CephのHelmチャートで、PodのnodeAffinityに関するvalueを指定したところ、マニフェストのレンダリングに失敗してしまいました。原因は、 rook-ceph-operator-config のテンプレートの書き方にありました。
本記事では、問題が発生した箇所のみをピックアップしたHelmチャートを作り、どのようにして解決したかを記載します。Rook Ceph以外のHelmチャートでも発生し得る問題なので、ご参考になれば幸いです。
発生した問題の再現
以下は、Rook CephのHelmチャートで問題が起きた箇所のみをピックアップしたものの構成です。
.
├── Chart.yaml
├── templates
│ └── configmap.yaml
└── values.yaml
ConfigMapは以下のような内容になっています。
kind: ConfigMap
apiVersion: v1
metadata:
name: rook-ceph-operator-config-snippet
namespace: {{ .Release.Namespace }}
data:
CSI_RBD_PLUGIN_RESOURCE: {{ .Values.csi.csiRBDPluginResource | quote }}
CSI_RBD_PLUGIN_NODE_AFFINITY: {{ .Values.csi.rbdPluginNodeAffinity }}
CSI_RBD_PLUGIN_RESOURCE には、あるPodのリソースに関する情報(CPU, memoryのrequest/limit)が格納され、Helmの csi.csiRBDPluginResource を介して値が渡されます。
また CSI_RBD_PLUGIN_NODE_AFFINITY は、同じPodのnodeAffinityに関する情報が入り、 csi.rbdPluginNodeAffinity のvalueが渡されます。
両方とも、YAMLやJSON等で表される構造を持ったデータを値として渡す必要があり、values.yamlには次のように書きました。
csi:
csiRBDPluginResource: |
- name : csi-provisioner
resource:
requests:
memory: 100Mi
cpu: 100m
rbdPluginNodeAffinity: |
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
Helmチャートをインストール可能かどうかlintコマンドで確認すると、エラーになってしまいます。
$ helm lint .
==> Linting .
[INFO] Chart.yaml: icon is recommended
[ERROR] templates/configmap.yaml: unable to parse YAML: error converting YAML to JSON: yaml: line 8: mapping values are not allowed in this context
Error: 1 chart(s) linted, 1 chart(s) failed
ConfigMapの CSI_RBD_PLUGIN_NODE_AFFINITY に、許容されていない mapping values を格納しようとしていることが原因のようです。
一方 CSI_RBD_PLUGIN_RESOURCE はエラーが出ていません。現時点では確認できませんが、仮にlintに成功したとしてその値を確認すると、次のような値がレンダリングされています。
CSI_RBD_PLUGIN_RESOURCE: "- name : csi-provisioner\n resource:\n requests:\n memory: 100Mi\n cpu: 100m\n"
valueの値を変えてレンダリング結果を確認
このような違いがでるのはなぜなのでしょうか?
原因を探るため、両方のvalueに1行のシンプルな文字列を入れてみます。
csi:
csiRBDPluginResource: "hogehoge"
rbdPluginNodeAffinity: "fugafuga"
lintが通るようになりました。
$ helm lint . -f values-simple.yaml
==> Linting .
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
helm templateも通りますが、一方だけダブルクォートされていますね。
$ helm template . -f values-simple.yaml
---
# Source: helm-chart-value-one-liner-test/templates/configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: rook-ceph-operator-config-snippet
namespace: default
data:
CSI_RBD_PLUGIN_RESOURCE: "hogehoge"
CSI_RBD_PLUGIN_NODE_AFFINITY: fugafuga
values.yamlを使った場合のエラーの原因
Helmのテンプレートを見ると、以下のように CSI_RBD_PLUGIN_RESOURCE だけ | quote が追加されており、これがレンダリング結果の違いを生んでいるようです。
CSI_RBD_PLUGIN_RESOURCE: {{ .Values.csi.csiRBDPluginResource | quote }}
CSI_RBD_PLUGIN_NODE_AFFINITY: {{ .Values.csi.rbdPluginNodeAffinity }}
最初のhelm lintのエラーでもわかるように、values.yaml に記載した値をHelmチャートでそのまま参照すると、文字列ではなくて「mapping(YAMLの構造)」と解釈されてしまいます。2つ目の変数 CSI_RBD_PLUGIN_NODE_AFFINITY がそのケースです。
一方、CSI_RBD_PLUGIN_RESOURCE ではテンプレートの最後に | quote をつけることで、mapping 全体がダブルクォートされて文字列(string)に変換されています。helm templateが成功すると、以下のようにダブルクォートして出力されます。
CSI_RBD_PLUGIN_RESOURCE: "- name : csi-provisioner\n resource:\n requests:\n memory: 100Mi\n cpu: 100m\n"
ということは、 CSI_RBD_PLUGIN_NODE_AFFINITY にも | quote をつければ解決するのですが、外部Helmチャートを修正するにはそれなりのエネルギーが必要です。
Helmチャートのテンプレートは変えずに、valueの指定方法を工夫して解決できないでしょうか?
この問題はRook Cephのv1.18.3 で解決されており、このバージョンにアップデートすれば、元の values.yaml の指定方法でも問題なく動作します。
解決策
次のように書くと解決します。
csi:
csiRBDPluginResource: |
- name : csi-provisioner
resource:
requests:
memory: 100Mi
cpu: 100m
rbdPluginNodeAffinity: |
|-
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
ポイントは、 rbdPluginNodeAffinity 配下の元の記述の間に |- の行を追記して、元の記述の各行に適切にスペースを開けることです。
このようにすると、lintが通ります。
$ helm lint . -f values-solved.yaml
==> Linting .
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
helm templateすると、次のように出力されます。
$ helm template . -f values-solved.yaml
---
# Source: helm-chart-value-one-liner-test/templates/configmap.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: rook-ceph-operator-config-snippet
namespace: default
data:
CSI_RBD_PLUGIN_RESOURCE: "- name : csi-provisioner\n resource:\n requests:\n memory: 100Mi\n cpu: 100m\n"
CSI_RBD_PLUGIN_NODE_AFFINITY: |-
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
CSI_RBD_PLUGIN_NODE_AFFINITY: の後に、 |- がついているのが鍵です。記号の説明は割愛しますが(こちらの記事がわかりやすいです!)、Helmチャートからマニフェストをレンダリングした後に requiredDuring... 以降の記述がYAMLの構造ではなくて、文字列として解釈されるようになりました。ConfigMapの各キーに対応する値の型は string の必要があり、その要件を満たしているのでエラーが発生しなくなりました。
尚、quote が入っている CSI_RBD_PLUGIN_RESOURCE: の値は、 "- name : csi-provisioner\n resource:\n requests:\n memory: 100Mi\n cpu: 100m\n" のように一行で記述されていますが、HelmチャートをインストールしてConfigMapの中身を確認すると、次のように改行で表示されているのを確認できました。
helm install cm-test . -f values-solved.yaml -n default
$ kubectl get cm rook-ceph-operator-config-snippet -oyaml
apiVersion: v1
kind: ConfigMap
data:
CSI_RBD_PLUGIN_RESOURCE: |
- name : csi-provisioner
resource:
requests:
memory: 100Mi
cpu: 100m
CSI_RBD_PLUGIN_NODE_AFFINITY: |
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
上と同じことをRook CephのHelmチャートでも実施すれば、nodeAffinityの設定が効いてやりたいことをできるはずです。
おわりに
Helmチャートを扱う場合はHelmだけでなく、YAMLの知識が必要なことを痛感させられました。。。