Tanzu Application Platform v1.6(以下TAP)ではアプリをtypeweb
でデプロイするとknativeでデプロイするが、knativeの場合、デフォルトでPVCが利用できない。
$ kubectl get cm -o yaml config-features -n knative-serving |grep persistent-volume-claim
kubernetes.podspec-persistent-volume-claim: "disabled"
一方で、typeserver
でデプロイするとDeploymentでデプロイするが、こちらもPVをデプロイするオプションはなく、PVCも作成できない。
作成したアプリにPVを割り当てたい場合は不便なので、普通のDeploymentでデプロイし、その際にPVCからPVを作成し、PVが割り当てられるようにする。
今回はPVCを作成するClusterConfigTemplate
を作成し、Workloadタイプを新規に作成して、そのタイプで作成したClusterConfigTemplate
を利用するようにして実現する。
なお、このメモをまとめるに当たってWarroyo氏のHow to create custom workload types with TAPを参考にした。
Workloadタイプの作成
既存のworkloadタイプではPVを作成するものがないので、PVを作成できるworkloadタイプを作成する。
ここでは将来的に使いまわしやすいよう、yttでoverlayする形でYAMLを作成する。
まず、typeserver
で使っているClusterConfigTemplate
を取り出した後、不要なフィールドを削除し、yttでoverlay出来るベースのテンプレートをとして利用可能な形に加工した。
取り出す際に叩いたコマンドは以下。
kubectl get clusterconfigtemplates.carto.run server-template -o yaml | kubectl neat -f - > ./server-template.yaml
加工後のClusterConfigTemplate
は以下。
cat << EOF > ./server-template.yaml
#@ load("@ytt:overlay", "overlay")
#@ load("@ytt:data", "data")
---
kind: ClusterConfigTemplate
metadata:
annotations:
doc: #@ data.values.doc
name: #@ data.values.name
spec:
configPath: .data
lifecycle: mutable
params:
- default:
- containerPort: 8080
name: http
port: 8080
name: ports
ytt: #@ data.values.ytt
EOF
また、overlayするvalues.yamlの雛形は以下で作成した。ytt部分はkubectl get clusterconfigtemplates.carto.run server-template -o jsonpath={.spec.ytt}
の内容をコピペしている。
#@data/values
---
name: volumes-template
doc: |
This template supports PVC.
You can use this at own risk.
ytt: |
#@ load("@ytt:data", "data")
#@ load("@ytt:yaml", "yaml")
#@ load("@ytt:struct", "struct")
#@ load("@ytt:assert", "assert")
#@ def merge_labels(fixed_values):
#@ labels = {}
#@ if hasattr(data.values.workload.metadata, "labels"):
#@ labels.update(data.values.workload.metadata.labels)
#@ end
#@ labels.update(fixed_values)
#@ return labels
#@ end
#@ def intOrString(v):
#@ return v if type(v) == "int" else int(v.strip()) if v.strip().isdigit() else v
#@ end
#@ def merge_ports(ports_spec, containers):
#@ ports = {}
#@ for c in containers:
#@ for p in getattr(c, "ports", []):
#@ ports[p.containerPort] = {"targetPort": p.containerPort, "port": p.containerPort, "name": getattr(p, "name", str(p.containerPort))}
#@ end
#@ end
#@ for p in ports_spec:
#@ targetPort = getattr(p, "containerPort", p.port)
#@ type(targetPort) in ("string", "int") or fail("containerPort must be a string or int")
#@ targetPort = intOrString(targetPort)
#@
#@ port = p.port
#@ type(port) in ("string", "int") or fail("port must be a string or int")
#@ port = int(port)
#@ ports[p.port] = {"targetPort": targetPort, "port": port, "name": getattr(p, "name", str(p.port))}
#@ end
#@ return ports.values()
#@ end
#@ def delivery():
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: #@ data.values.workload.metadata.name
annotations:
kapp.k14s.io/update-strategy: "fallback-on-replace"
ootb.apps.tanzu.vmware.com/servicebinding-workload: "true"
kapp.k14s.io/change-rule: "upsert after upserting servicebinding.io/ServiceBindings"
labels: #@ merge_labels({ "app.kubernetes.io/component": "run", "carto.run/workload-name": data.values.workload.metadata.name })
spec:
selector:
matchLabels: #@ data.values.config.metadata.labels
template: #@ data.values.config
---
apiVersion: v1
kind: Service
metadata:
name: #@ data.values.workload.metadata.name
labels: #@ merge_labels({ "app.kubernetes.io/component": "run", "carto.run/workload-name": data.values.workload.metadata.name })
spec:
selector: #@ data.values.config.metadata.labels
ports:
#@ hasattr(data.values.params, "ports") and len(data.values.params.ports) or assert.fail("one or more ports param must be provided.")
#@ declared_ports = {}
#@ if "ports" in data.values.params:
#@ declared_ports = data.values.params.ports
#@ else:
#@ declared_ports = struct.encode([{ "containerPort": 8080, "port": 8080, "name": "http"}])
#@ end
#@ for p in merge_ports(declared_ports, data.values.config.spec.containers):
- #@ p
#@ end
#@ end
---
apiVersion: v1
kind: ConfigMap
metadata:
name: #@ data.values.workload.metadata.name + "-server"
labels: #@ merge_labels({ "app.kubernetes.io/component": "config" })
data:
delivery.yml: #@ yaml.encode(delivery())
これを以下のように変更する。
--- values.orig.yaml 2023-09-12 08:34:55.507524055 +0000
+++ values.yaml 2023-09-12 11:39:08.929620707 +0000
@@ -1,6 +1,6 @@
#@data/values
---
-name: server-template
+name: volumes-template
doc: |
This template supports PVC.
You can use this at own risk.
@@ -9,7 +9,19 @@
#@ load("@ytt:yaml", "yaml")
#@ load("@ytt:struct", "struct")
#@ load("@ytt:assert", "assert")
-
+ #@ load("@ytt:overlay", "overlay")
+
+ #@ def addVolumes():
+ spec:
+ containers:
+ #@overlay/match by="name"
+ - name: workload
+ #@overlay/match missing_ok=True
+ volumeMounts: #@ data.values.params.volumes.volumeMounts
+ #@overlay/match missing_ok=True
+ volumes: #@ data.values.params.volumes.volumes
+ #@ end
+
#@ def merge_labels(fixed_values):
#@ labels = {}
#@ if hasattr(data.values.workload.metadata, "labels"):
@@ -57,7 +69,7 @@
spec:
selector:
matchLabels: #@ data.values.config.metadata.labels
- template: #@ data.values.config
+ template: #@ overlay.apply(data.values.config,addVolumes())
---
apiVersion: v1
kind: Service
@@ -77,13 +89,26 @@
#@ for p in merge_ports(declared_ports, data.values.config.spec.containers):
- #@ p
#@ end
+
+ ---
+ apiVersion: v1
+ kind: PersistentVolumeClaim
+ metadata:
+ name: #@ data.values.workload.metadata.name + "-volumes"
+ labels: #@ merge_labels({ "app.kubernetes.io/component": "run", "carto.run/workload-name": data.values.workload.metadata.name })
+ spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: #@ data.values.params.pvSize
+ storageClassName: default
#@ end
-
---
apiVersion: v1
kind: ConfigMap
metadata:
- name: #@ data.values.workload.metadata.name + "-server"
+ name: #@ data.values.workload.metadata.name + "-volumes"
labels: #@ merge_labels({ "app.kubernetes.io/component": "config" })
data:
delivery.yml: #@ yaml.encode(delivery())
編集内容としては、#@ def addVolumes():
〜#@ end
で関数を定義し、PVのmount処理を定義している。
また、#@ overlay.apply(data.values.config,addVolumes())
で関数呼び出しによってDeploymentの方に処理を追加している。
PVCに関しては #@ def delivery():
の最後に新規に作成するようにしている。
名前が#@ data.values.workload.metadata.name + "-volumes"
としてしまっているため、WorkloadでaddVolumes
の中身を定義する際は、workload名-volumes
にする必要がある点に注意。
また、 #@ def delivery():
の中に入れないと反映されない点も注意。
これを新しいClusterConfigTemplate
としてapplyする。
ytt -f server-template.yaml -f values.yaml | kubectl apply -f -
作成したClusterConfigTemplate
をtypeとして指定できるよう、typeを定義する。
typeの定義はTAPのインストール時に作成したtap-values.yamlに追記する形で行う。
なお、supported_workloads
を定義するとデフォルト値に上書きされる形で定義されるため、デフォルトのweb
やserver
などが消えてしまう。そのため、デフォルト値に追記する形で定義する。
--- tap-values.yaml 2023-08-30 01:15:30.272824163 +0000
+++ tap-values-pvc.yaml 2023-09-12 08:36:32.691925655 +0000
@@ -48,6 +48,10 @@
profile: full
supply_chain: testing_scanning
+ootb_supply_chain_testing_scanning:
+ supported_workloads:
+ - type: web
+ cluster_config_template_name: config-template
+ - type: server
+ cluster_config_template_name: server-template
+ - type: worker
+ cluster_config_template_name: worker-template
+ - type: server-with-volumes
+ cluster_config_template_name: volumes-template
contour:
envoy:
設定をTAPに反映させる。
tanzu package installed update tap --values-file tap-values-pvc.yaml -n tap-install
また、PVCの作成権限がなく、このままWorkloadを作成すると、以下のようなエラーになってしまう。
kapp: Error: Checking existence of resource persistentvolumeclaim/tanzu-java-web-app-hoge-volumes (v1) namespace: tap-demo:
API server says: persistentvolumeclaims "tanzu-java-web-app-hoge-volumes" is forbidden:
User "system:serviceaccount:tap-demo:default" cannot get resource "persistentvolumeclaims" in API group "" in the namespace "tap-demo" (reason: Forbidden)
そのため、TAPが使うアカウントの権限も強化しておく。
kubectl create rolebinding pvc-for-test --clusterrole edit --user="system:serviceaccount:tap-demo:default" -n tap-demo
検証
Workloadをデプロイする。
前回デプロイした、Tanzu Java Web Appを再利用し、config/workload.yaml
のparamsを以下のように編集する。
params:
- name: pvSize
value: 2G
- name: volumes
value:
volumes:
- name: data-mount
persistentVolumeClaim:
claimName: tanzu-java-web-app-pvc-volumes
volumeMounts:
- name: data-mount
mountPath: /data
これにより、先程作成したaddVolumes
やPVCに値を渡す。
Workloadをデプロイする。前回と違って今回作成したtypeを--type server-with-volumes
として指定する。
tanzu apps workload create tanzu-java-web-app-pvc -f ./config/workload.yaml --type server-with-volumes --label app.kubernetes.io/part-of=tanzu-java-web-app-pvc --label apps.tanzu.vmware.com/has-tests="true" --yes --namespace tap-demo
指定したPVCが作成されている事がわかる。
$ kubectl get pvc tanzu-java-web-app-hoge-volumes -n tap-demo
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
tanzu-java-web-app-hoge-volumes Bound pvc-17467f81-4c2a-4714-9b1a-7a877b1d9ca7 1908Mi RWO default 2m35s
Podも無事起動した。
$ kubectl get pod tanzu-java-web-app-pvc-575fcdd7f8-r8qsl -n tap-demo
NAME READY STATUS RESTARTS AGE
tanzu-java-web-app-pvc-575fcdd7f8-r8qsl 1/1 Running 0 41s
describeで見ると、mountも問題なさそうだ。
Mounts:
/data from data-mount (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-xhlsv (ro)
なお、今回typeserver
にあっていないWorkloadを使ったため、Podの設定とかは適切なものになっていない。(Readiness probeでエラーが出たりする)
あくまでもお試しという点でご了承を。