前の記事ではyttのTemplating機能を見てきました。本記事ではYAMLの柔軟な加工に便利なOverlay機能を見ます。
目次
Mapの操作
まずは最もよく使うMapの操作方法を見ます。
値の変更
次のKubernetesのmanifestファイルであるdeployment.ymlの中のDeploymentのreplicasを3に変更したいとします。
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)を作成します。
#@ 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の対象とします。
指定方法はこちらを参照してください。
次のコマンドを実行して、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)を作成します。
#@ 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アノテーションを設定します。
#@ 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/****アノテーションの定義はないケースにも対応するには次のように修正します。
#@ 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+"もつけます。
#@ 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自体の定義も追加します。
#@ 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の場合を除外します。
#@ 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アノテーションをつけます。値はなんでも良いです。
#@ 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はcontainersList中のnameがhello-cnbなMapにenvを追加します。
元のファイルに存在しない場合は、前述の例と同様に#@overlay/match missing_ok=Trueをつけます。
なお、追加するenvもMapのListです。元のファイルにenvが定義されており、
かつその中にnameがINFO_MESSAGEの要素がない場合にエラーにならないように、#@overlay/match by="name", missing_ok=Trueを付けます。
#@ 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ではcontainersListに値を追加します。これも同じく#@overlay/match by="name" missing_ok=Trueを付けています。
#! 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のportsList内のtargetPortを削除します。
前述の例と同じく、@overlay/removeアノテーションを付けます。
#@ 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を例に使います。
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です。)
#@ 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アノテーションを使います。
#@ 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アノテーションを使います。
#@ 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があります。
これに対して、Joe Beda氏がコメントしているので一読の価値があります。