Kubernetesクラスタ運用中、Secretの値に特定の処理を施した上で保存・再利用したいケースはしばしば発生します。たとえば、外部システムが投入するSecretに含まれる文字列を大文字化、もしくは一部のパターンを置換して管理したい場合などです。本記事では、KubernetesネイティブなポリシーエンジンKyvernoを用いて、Secret適用時に文字列処理を行う2つのアプローチについて解説します。
Kyvernoとは
Kyvernoは、KubernetesリソースをYAMLベースで記述し、ポリシーとして適用できる強力なポリシーエンジンです。KyvernoはAdmissionコントローラーとして動作し、以下のような機能を提供します:
- Validate: リソース適用前の条件検証
- Mutate: リソース適用時にパッチを当ててリソースを変更
- Generate: あるリソースをトリガーに、別のリソースを自動生成
文字列処理をしたいシナリオ
たとえば、source-secretというSecretが作成された時、その中のdata.usernameを大文字化してtarget-secretという新たなSecretに反映したいと考えます。最初に考えられるアプローチは、Kyvernoのgenerateを使ってsource-secretが作成されたタイミングでtarget-secretを自動生成する、というものです。
しかしここで、Kyvernoのgenerateには「同一Kindのリソースを同時に生成できない場合がある」という制約があります。特に同一のSecretを生成する際など、generateの仕組み上想定しづらいパターンが発生する可能性があります。
generateルールの制約
Kyvernoのgenerateは、たとえばConfigMapが作成されたタイミングで別のConfigMapを生成する、といった使い方に適しています。しかし、generateで「Secretが来たらSecretを生成する」という場合、"generation kind and match resource kind should not be the same"というエラーが発生します。これは同一Kindを生成すると、その生成されたリソースからまたリソースが増えてしまうこと抑制しているものだとおもわれます。
このような状況では、別のアプローチとしてmutateを使用する方法が考えられます。
mutateで同一Secret内のデータ変換を実現
mutateルールを使えば、source-secretが適用される直前にSecretリソース自体を書き換えることが可能です。つまり、同一のSecretリソースのdata.usernameを事前に大文字化してからクラスタに保存できます。これなら、同一Kind生成の問題を回避できます。
Kyvernoで使える文字列処理関数
KyvernoはJMESPath拡張関数を提供しており、文字列操作に便利な関数が多数あります:
- to_upper(string): 文字列を大文字に変換
- to_lower(string): 文字列を小文字に変換
- replace(old, new, string), replaceAll(old, new, string): 文字列置換
- regex_replaceAll(pattern, replace, string): 正規表現置換
- base64_decode(string) / base64_encode(string): Base64エンコード/デコード
- トリム系関数 (trim, trim_prefix, trim_suffix) など
Secretのdata値はBase64エンコードされています。そのため、data.usernameを加工する場合はbase64_decode()で文字列を元に戻し、その後to_upper()等で大文字化。最後にbase64_encode()で再度エンコードし直す流れとなります。
mutateルールの例
以下はdefaultネームスペースのsource-secretが作成される際に、data.usernameを大文字化して上書きするポリシー例です:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: mutate-secret-data
spec:
rules:
- name: mutate-secret-username
match:
resources:
kinds:
- Secret
namespaces:
- default
names:
- source-secret
mutate:
patchStrategicMerge:
apiVersion: v1
kind: Secret
metadata:
name: "{{ request.object.metadata.name }}"
namespace: "{{ request.object.metadata.namespace }}"
data:
username: "{{ base64_encode(to_upper(base64_decode(request.object.data.username))) }}"
ポリシーの動作確認
- 上記ポリシーをkubectl apply -fで適用します。
- 以下のSecretをkubectl apply -fで投入します:
apiVersion: v1
kind: Secret
metadata:
name: source-secret
namespace: default
type: Opaque
data:
username: dXNlck5hbWU= # "userName"をBase64エンコードした値
- 適用後にkubectl get secret source-secret -o yamlで中身を確認すると、usernameの値はUSERNAMEに大文字化された文字列のBase64エンコードに置き換わっています。
これで、Admission時に同一Secret内で文字列変換が行われたことが確認できます。
まとめ
当初はgenerate機能を用いて、「あるSecretをトリガーに別のSecretを文字列処理して生成する」シナリオを考えましたが、Kyvernoのgenerateには同一Kind生成が難しい状況があります。そこで、同一リソース上でのデータ変換が必要な場合はmutateを使うことで、Admission時にリソース自体を変換できます。
mutateなら、Secretが保存される直前にdataを任意の文字列処理で加工でき、base64_decode(), to_upper(), base64_encode()などのKyverno拡張関数を組み合わせることで、複雑な文字列変換も実現可能です。
KyvernoはポリシーベースでKubernetesリソースを制御・拡張できる有用なツールです。generateとmutateの使い分けを意識しつつ、SecretやConfigMap、他のKubernetesリソースの管理に役立ててみてください。