Customize the Helm chart
The chart installs a working deployment with its defaults, and most sites change a few of them to fit
their cluster: a registry mirror, an existing database, their ingress controller, a storage class, or
a pod security policy. Every change on this page goes in the my-values.yaml you installed with and
takes effect when you run the install command from Install again. Check the
render first, as in step 6 of the install, so a value the chart rejects fails before anything changes.
| How do I | Section |
|---|---|
| Pull images from my own registry? | Pull images from your own registry |
| Use a database I already run? | Use your own metadata database |
| Use my ingress controller, or the Gateway API? | Publish through your ingress controller |
| Expose a service without an ingress controller? | Expose a Service directly |
| Give a workload more CPU or memory? | Size the workloads |
| Choose storage classes and volume sizes? | Choose storage classes and volume sizes |
| Run under the restricted Pod Security Standard or OpenShift? | Run under restricted pod security |
| Leave out OpenSearch, lineage, MCP, or Lance REST? | Turn off optional components |
| Restrict network traffic to the server? | Restrict network traffic |
| Give the server longer to start? | Tune health checks |
| Run workloads on dedicated nodes? | Place workloads on node pools |
| Add labels, annotations, volumes, sidecars, or CA certificates? | Labels and annotations, Extra volumes and containers, Trust your own certificate authorities |
| Know which Kubernetes permissions the chart grants? | Kubernetes permissions |
| Know what cannot be changed? | Fixed in this release |
Pull images from your own registry
Copy the release images into your registry with the offline toolkit, then set global.registry to
that registry's host. The chart rewrites every image to it except Fluent Bit. See
Air-gapped installation for the steps and the pull Secret
settings.
Use your own metadata database
Set gravitino.postgresql.enabled: false and point the server at a PostgreSQL or MySQL database you
operate. See Metadata database.
Publish through your ingress controller
The chart publishes every host through one Ingress, set under ingress: the UI and MCP on
ingress.hosts, and the server API, Iceberg REST, Lance REST, SCIM and an MCP host of its own on
ingress.api, ingress.iceberg, ingress.lance, ingress.scim and ingress.mcp. Set
ingress.className to your controller's class, as described in
Published hosts.
The server API host can be published through the Gateway API instead, as an HTTPRoute attached to a
Gateway you run. Leave ingress.api off when you do:
gravitino:
ingress:
enabled: true
type: gateway
gateway:
parentRefs:
- name: "{gateway_name}"
namespace: "{gateway_namespace}"
hosts:
- host: "{api_host}"
paths:
- path: /
pathType: PathPrefix
The UI, Iceberg REST, and Lance REST hosts are published only through an Ingress in this release.
Expose a Service directly
Without an ingress controller, give a Service a type of LoadBalancer or NodePort:
| Service | Value | Ports |
|---|---|---|
| Server | gravitino.service.type | 8090 for the API, 9001 for Iceberg REST, 9101 for Lance REST |
| UI | enterpriseWeb.service.type | 3000 |
gravitino.service.loadBalancerSourceRanges limits which addresses reach a LoadBalancer server
Service. The Services carry plain HTTP, so traffic is unencrypted unless your load balancer
terminates TLS in front of them; HTTPS publishes through an
ingress controller instead.
Size the workloads
Each workload takes standard Kubernetes resources. The defaults are in
Workloads and resources.
| Workload | Values |
|---|---|
| Server | gravitino.resources, with the heap in gravitino.env GRAVITINO_MEM |
| PostgreSQL | gravitino.postgresql.resources |
| UI | enterpriseWeb.resources |
| Metrics service | metrics.resources |
| MCP server | mcp.resources |
| OpenSearch | opensearch.resources, with the heap in opensearch.opensearchJavaOpts |
| Lineage API | marquez.marquez.resources |
Raise the server's memory and its heap together: the chart refuses a GRAVITINO_MEM that does not
fit in gravitino.resources.limits.memory. See step 5 of Install for the
values the install sets.
Choose storage classes and volume sizes
| Volume | Size | Storage class |
|---|---|---|
| Server logs | gravitino.logPersistence.size | gravitino.logPersistence.storageClassName |
| Metrics service logs | metrics.logPersistence.size | metrics.logPersistence.storageClassName |
| PostgreSQL data | gravitino.postgresql.persistence.size | The cluster's default StorageClass |
| OpenSearch data | opensearch.persistence.size | opensearch.persistence.storageClass |
The in-cluster PostgreSQL always uses the cluster's default StorageClass. To place it on another class, make that class the default before installing, or use your own database.
PostgreSQL and OpenSearch run as StatefulSets, whose volume claims are fixed when they are created. Set their size before the first install; a later change to it is rejected by Kubernetes. To grow an existing volume, expand the PersistentVolumeClaim itself, where the StorageClass allows expansion.
Run under restricted pod security
The chart's own workloads already meet the restricted Pod Security Standard: they run as non-root
users, drop all capabilities, block privilege escalation, and use the RuntimeDefault seccomp
profile. OpenSearch adds one init container that runs as root to set the ownership of its data
volume. Turn it off, and the pod's fsGroup sets the ownership instead:
opensearch:
persistence:
enableInitChown: false
Each workload's security context can be changed under its own values: gravitino.podSecurityContext
and gravitino.containerSecurityContext, enterpriseWeb.securityContext, metrics.securityContext,
mcp.containerSecurityContext, and opensearch.securityContext.
On OpenShift, apply the packaged openshift-values.yaml, which lets the restricted-v2 SCC assign
user and group IDs; see Red Hat OpenShift.
Turn off optional components
| Component | Values | What is lost |
|---|---|---|
| OpenSearch | opensearch.enabled: false, gravitino.search.storageImpl: memory, and marquez.marquez.opensearch.enable: false | A persistent search index, and search in the lineage view; see below |
| Lineage (Marquez) | marquez.enabled: false and gravitino.lineage.enabled: false | The lineage view and the stored lineage history; see Lineage |
| MCP server | mcp.enabled: false | AI agent access; see MCP server |
| Lance REST service | gravitino.auxService.names: "iceberg-rest" and ingress.lance.enabled: false | Lance clients; see Lance REST service |
Lineage also searches OpenSearch, so marquez.marquez.opensearch.enable: false turns that search off
while lineage stays on; with lineage turned off as well, it is not needed. Without OpenSearch,
memory holds the search index in the server's memory, so after the server restarts the index is
empty until it is rebuilt, as in Restore.
To use an OpenSearch cluster you already run instead, keep opensearch.enabled: false and set
gravitino.search.opensearch.url, gravitino.search.opensearch.username, and
gravitino.search.opensearch.existingSecret, a Secret with the password under the key password.
The chart refuses to render with gravitino.lineage.enabled left on after marquez.enabled is
turned off.
Restrict network traffic
gravitino.networkPolicy.enabled: true adds a NetworkPolicy for the server pod. Its defaults are
strict:
- Inbound, only pods in the release namespace reach the server's ports. An ingress controller in another namespace is blocked.
- Outbound, only DNS and the in-cluster database are allowed. Your sources, object storage, identity provider, and any external metadata database are blocked.
Before turning it on, list what the server must reach in gravitino.networkPolicy.ingressRules and
gravitino.networkPolicy.egressRules. Each list replaces the matching defaults rather than adding to
them, so include DNS and the database in egressRules as well. The example admits an ingress
controller in the ingress-nginx namespace and allows outbound DNS and HTTPS:
gravitino:
networkPolicy:
enabled: true
ingressRules:
- from:
- podSelector: {}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
egressRules:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- podSelector: {}
- ports:
- protocol: TCP
port: 443
Add a rule for every other port the server reaches, such as a database's or a metastore's. The policy covers the server pod only; the other workloads have none.
Tune health checks
The server's readiness and liveness checks are under gravitino.readinessProbe and
gravitino.livenessProbe. Change only their timings, such as initialDelaySeconds or
failureThreshold, and keep the check itself; the chart's liveness check also keeps the pod running
when it stops for a license limit. A server with many catalogs to load at startup may need a longer
initial delay:
gravitino:
readinessProbe:
initialDelaySeconds: 60
livenessProbe:
initialDelaySeconds: 180
The UI's checks are under enterpriseWeb.readinessProbe and enterpriseWeb.livenessProbe.
Place workloads on node pools
Each workload takes the standard Kubernetes scheduling settings, nodeSelector, tolerations and
affinity, under its own key in the values file:
| Workload | Values |
|---|---|
| Server | gravitino.nodeSelector, gravitino.tolerations, gravitino.affinity, gravitino.topologySpreadConstraints |
| UI and metrics service | nodeSelector, tolerations, affinity at the top level of the values file; the two share them |
| MCP server | mcp.nodeSelector, mcp.tolerations, mcp.affinity |
| Lineage API and web view | marquez.nodeSelector, marquez.tolerations, marquez.affinity |
| OpenSearch | opensearch.nodeSelector, opensearch.tolerations, opensearch.affinity |
| Registry-token renewal job | global.pullSecrets.ecrRefresh.nodeSelector, .tolerations, .affinity |
The in-cluster metadata database and the schema Jobs that run at install and upgrade have no scheduling values in this release, so they run wherever the scheduler places them. They need at least one node without a taint they would have to tolerate, even when the metadata database is external, because the server waits for the schema Job before it starts.
The example puts every workload on a node pool labelled pool: datastrato and tainted
dedicated=datastrato:NoSchedule:
nodeSelector:
pool: datastrato
tolerations:
- key: dedicated
operator: Equal
value: datastrato
effect: NoSchedule
gravitino:
nodeSelector:
pool: datastrato
tolerations:
- key: dedicated
operator: Equal
value: datastrato
effect: NoSchedule
mcp:
nodeSelector:
pool: datastrato
tolerations:
- key: dedicated
operator: Equal
value: datastrato
effect: NoSchedule
marquez:
nodeSelector:
pool: datastrato
tolerations:
- key: dedicated
operator: Equal
value: datastrato
effect: NoSchedule
opensearch:
nodeSelector:
pool: datastrato
tolerations:
- key: dedicated
operator: Equal
value: datastrato
effect: NoSchedule
The server needs a node with 8Gi of memory free for its request; see the workloads and resources table in Install.
Labels and annotations
Pod annotations carry settings for tools that read them, such as a service mesh, a log collector or a backup operator. Pod labels let policies and monitoring select the pods.
| Workload | Pod annotations | Pod labels |
|---|---|---|
| Server | gravitino.podAnnotations | gravitino.podLabels |
| UI | enterpriseWeb.podAnnotations | |
| Metrics service | metrics.podAnnotations | |
| MCP server | mcp.podAnnotations | |
| Lineage API | marquez.marquez.podAnnotations |
To give the server's Kubernetes service account a cloud identity, such as an IAM role on EKS or a
Google service account on GKE, annotate it with gravitino.serviceAccount.annotations.
Extra volumes and containers
The server takes extra volumes, containers and init containers:
| Value | Adds |
|---|---|
gravitino.extraVolumes | Volumes on the server pod |
gravitino.extraVolumeMounts | Mounts of those volumes in the server container |
gravitino.extraInitContainers | Containers that run before the server starts |
gravitino.extraContainers | Containers that run beside the server, such as a log shipper |
The chart refuses to render a mount of a Secret that does not exist yet, so create the Secret first.
/opt/gravitino/logs is always the chart's logs volume and cannot be remounted. The
Google Cloud credentials section shows a complete example.
The MCP server takes mcp.extraVolumes and mcp.extraVolumeMounts the same way.
Trust your own certificate authorities
When the server connects to a database, metastore, identity provider or other service whose certificate is signed by an internal certificate authority, import that authority into the server's Java trust store:
kubectl -n "$NAMESPACE" create secret generic internal-ca --from-file=ca.crt=./internal-ca.crt
gravitino:
ssl:
trustStore:
enabled: true
existingSecrets:
- name: internal-ca
Every .crt and .pem file in each Secret listed is imported. existingSecret takes a single
Secret instead. ssl.trustStore.javaOptions adds Java system properties for TLS, such as
-Djavax.net.debug=ssl:handshake while troubleshooting a handshake.
The UI makes its own TLS connections, to your identity provider for sign-in. When the identity provider's certificate comes from an internal authority, mount the same Secret into the UI and point Node.js at it:
enterpriseWeb:
volumes:
- name: internal-ca
secret:
secretName: internal-ca
volumeMounts:
- name: internal-ca
mountPath: /etc/internal-ca
readOnly: true
env:
- name: NEXTAUTH_URL
value: "https://{ui_hostname}"
- name: AUTH_TRUST_HOST
value: "true"
- name: NODE_EXTRA_CA_CERTS
value: /etc/internal-ca/ca.crt
enterpriseWeb.env replaces the chart's list, so keep the NEXTAUTH_URL and AUTH_TRUST_HOST
entries from your my-values.yaml with the values you installed with. The UI's session secret is
not in this list: the chart keeps it in the Secret {release}-ui-auth.
Environment variables
Environment variables for the server go in gravitino.env, and for the UI in enterpriseWeb.env.
See Configuration files and environment.
Kubernetes permissions
The chart creates no cluster-wide roles. Each Role it creates is limited to the release namespace:
| Service account | Can |
|---|---|
| UI | Read and update the server ConfigMap; read and update Deployments and StatefulSets; read pods, Ingresses, and Istio Gateways |
| Schema Job | Scale the server Deployment and read pods, during install and upgrade |
| Registry-token renewal | Create and update the image pull Secret, when global.pullSecrets.ecrRefresh is on |
Where the installer may not grant access to Istio Gateways, set enterpriseWeb.rbac.istioGateway: false.
Fixed in this release
- One server replica. The server runs as a single pod and does not scale out or autoscale.
- A disruption budget blocks drains.
gravitino.podDisruptionBudgetwith its defaultminAvailable: 1keeps a node with the server pod from draining, since one replica can never be disrupted. Leave it off. - PostgreSQL storage class. The in-cluster PostgreSQL uses the cluster's default StorageClass.