Skip to main content
Version 3.0

External exposure: Ingress and Gateway API

CompatibilityKubernetes1.32+OpenShift4.19+Rancher2.10.x+Helm3.2.0+

Page purpose

This page explains how to expose HTTP/HTTPS interfaces of TDP Kubernetes components outside the cluster, using Ingress or Gateway API.

Both models solve the same goal — publishing HTTP/HTTPS traffic with a stable hostname — but use different Kubernetes resources, depend on distinct controllers, and have separate configuration blocks in TDP charts.

When to expose a component externally

Expose a component when it needs to be accessed by users, browsers, external tools, or HTTP/HTTPS integrations outside the Kubernetes cluster — for example, when you want a stable address like https://superset.company.com instead of relying on temporary commands or local ports.

ModeWhen to use
Internal Service (<SERVICE_NAME>.<NAMESPACE>.svc.cluster.local)Communication between components inside the cluster
kubectl port-forwardTemporary access for testing or diagnostics — depends on the terminal, not shared
IngressStable HTTP/HTTPS hostname exposure using an Ingress Controller
Gateway APIStable HTTP/HTTPS hostname exposure using a Gateway Controller and Gateway resource

Ingress vs Gateway API

Ingress and Gateway API are alternatives for publishing HTTP/HTTPS traffic outside the cluster. The mode is chosen in the TDP-Settings.gateway block, but the detailed configuration is not the same in both models.

AspectIngressGateway API
Kubernetes resource createdIngressHTTPRoute (and optionally Gateway)
Required controllerIngress Controller (NGINX, Traefik…)Gateway Controller (Envoy Gateway, Istio…)
Entry resource in the clusterManaged by the Ingress ControllerGateway resource (created by the chart or pre-existing)
Controller associationingressClassNamegatewayApi.gatewayClassName and gatewayApi.parentRefs
Hostnamehosts[].host or hostnamehostnames[]
Pathspaths[]rules[].matches[]
BackendDefined by the chart template or pathsbackendRefs[]
TLSingress.tls or the component's equivalent blockIn the Gateway or in certificateRefs

Exclusivity rule

Use only one mode per component

TDP-Settings.gateway.ingress.enabled and TDP-Settings.gateway.gatewayApi.enabled are mutually exclusive.

  • Ingress: TDP-Settings.gateway.ingress.enabled: true and gatewayApi.enabled: false
  • Gateway API: TDP-Settings.gateway.gatewayApi.enabled: true and ingress.enabled: false

Never enable both at the same time for the same component.

Prerequisites

Common to both modes:

  • Component namespace created and chart installed (or values file prepared).
  • DNS or /etc/hosts pointing the hostname to the entry controller address.

Ingress:

  • Ingress Controller installed in the cluster (NGINX, Traefik, or equivalent).
  • ingressClassName known (kubectl get ingressclass).

Gateway API:

  • Gateway Controller installed in the cluster.
  • GatewayClass available (kubectl get gatewayclass).
  • CRD gateways.gateway.networking.k8s.io installed.

For local testing, add the hostname to /etc/hosts:

# /etc/hosts
<CONTROLLER_IP> <COMPONENT_HOSTNAME>

Common configuration pattern

The control key is TDP-Settings.gateway. The detailed structure varies by chart — consult the table below, the component configuration page, or the values.yaml:

Terminal input
helm show values oci://registry.tecnisys.com.br/tdp/charts/<CHART_NAME> > default-values.yaml

Example:

Terminal input
helm show values oci://registry.tecnisys.com.br/tdp/charts/tdp-cloudbeaver > values-cloudbeaver.yaml

How it works

In Ingress mode, the chart creates an Ingress resource in the cluster. An Ingress Controller receives external traffic and forwards it to the component Service based on the configured hostname and path.

Apply the values

Add the desired configuration to your values file and apply via Helm:

Terminal input
helm upgrade --install <RELEASE_NAME> \
oci://registry.tecnisys.com.br/tdp/charts/<CHART_NAME> \
-n <NAMESPACE> --create-namespace --timeout 10m \
-f <VALUES_FILE>

Replace <CHART_NAME> with the component chart name (e.g., tdp-jupyter, tdp-airflow). The full OCI path is in the support table above or on the component configuration page.

Test locally without DNS

If the hostname doesn't yet have a DNS entry, get the Ingress Controller IP and add it to /etc/hosts:

Terminal input
# Example for ingress-nginx — adjust namespace and Service name if different
kubectl get svc -n ingress-nginx ingress-nginx-controller \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'

# Add to /etc/hosts (replace with actual values)
echo "<CONTROLLER_IP> <COMPONENT_HOSTNAME>" | sudo tee -a /etc/hosts

Then access: http://<COMPONENT_HOSTNAME>

Common pattern (Jupyter, Kafka UI, ClickHouse, Airflow)

Components with top-level ingress.* follow this model:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

ingress:
ingressClassName: <INGRESS_CLASS>
hosts:
- host: <COMPONENT_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []

JupyterHub

For long notebook sessions, add timeout annotations to the Ingress:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

ingress:
ingressClassName: <INGRESS_CLASS>
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
hosts:
- host: <JUPYTER_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []

Without timeout annotations, cells with long execution can result in a gateway timeout (504).

Airflow

In addition to the Ingress, configure proxy and CORS in the subchart:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

ingress:
ingressClassName: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <AIRFLOW_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []

tdp-airflow:
config:
webserver:
base_url: "http://<AIRFLOW_HOSTNAME>"
x_forwarded_proto: "http"
x_forwarded_host: "<AIRFLOW_HOSTNAME>"
x_forwarded_port: "<SERVICE_PORT>"
api:
cors_enabled: "True"
cors_allow_origin: "*"

Argo CD

Uses its own structure under tdp-argo.server.ingressnot the top-level ingress block:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

tdp-argo:
configs:
params:
server.insecure: "true"
cm:
url: http://<ARGOCD_HOSTNAME>
server:
ingress:
enabled: true
controller: generic
hostname: <ARGOCD_HOSTNAME>
ingressClassName: <INGRESS_CLASS>
annotations: {}
path: /
pathType: Prefix

OpenShift

On OpenShift, use ingressClassName: openshift-default and the hostname follows the cluster's route pattern:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

tdp-argo:
configs:
params:
server.insecure: "true"
cm:
url: http://argocd-<NAMESPACE>.apps.<CLUSTER_DOMAIN>
server:
ingress:
enabled: true
controller: generic
ingressClassName: openshift-default
hostname: argocd-<NAMESPACE>.apps.<CLUSTER_DOMAIN>
path: /
pathType: Prefix
annotations: {}

ClickHouse

Ingress exposes the HTTP interface (8123). Configure extraUsers to allow connections from the Ingress network:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

tdp-clickhouse:
clickhouse:
extraUsers: |
<yandex>
<profiles>
<default>
<query_cache_system_table_handling>ignore</query_cache_system_table_handling>
</default>
</profiles>
<users>
<default>
<networks>
<ip>::/0</ip>
</networks>
</default>
</users>
</yandex>

ingress:
ingressClassName: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <CLICKHOUSE_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []
Native TCP (port 9000)

The native ClickHouse TCP port cannot be exposed via Ingress. Use port-forward, LoadBalancer, or NodePort for TCP access.

After exposure, access the ClickHouse web interfaces:

  • Play UI: http://<CLICKHOUSE_HOSTNAME>/play
  • Dashboard: http://<CLICKHOUSE_HOSTNAME>/dashboard

CloudBeaver

Use tdp-cloudbeaver.ingress to publish the CloudBeaver web UI:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

tdp-cloudbeaver:
ingress:
ingressClassName: <INGRESS_CLASS>
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
hosts:
- host: <CLOUDBEAVER_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: false

Hostname examples:

  • Standard Kubernetes: cloudbeaver.tdp.local
  • OpenShift: cloudbeaver-<NAMESPACE>.apps.<CLUSTER_DOMAIN>

Kafka UI

Exposes only the Kafka UI — not the Kafka brokers:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

ingress:
ingressClassName: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <KAFKA_UI_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []

Hostname examples:

  • Standard Kubernetes: kafka-ui.tdp.local
  • OpenShift: kafka-ui-<NAMESPACE>.apps.<CLUSTER_DOMAIN>
Kafka UI ≠ Kafka brokers

Ingress publishes the Kafka UI web interface. External producers and consumers still need additional configuration to access the brokers (Kafka listeners), which is not covered by Ingress/Gateway API in this chart.

NiFi

NiFi uses ingress.rules (not ingress.hosts) as the Ingress structure, and requires session affinity annotations.

Standard Kubernetes (HTTP)

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

ingress:
enabled: true
ingressClassName: <INGRESS_CLASS>
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/affinity-mode: "persistent"
nginx.ingress.kubernetes.io/session-cookie-name: "INGRESSCOOKIE"
nginx.ingress.kubernetes.io/session-cookie-path: "/"
rules:
- host: <NIFI_HOSTNAME>
paths:
- path: /
pathType: Prefix
backend:
service:
name: tdp-nifi-service
port:
number: 8080
tls: []

Standard Kubernetes (HTTPS)

When nifiCluster.security.protocol: "https", change the annotation and port:

ingress:
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
rules:
- host: <NIFI_HOSTNAME>
paths:
- path: /
pathType: Prefix
backend:
service:
name: tdp-nifi-service
port:
number: 8443

OpenShift

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

ingress:
enabled: true
ingressClassName: openshift-default
annotations:
haproxy.router.openshift.io/balance: "cookie"
haproxy.router.openshift.io/cookie_name: "INGRESSCOOKIE"
rules:
- host: nifi-<NAMESPACE>.apps.<CLUSTER_DOMAIN>
paths:
- path: /
pathType: Prefix
backend:
service:
name: tdp-nifi-service
port:
number: 8080
tls: []

Hostname examples:

  • Standard Kubernetes: nifi.tdp.local
  • OpenShift: nifi-<NAMESPACE>.apps.<CLUSTER_DOMAIN>
Values file name

Use any name you prefer for the Ingress values file and pass it with -f <VALUES_FILE>.

OpenMetadata

For OpenMetadata, the Ingress configuration is under tdp-openmetadata.ingress.* (inside the subchart), not the root ingress.*.

Standard Kubernetes

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

tdp-openmetadata:
ingress:
className: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <OPENMETADATA_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []

OpenShift

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

tdp-openmetadata:
ingress:
className: openshift-default
annotations: {}
hosts:
- host: openmetadata-<NAMESPACE>.apps.<CLUSTER_DOMAIN>
paths:
- path: /
pathType: Prefix
tls: []

Hostname examples:

  • Standard Kubernetes: openmetadata.tdp.local
  • OpenShift: openmetadata-<NAMESPACE>.apps.<CLUSTER_DOMAIN>

Ozone

Ozone exposes four independent endpoints: S3 Gateway REST, S3 Gateway Web UI, Ozone Manager UI, and SCM UI. Each endpoint has its own Ingress block:

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

tdp-ozone:
ingress:
s3g:
rest:
enabled: true
ingressClassName: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <OZONE_S3_REST_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []
web:
enabled: true
ingressClassName: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <OZONE_S3_WEB_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []
om:
enabled: true
ingressClassName: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <OZONE_OM_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []
scm:
enabled: true
ingressClassName: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <OZONE_SCM_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []

Hostname examples:

  • Standard Kubernetes: ozone-s3.tdp.local, ozone-s3-ui.tdp.local, ozone-om.tdp.local, ozone-scm.tdp.local
  • OpenShift: ozone-s3-<NAMESPACE>.apps.<CLUSTER_DOMAIN>, ozone-s3-ui-<NAMESPACE>.apps.<CLUSTER_DOMAIN>, ozone-om-<NAMESPACE>.apps.<CLUSTER_DOMAIN>, ozone-scm-<NAMESPACE>.apps.<CLUSTER_DOMAIN>

Ranger

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

ingress:
ingressClassName: <INGRESS_CLASS>
annotations: {}
hosts:
- host: <RANGER_HOSTNAME>
paths:
- path: /
pathType: Prefix
tls: []

Hostname examples:

  • Standard Kubernetes: ranger.tdp.local
  • OpenShift: ranger-<NAMESPACE>.apps.<CLUSTER_DOMAIN>

Superset

In Superset, the Ingress configuration is nested under tdp-superset.ingress.* (inside the subchart), not under the root ingress.*.

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

tdp-superset:
ingress:
enabled: true
ingressClassName: <INGRESS_CLASS>
annotations: {}
path: /
pathType: Prefix
hosts:
- <SUPERSET_HOSTNAME>
tls: []

Hostname examples:

  • Standard Kubernetes: superset.tdp.local
  • OpenShift: superset-<NAMESPACE>.apps.<CLUSTER_DOMAIN>

Trino

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

ingress:
enabled: true
ingressClassName: <INGRESS_CLASS>
annotations: {}
path: /
pathType: Prefix
hosts:
- <TRINO_HOSTNAME>
tls: []

Hostname examples:

  • Standard Kubernetes: trino.tdp.local
  • OpenShift: trino-<NAMESPACE>.apps.<CLUSTER_DOMAIN>

Spark

Spark requires additional reverse proxy parameters for the UI to work correctly via Ingress. The configuration is under spark.ingress.* (not the root ingress.*).

TDP-Settings:
gateway:
ingress:
enabled: true
gatewayApi:
enabled: false

spark:
ingress:
enabled: true
ingressClassName: <INGRESS_CLASS>
hostname: <SPARK_HOSTNAME>
path: /
pathType: Prefix
master:
configOptions: "-Dspark.ui.reverseProxy=true -Dspark.ui.reverseProxyUrl=http://<SPARK_HOSTNAME>"
worker:
configOptions: "-Dspark.ui.reverseProxy=true -Dspark.ui.reverseProxyUrl=http://<SPARK_HOSTNAME>"
sparkConf:
"spark.ui.reverseProxy": "true"
"spark.ui.reverseProxyUrl": "http://<SPARK_HOSTNAME>"

Examples:

  • Standard Kubernetes: <INGRESS_CLASS>: nginx, <SPARK_HOSTNAME>: spark.tdp.local
  • OpenShift: <INGRESS_CLASS>: openshift-default, <SPARK_HOSTNAME>: spark-<NAMESPACE>.apps.<CLUSTER_DOMAIN>

TLS with Ingress

In the examples above, tls: [] means no TLS entry is configured. To enable HTTPS on the Ingress, replace the empty array with a list that follows the Kubernetes spec.tls format:

ingress:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
- secretName: <SECRET_NAME>
hosts:
- <COMPONENT_HOSTNAME>

Use the same format in the component-specific block when the chart does not use top-level ingress.*, for example tdp-ozone.ingress.s3g.rest.tls or tdp-openmetadata.ingress.tls.

If the TLS Secret is created manually, it must exist in the component namespace before installation or upgrade. With cert-manager, the Ingress annotation requests automatic issuance/renewal and the controller writes the Secret referenced by secretName.

Validation

Terminal input
kubectl get ingress -n <NAMESPACE>
kubectl describe ingress <INGRESS_NAME> -n <NAMESPACE>
curl -I http://<COMPONENT_HOSTNAME>

Troubleshooting

ProblemDiagnosisSolution
Ingress not createdkubectl get ingress -n <NAMESPACE> shows nothingCheck TDP-Settings.gateway.ingress.enabled: true and re-apply Helm
Incorrect ingressClassNamekubectl describe ingress shows error eventsRun kubectl get ingressclass
Hostname does not resolvecurl fails with DNS lookup errorConfigure /etc/hosts or DNS to point to the Ingress Controller IP
404 Not FoundIngress created but returns 404Check path and pathType
502 Bad GatewayIngress created but service does not respondRun kubectl get endpoints -n <NAMESPACE>
ClickHouse AUTHENTICATION_FAILEDAccess via Ingress rejectedConfigure tdp-clickhouse.clickhouse.extraUsers with <ip>::/0</ip>
Airflow providers do not loadUI opens but resources failCheck tdp-airflow.config.webserver.base_url and x_forwarded_* headers
Argo CD redirect loopUI does not load correctlyCheck server.insecure: "true" and configs.cm.url