--- title: ytt(YAML Templating Tool)入門 - Overlay編 tags: ["YAML", "ytt", "k14s", "Carvle"] categories: ["Dev", "Carvel", "ytt"] date: 2020-08-19T05:53:29Z updated: 2020-08-19T15:47:44Z --- [前の記事](/entries/544)では[ytt](https://get-ytt.io)のTemplating機能を見てきました。本記事ではYAMLの柔軟な加工に便利なOverlay機能を見ます。 * https://github.com/k14s/ytt/blob/develop/docs/lang-ref-ytt-overlay.md **目次** ### Mapの操作 まずは最もよく使うMapの操作方法を見ます。 #### 値の変更 次のKubernetesのmanifestファイルである`deployment.yml`の中の`Deployment`の`replicas`を`3`に変更したいとします。 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer ``` 次のoverlay file(`scale.yml`)を作成します。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Deployment", "metadata": {"name": "demo"}}) --- spec: replicas: 3 ``` `---`の上の`@overlay/match`アノテーションでマルチドキュメントなYAMLのうち、どのドキュメントを対象にするかを指定します。今回は`Kind`が`Deployment`で、`metadata.name`が`demo`なドキュメントのみをoverlayの対象とします。 指定方法は[こちら](https://github.com/k14s/ytt/blob/develop/docs/lang-ref-ytt-overlay.md#functions)を参照してください。 次のコマンドを実行して、`replicas`が変わっていることを確認してください。 ``` $ ytt -f deployment.yml -f scale.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 3 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer ``` 同じように`Service`の`type`を`LoadBalancer`から`NodePort`に変更します。次のoverlay file(`nodeport.yml`)を作成します。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Service", "metadata": {"name": "demo"}}) --- spec: type: NodePort ``` 次のコマンドを実行して、`type`が変わっていることを確認してください。 ``` $ ytt -f deployment.yml -f nodeport.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: NodePort ``` overlay filesは複数指定できるので、`replicas`の変更と`type`を同時に適用できます。 ``` $ ytt -f deployment.yml -f scale.yml -f nodeport.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 3 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: NodePort ``` 同じディクレトリ配下の全てのファイルを入力にしたい場合は ``` ytt -f . ``` で良いです。 #### 値の追加 次は元の`deployment.yml`にはない要素を追加します。 次の`prometheus-annotation.yml`で`Deployment`の`template`の`metadata`に`annotations`を追加します。 元のファイルにない要素には`#@overlay/match missing_ok=True`アノテーションを設定します。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Deployment", "metadata": {"name": "demo"}}) --- spec: template: metadata: #@overlay/match missing_ok=True annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" ``` 次のコマンドを実行して、`annotations`が追加されていることを確認してください。 ``` $ ytt -f deployment.yml -f prometheus-annotation.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo annotations: prometheus.io/scrape: "true" prometheus.io/port: "8080" spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer ``` 今回の場合は、元のファイルに`annotations`自体の定義がなかったため、上記のoverlay fileで問題ありませんが、 `annotations`の定義はあるけども`prometheus.io/****`アノテーションの定義はないケースにも対応するには次のように修正します。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Deployment", "metadata": {"name": "demo"}}) --- spec: template: metadata: #@overlay/match missing_ok=True annotations: #@overlay/match missing_ok=True prometheus.io/scrape: "true" #@overlay/match missing_ok=True prometheus.io/port: "8080" ``` もう一つ例を見ます。次の`namespace.yml`は**全てのYAMLドキュメント**の`metadata`に`namespace: demo`を追加します。 この場合、`#@overlay/match`の`by`に指定する関数は`overlay.all`です。 デフォルトでは1ドキュメントだけがヒットすることを期待しているのですが、今回は2ドキュメントがヒットするので、 `expects="1+"`もつけます。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all, expects="1+" --- metadata: #@overlay/match missing_ok=True namespace: demo ``` 次のコマンドを実行して、`namespace`が追加されていることを確認してください。 ``` $ ytt -f deployment.yml -f namespace.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo namespace: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo namespace: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer ``` `namespace.yml`にYAMLドキュメントも追加できます。ここでは`Namespace`自体の定義も追加します。 ```yaml #@ load("@ytt:overlay", "overlay") apiVersion: v1 kind: Namespace metadata: name: demo #@overlay/match by=overlay.all, expects="1+" --- metadata: #@overlay/match missing_ok=True namespace: demo ``` 次のコマンドで出力内容に`Namespace`も追加されることを確認してください。 ``` $ ytt -f deployment.yml -f namespace.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo namespace: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo namespace: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer --- apiVersion: v1 kind: Namespace metadata: name: demo namespace: demo ``` `Namespace`の`metadata`に`namespace: demo`が含まれるのが変だと感じる場合は、次のように`overlay.all`ではなく、`overlay.not_op`を使い、`Kind`が`Namespace`の場合を除外します。 ```yaml #@ load("@ytt:overlay", "overlay") apiVersion: v1 kind: Namespace metadata: name: demo #@overlay/match by=overlay.not_op(overlay.subset({"kind": "Namespace"})), expects="1+" --- metadata: #@overlay/match missing_ok=True namespace: demo ``` 次のコマンドで、`Namespace`の定義に`namespace: demo`が含まれていないことを確認してください。 ``` $ ytt -f deployment.yml -f namespace.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo namespace: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo namespace: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer --- apiVersion: v1 kind: Namespace metadata: name: demo ``` #### 値の削除 次は削除します。次の`remove-service-type.yml`は`Service`の`type`を削除します。 削除したい要素には`#@overlay/remove`アノテーションをつけます。値はなんでも良いです。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Service", "metadata": {"name": "demo"}}) --- spec: #@overlay/remove type: ``` 次のコマンドで、`Service`の`type`が削除されていることを確認してください。 ``` $ ytt -f deployment.yml -f remove-service-type.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo ``` ### List内のMapの操作 次にList内のMapを操作する例を見ます。 この場合Listの中のどの要素を操作するのかを`#@overlay/match`アノテーションで指定する必要があります。 #### 値の変更 次の`change-image.yml`では`image`の値を変更します。 `containers`がListなので、このうちのどの要素をoverlayの対象かを指定する必要があります。 `@overlay/match by="name"`アノテーションをつけると、`containers`のうち`name`が合致するものを対象とします。 次の例では`name`が`hello-cnb`な要素の`image`を`harbor.example.com/demo/hello-cnb`に変更します。 ``` #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Deployment", "metadata": {"name": "demo"}}) --- spec: template: spec: containers: #@overlay/match by="name" - name: hello-cnb image: harbor.example.com/demo/hello-cnb ``` 次のコマンドを実行して、`image`の変更を確認してください。 ``` $ ytt -f deployment.yml -f change-image.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: harbor.example.com/demo/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer ``` #### 値の追加 次にList内のMapに値を追加する例を見ます。 次の`demo-env.yml`は`containers`List中の`name`が`hello-cnb`なMapに`env`を追加します。 元のファイルに存在しない場合は、前述の例と同様に`#@overlay/match missing_ok=True`をつけます。 なお、追加する`env`もMapのListです。元のファイルに`env`が定義されており、 かつその中に`name`が`INFO_MESSAGE`の要素がない場合にエラーにならないように、`#@overlay/match by="name", missing_ok=True`を付けます。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Deployment", "metadata": {"name": "demo"}}) --- spec: template: spec: containers: #@overlay/match by="name" - name: hello-cnb #@overlay/match missing_ok=True env: #@overlay/match by="name", missing_ok=True - name: INFO_MESSAGE value: "Hello World!" ``` 次のコマンドを実行して、`env`が追加されていることを確認してください。 ``` $ ytt -f deployment.yml -f demo-env.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb env: - name: INFO_MESSAGE value: Hello World! --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer ``` 次の`sidecar.yml`では`containers`Listに値を追加します。これも同じく`#@overlay/match by="name" missing_ok=True`を付けています。 ```yaml #! sidecar.yml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Deployment", "metadata": {"name": "demo"}}) --- spec: template: spec: containers: #@overlay/match by="name" missing_ok=True - name: hello-sidecar image: busybox command: ['sh', '-c', 'echo Hello ytt! && sleep 3600'] ``` 次のコマンドを実行して、`containers`が更新されていることを確認してください。 ``` $ ytt -f deployment.yml -f sidecar.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb - name: hello-sidecar image: busybox command: - sh - -c - echo Hello ytt! && sleep 3600 --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP targetPort: 8080 selector: app: demo type: LoadBalancer ``` #### 値の削除 次はList内のMapの値を削除します。次の`remove-service-targetport.yml`は`Service`の`ports`List内の`targetPort`を削除します。 前述の例と同じく、`@overlay/remove`アノテーションを付けます。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.subset({"kind":"Service", "metadata": {"name": "demo"}}) --- spec: ports: #@overlay/match by="name" - name: 80-8080 #@overlay/remove targetPort: ``` 次のコマンドを実行して、`targetPort`が削除されていることを確認してください。 ``` $ ytt -f deployment.yml -f remove-service-targetport.yml apiVersion: apps/v1 kind: Deployment metadata: name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - image: making/hello-cnb name: hello-cnb --- apiVersion: v1 kind: Service metadata: name: demo spec: ports: - name: 80-8080 port: 80 protocol: TCP selector: app: demo type: LoadBalancer ``` ### Listの操作 最後に普通のListの操作を見ます。 #### 値の変更 ここではCloud Foundryのmanifest fileである、次の`manifest.yml`を例に使います。 ```yaml applications: - name: hello memory: 1g path: hello.jar buildpacks: - java_buildpack ``` 次の`replace-buildpack.yml`は`buildpacks` Listの中の`java_buildpack`を`java_buildpack_offline`に変えます。 MapのListの場合と異なり、普通のListを操作する場合は、どん要素が対象であるかを示すために、 `@overlay/match`の`by`でListの値そのものと比較するか、Listのindexを指定する必要があります。 値そのものを差し替えるために`@overlay/replace`を付けます。(デフォルトは`@overlay/merge`です。) ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all --- applications: #@overlay/match by=overlay.all - buildpacks: #@overlay/match by=overlay.subset("java_buildpack") #@overlay/replace - java_buildpack_offline ``` 次のコマンドを実行して、`buildpacks`の値が変更されていることを確認してください。 ``` $ ytt -f manifest.yml -f replace-buildpack.yml applications: - name: hello memory: 1g path: hello.jar buildpacks: - java_buildpack_offline ``` #### 値の追加 次の`insert-buildpack.yml`は`buildpacks` Listの中の先頭に`datadog_buildpack`を追加します。 `@overlay/insert before=True`アノテーションを使います。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all --- applications: #@overlay/match by=overlay.all - buildpacks: #@overlay/match by=overlay.index(0) #@overlay/insert before=True - datadog_buildpack ``` 次のコマンドを実行して、`buildpacks`に値が追加されていることを確認してください。 ``` $ ytt -f manifest.yml -f insert-buildpack.yml applications: - name: hello memory: 1g path: hello.jar buildpacks: - datadog_buildpack - java_buildpack ``` #### 値の削除 次の`remove-buildpack.yml`は`buildpacks` Listの中の`java_buildpack`を削除します。 `@overlay/remove`アノテーションを使います。 ```yaml #@ load("@ytt:overlay", "overlay") #@overlay/match by=overlay.all --- applications: #@overlay/match by=overlay.all - buildpacks: #@overlay/match by=overlay.subset("java_buildpack") #@overlay/remove - ``` 次のコマンドを実行して、`buildpacks`の値が削除されていることを確認してください。 ``` $ ytt -f manifest.yml -f remove-buildpack.yml applications: - name: hello memory: 1g path: hello.jar buildpacks: [] ``` --- yttのOverlayの色々なパターンを見てきました。 より実践的な利用例が見たい場合は、cf-for-k8s及びそのサブプロジェクトでyttが多用されているので、参考にすると良いです。 * https://github.com/cloudfoundry/cf-for-k8s/tree/master/config * https://github.com/cloudfoundry/cf-k8s-networking/tree/develop/config * https://github.com/cloudfoundry/cf-k8s-logging/tree/main/config * https://github.com/cloudfoundry/capi-k8s-release/tree/master/templates cf-for-k8sがなぜ広く採用されているHelmやKustomizeを使用しないかという[issue](https://github.com/cloudfoundry/cf-for-k8s/issues/265)があります。 これに対して、Joe Beda氏が[コメント](https://github.com/cloudfoundry/cf-for-k8s/issues/265#issuecomment-672371656)しているので一読の価値があります。