Pod-Level Customization
On this page
Ververica Platform lets you define custom Kubernetes settings for the pods that run your Flink jobs. Use Kubernetes pod templates to attach labels, annotations, and volume mounts, and to configure JobManager and TaskManager pods independently. For example, you can add sidecar containers for logging, monitoring, or security proxies.
Pod-level customization is configured through the deployment YAML under spec.template.spec.kubernetes, using the kubernetes.jobManagerPodTemplate and kubernetes.taskManagerPodTemplate fields. Each accepts a full Kubernetes pod spec, so you can configure the JobManager and TaskManager pods independently.
The kubectl commands on this page require the deployment to be in a running state. If the deployment has not been started, no pods exist and the commands return no results.
The REST API examples on this page target API v1 and use JSON Merge Patch (Content-Type: application/merge-patch+json, RFC 7396) to update fields on a running deployment. Replace <VVP_HOST>, <TOKEN>, <NAMESPACE>, <DEPLOYMENT_ID>, <DEPLOYMENT_NAME>, and <DEPLOYMENT_TARGET_NAME> with your own values. See API Tokens for how to obtain a token.
The PATCH requests address a deployment by its ID, not its name. To find the ID, list the deployments in the namespace with GET https://<VVP_HOST>/api/v1/namespaces/<NAMESPACE>/deployments and read metadata.id of your deployment. The kubectl commands select pods by the deployment name.
Labels
Attach Kubernetes labels to a pod. Labels are useful for monitoring selectors, network policies, and cost allocation.
1kubernetes:
2 jobManagerPodTemplate:
3 metadata:
4 labels:
5 team: data-engineering
6 env: production
7 taskManagerPodTemplate:
8 metadata:
9 labels:
10 team: data-engineering
11 env: productionUsing the REST API
To apply the same labels through the REST API, send a PATCH request with a JSON Merge Patch body:
1curl -X PATCH https://<VVP_HOST>/api/v1/namespaces/<NAMESPACE>/deployments/<DEPLOYMENT_ID> \
2 -H 'Authorization: Bearer <TOKEN>' \
3 -H 'Content-Type: application/merge-patch+json' \
4 -d '{
5 "spec": {
6 "template": {
7 "spec": {
8 "kubernetes": {
9 "jobManagerPodTemplate": {
10 "metadata": {
11 "labels": {
12 "team": "data-engineering",
13 "env": "production"
14 }
15 }
16 },
17 "taskManagerPodTemplate": {
18 "metadata": {
19 "labels": {
20 "team": "data-engineering",
21 "env": "production"
22 }
23 }
24 }
25 }
26 }
27 }
28 }
29 }'To verify that labels are applied:
1kubectl -n <NAMESPACE> get pod \
2 -l deploymentName=<DEPLOYMENT_NAME> \
3 --show-labelsAnnotations
Attach Kubernetes annotations to a pod. Annotations are commonly used for Prometheus scraping configuration, sidecar injectors such as Istio or Vault, and tooling metadata.
1kubernetes:
2 jobManagerPodTemplate:
3 metadata:
4 annotations:
5 monitoring.example.com/scrape: "true"
6 taskManagerPodTemplate:
7 metadata:
8 annotations:
9 monitoring.example.com/scrape: "true"Using the REST API
To apply the same annotations through the REST API, send a PATCH request with a JSON Merge Patch body:
1curl -X PATCH https://<VVP_HOST>/api/v1/namespaces/<NAMESPACE>/deployments/<DEPLOYMENT_ID> \
2 -H 'Authorization: Bearer <TOKEN>' \
3 -H 'Content-Type: application/merge-patch+json' \
4 -d '{
5 "spec": {
6 "template": {
7 "spec": {
8 "kubernetes": {
9 "jobManagerPodTemplate": {
10 "metadata": {
11 "annotations": {
12 "monitoring.example.com/scrape": "true"
13 }
14 }
15 },
16 "taskManagerPodTemplate": {
17 "metadata": {
18 "annotations": {
19 "monitoring.example.com/scrape": "true"
20 }
21 }
22 }
23 }
24 }
25 }
26 }
27 }'To verify that annotations are applied:
1kubectl -n <NAMESPACE> describe pod \
2 -l deploymentName=<DEPLOYMENT_NAME>,component=jobmanagerVolume Mounts
Mount additional volumes into a pod. Define the volume under spec.volumes and reference it from the main Flink container's volumeMounts (the main container is named flink-main-container).
1kubernetes:
2 jobManagerPodTemplate:
3 spec:
4 volumes:
5 - name: shared-data
6 emptyDir: {}
7 containers:
8 - name: flink-main-container
9 volumeMounts:
10 - name: shared-data
11 mountPath: /opt/flink/shared-data
12 taskManagerPodTemplate:
13 spec:
14 volumes:
15 - name: shared-data
16 emptyDir: {}
17 containers:
18 - name: flink-main-container
19 volumeMounts:
20 - name: shared-data
21 mountPath: /opt/flink/shared-dataUsing the REST API
To mount the same volume through the REST API, send a PATCH request with a JSON Merge Patch body:
1curl -X PATCH https://<VVP_HOST>/api/v1/namespaces/<NAMESPACE>/deployments/<DEPLOYMENT_ID> \
2 -H 'Authorization: Bearer <TOKEN>' \
3 -H 'Content-Type: application/merge-patch+json' \
4 -d '{
5 "spec": {
6 "template": {
7 "spec": {
8 "kubernetes": {
9 "jobManagerPodTemplate": {
10 "spec": {
11 "volumes": [
12 { "name": "shared-data", "emptyDir": {} }
13 ],
14 "containers": [
15 {
16 "name": "flink-main-container",
17 "volumeMounts": [
18 { "name": "shared-data", "mountPath": "/opt/flink/shared-data" }
19 ]
20 }
21 ]
22 }
23 },
24 "taskManagerPodTemplate": {
25 "spec": {
26 "volumes": [
27 { "name": "shared-data", "emptyDir": {} }
28 ],
29 "containers": [
30 {
31 "name": "flink-main-container",
32 "volumeMounts": [
33 { "name": "shared-data", "mountPath": "/opt/flink/shared-data" }
34 ]
35 }
36 ]
37 }
38 }
39 }
40 }
41 }
42 }
43 }'To verify that volumes are mounted:
1kubectl -n <NAMESPACE> describe pod \
2 -l deploymentName=<DEPLOYMENT_NAME>,component=jobmanager \
3 | grep -A4 "shared-data"In earlier 3.1.x versions, a volumeMounts entry targeting flink-main-container could be silently dropped due to a container-naming mismatch during pod template merging, in native-Kubernetes deployments. This is fixed in 3.1.2: volume mounts defined this way now reliably reach the container.
Component-Specific Labels
Apply labels to one component only:
1kubernetes:
2 jobManagerPodTemplate:
3 metadata:
4 labels:
5 role: jobmanager-custom
6 taskManagerPodTemplate:
7 metadata:
8 labels:
9 role: taskmanager-customUsing the REST API
To apply labels to one component only through the REST API, send a PATCH request with a JSON Merge Patch body:
1curl -X PATCH https://<VVP_HOST>/api/v1/namespaces/<NAMESPACE>/deployments/<DEPLOYMENT_ID> \
2 -H 'Authorization: Bearer <TOKEN>' \
3 -H 'Content-Type: application/merge-patch+json' \
4 -d '{
5 "spec": {
6 "template": {
7 "spec": {
8 "kubernetes": {
9 "jobManagerPodTemplate": {
10 "metadata": {
11 "labels": { "role": "jobmanager-custom" }
12 }
13 },
14 "taskManagerPodTemplate": {
15 "metadata": {
16 "labels": { "role": "taskmanager-custom" }
17 }
18 }
19 }
20 }
21 }
22 }
23 }'Sidecar Containers
Add sidecar containers to run alongside the Flink process. Sidecars are useful for log shippers, monitoring agents, and security proxies, without requiring changes to the Flink image.
The image field is required for each sidecar container.
1kubernetes:
2 jobManagerPodTemplate:
3 spec:
4 containers:
5 - name: monitoring-sidecar
6 image: nginx:alpine
7 env:
8 - name: MONITORING_TARGET
9 value: jobmanager
10 taskManagerPodTemplate:
11 spec:
12 containers:
13 - name: monitoring-sidecar
14 image: nginx:alpine
15 env:
16 - name: MONITORING_TARGET
17 value: taskmanagerUsing the REST API
To add the same sidecar container through the REST API, send a PATCH request with a JSON Merge Patch body:
1curl -X PATCH https://<VVP_HOST>/api/v1/namespaces/<NAMESPACE>/deployments/<DEPLOYMENT_ID> \
2 -H 'Authorization: Bearer <TOKEN>' \
3 -H 'Content-Type: application/merge-patch+json' \
4 -d '{
5 "spec": {
6 "template": {
7 "spec": {
8 "kubernetes": {
9 "jobManagerPodTemplate": {
10 "spec": {
11 "containers": [
12 {
13 "name": "monitoring-sidecar",
14 "image": "nginx:alpine",
15 "env": [
16 { "name": "MONITORING_TARGET", "value": "jobmanager" }
17 ]
18 }
19 ]
20 }
21 },
22 "taskManagerPodTemplate": {
23 "spec": {
24 "containers": [
25 {
26 "name": "monitoring-sidecar",
27 "image": "nginx:alpine",
28 "env": [
29 { "name": "MONITORING_TARGET", "value": "taskmanager" }
30 ]
31 }
32 ]
33 }
34 }
35 }
36 }
37 }
38 }
39 }'To verify that sidecar containers are running:
1# Check that both containers are ready (2/2)
2kubectl -n <NAMESPACE> get pods \
3 -l deploymentName=<DEPLOYMENT_NAME>
4
5# Inspect the sidecar on the JobManager pod
6kubectl -n <NAMESPACE> describe pod \
7 -l deploymentName=<DEPLOYMENT_NAME>,component=jobmanager \
8 | grep -A10 "monitoring-sidecar:"
9
10# Inspect the sidecar on the TaskManager pod
11kubectl -n <NAMESPACE> describe pod \
12 -l deploymentName=<DEPLOYMENT_NAME>,component=taskmanager \
13 | grep -A10 "monitoring-sidecar:"Node Scheduling
Control which nodes run the JobManager and TaskManager pods. Use nodeSelector to target nodes with specific labels, tolerations to allow scheduling on tainted nodes, and affinity for more expressive placement rules. Set them under spec of each pod template.
1kubernetes:
2 jobManagerPodTemplate:
3 spec:
4 nodeSelector:
5 workload: flink
6 tolerations:
7 - key: dedicated
8 operator: Equal
9 value: flink
10 effect: NoSchedule
11 affinity:
12 nodeAffinity:
13 requiredDuringSchedulingIgnoredDuringExecution:
14 nodeSelectorTerms:
15 - matchExpressions:
16 - key: topology.kubernetes.io/zone
17 operator: In
18 values:
19 - zone-a
20 taskManagerPodTemplate:
21 spec:
22 nodeSelector:
23 workload: flink
24 tolerations:
25 - key: dedicated
26 operator: Equal
27 value: flink
28 effect: NoScheduleUsing the REST API
To apply the same scheduling rules through the REST API, send a PATCH request with a JSON Merge Patch body:
1curl -X PATCH https://<VVP_HOST>/api/v1/namespaces/<NAMESPACE>/deployments/<DEPLOYMENT_ID> \
2 -H 'Authorization: Bearer <TOKEN>' \
3 -H 'Content-Type: application/merge-patch+json' \
4 -d '{
5 "spec": {
6 "template": {
7 "spec": {
8 "kubernetes": {
9 "jobManagerPodTemplate": {
10 "spec": {
11 "nodeSelector": { "workload": "flink" },
12 "tolerations": [
13 { "key": "dedicated", "operator": "Equal", "value": "flink", "effect": "NoSchedule" }
14 ],
15 "affinity": {
16 "nodeAffinity": {
17 "requiredDuringSchedulingIgnoredDuringExecution": {
18 "nodeSelectorTerms": [
19 {
20 "matchExpressions": [
21 { "key": "topology.kubernetes.io/zone", "operator": "In", "values": ["zone-a"] }
22 ]
23 }
24 ]
25 }
26 }
27 }
28 }
29 },
30 "taskManagerPodTemplate": {
31 "spec": {
32 "nodeSelector": { "workload": "flink" },
33 "tolerations": [
34 { "key": "dedicated", "operator": "Equal", "value": "flink", "effect": "NoSchedule" }
35 ]
36 }
37 }
38 }
39 }
40 }
41 }
42 }'To verify which nodes the pods run on:
1kubectl -n <NAMESPACE> get pods \
2 -l deploymentName=<DEPLOYMENT_NAME> \
3 -o wideIn earlier versions, affinity, tolerations, and nodeSelector under the pod template spec were accepted by the API but not applied to the pods. Since 3.1.1, they reach the rendered JobManager and TaskManager pods.
Complete Configuration Example
The following YAML shows all supported pod customization options combined under spec.template.spec.kubernetes:
1spec:
2 template:
3 spec:
4 kubernetes:
5 jobManagerPodTemplate:
6 metadata:
7 labels:
8 team: data-engineering
9 env: production
10 annotations:
11 monitoring.example.com/purpose: my-flink-job
12 spec:
13 volumes:
14 - name: shared-data
15 emptyDir: {}
16 containers:
17 - name: flink-main-container
18 volumeMounts:
19 - name: shared-data
20 mountPath: /opt/flink/shared-data
21 - name: monitoring-sidecar
22 image: nginx:alpine
23 env:
24 - name: MONITORING_TARGET
25 value: jobmanager
26 taskManagerPodTemplate:
27 metadata:
28 labels:
29 team: data-engineering
30 env: production
31 annotations:
32 monitoring.example.com/purpose: my-flink-job
33 spec:
34 volumes:
35 - name: shared-data
36 emptyDir: {}
37 containers:
38 - name: flink-main-container
39 volumeMounts:
40 - name: shared-data
41 mountPath: /opt/flink/shared-data
42 - name: monitoring-sidecar
43 image: nginx:alpine
44 env:
45 - name: MONITORING_TARGET
46 value: taskmanagerUsing the REST API
To create a deployment with this full configuration through the REST API, send a POST request with the complete deployment body. Replace <DEPLOYMENT_TARGET_NAME> and the artifact fields with your own deployment target and job artifact:
1curl -X POST https://<VVP_HOST>/api/v1/namespaces/<NAMESPACE>/deployments \
2 -H 'Authorization: Bearer <TOKEN>' \
3 -H 'Content-Type: application/json' \
4 -d '{
5 "apiVersion": "v1",
6 "kind": "Deployment",
7 "metadata": {
8 "name": "<DEPLOYMENT_NAME>",
9 "namespace": "<NAMESPACE>"
10 },
11 "spec": {
12 "deploymentTargetName": "<DEPLOYMENT_TARGET_NAME>",
13 "state": "RUNNING",
14 "upgradeStrategy": { "kind": "STATELESS" },
15 "template": {
16 "spec": {
17 "artifact": {
18 "kind": "JAR",
19 "jarUri": "<ARTIFACT_JAR_URI>",
20 "flinkVersion": "<FLINK_VERSION>"
21 },
22 "kubernetes": {
23 "jobManagerPodTemplate": {
24 "metadata": {
25 "labels": { "team": "data-engineering", "env": "production" },
26 "annotations": { "monitoring.example.com/purpose": "my-flink-job" }
27 },
28 "spec": {
29 "volumes": [
30 { "name": "shared-data", "emptyDir": {} }
31 ],
32 "containers": [
33 {
34 "name": "flink-main-container",
35 "volumeMounts": [
36 { "name": "shared-data", "mountPath": "/opt/flink/shared-data" }
37 ]
38 },
39 {
40 "name": "monitoring-sidecar",
41 "image": "nginx:alpine",
42 "env": [
43 { "name": "MONITORING_TARGET", "value": "jobmanager" }
44 ]
45 }
46 ]
47 }
48 },
49 "taskManagerPodTemplate": {
50 "metadata": {
51 "labels": { "team": "data-engineering", "env": "production" },
52 "annotations": { "monitoring.example.com/purpose": "my-flink-job" }
53 },
54 "spec": {
55 "volumes": [
56 { "name": "shared-data", "emptyDir": {} }
57 ],
58 "containers": [
59 {
60 "name": "flink-main-container",
61 "volumeMounts": [
62 { "name": "shared-data", "mountPath": "/opt/flink/shared-data" }
63 ]
64 },
65 {
66 "name": "monitoring-sidecar",
67 "image": "nginx:alpine",
68 "env": [
69 { "name": "MONITORING_TARGET", "value": "taskmanager" }
70 ]
71 }
72 ]
73 }
74 }
75 }
76 }
77 }
78 }
79 }'