Switch to Istio Service Mesh and Gateway

Feature Flag

Istio-native routing requires the enableModernQuiverRouting feature flag. Set it in your gdcn-values.yaml together with the istio values shown below.

When Istio is enabled in the Helm chart, GoodData.CN no longer creates Kubernetes Ingress resources for organization routing. Instead, a shared Istio Gateway and organization-specific VirtualService resources expose per-organization hostnames and route requests into the platform.

Routing rules for all GoodData.CN backend services live in a shared VirtualService delivered by the Helm chart (istio.gateway.sharedVirtualServiceName, gooddata-cn by default). Each organization’s VirtualService delegates to this shared routing definition.

This architecture enables centralized TLS handling (including wildcard certificates) while keeping per-organization hostnames isolated.

Prerequisites

  • GoodData.CN chart version 3.53.0 or later. Earlier versions do not support istio.gateway.existingGateway, including for Dex.
  • A functional Istio installation: istio-base, istiod, and an ingress gateway.
  • The Istio ingress gateway is exposed through a LoadBalancer service or an equivalent upstream, and DNS routes every organization hostname (and the Dex authHost) to it.
  • A TLS secret for the Gateway. Istio reads the credential from the namespace where the ingress gateway pods run, not from the namespace of the Gateway resource. Create the secret there.
  • If your cluster enforces sidecar injection, make sure the GoodData.CN namespaces comply with your injection policy.

Enable Istio in Helm

You can either let the chart create and manage the Gateway, or point the chart at a Gateway you manage yourself.

Option 1: Chart-managed Gateway

The chart and the organization controller create the shared Gateway and keep it in sync with your organizations:

enableModernQuiverRouting: true

istio:
  enabled: true
  gateway:
    enabled: true
    selector:
      istio: ingressgateway

istio.gateway.selector must match the labels of your Istio ingress gateway pods.

Option 2: Existing Gateway

If you manage the Gateway yourself, for example from Terraform or from the same GitOps repository that holds your Istio configuration, reference it instead:

enableModernQuiverRouting: true

istio:
  enabled: true
  gateway:
    enabled: true
    existingGateway: "istio-ingress/istio-public-gateway"

Use gateway-name for a Gateway in the release namespace, or namespace/gateway-name for a Gateway elsewhere.

Organization TLS is ignored

When istio.gateway.existingGateway is set, the chart and the organization controller do not create or manage any Gateway, and Organization.spec.tls is ignored. TLS for every organization hostname comes from the credential referenced by your Gateway, so that certificate must cover all of them.

Effects of enabling Istio, in both options:

  • A shared VirtualService defining all GoodData API routes is installed by the Helm chart.
  • The organization controller creates one VirtualService per organization, each delegating routing to the shared VirtualService.
  • No Kubernetes Ingress resources are created, and ingress.ingressClassName has no effect.

Shared Gateway Model

A single Istio Gateway handles TLS termination and accepts traffic for all organization hostnames.

Each organization receives a VirtualService that:

  • Matches its hostname.
  • References the shared Gateway.
  • Delegates routing to the shared platform VirtualService (gooddata-cn) containing the API paths and backend services.

This model is required for wildcard certificate support and consistent routing behavior.

Reference Implementation

The Terraform evaluation module deploys this setup end to end and is a useful reference for a production configuration. It:

  • Installs the base, istiod, and gateway charts at Istio 1.30.2 from https://istio-release.storage.googleapis.com/charts, with istiod in istio-system and the gateway in istio-ingress.
  • Enables native sidecars (global.proxy.nativeSidecar plus ENABLE_NATIVE_SIDECARS) so the Envoy proxy starts before init containers.
  • Creates the Gateway istio-public-gateway in istio-ingress, serving HTTPS on 443 with tls.mode: SIMPLE and credentialName: gdcn-istio-gateway-tls, plus an HTTP server on port 80 that redirects to HTTPS. Both servers list the Dex authHost and every organization hostname.
  • Points the chart at that Gateway with istio.gateway.existingGateway: "istio-ingress/istio-public-gateway".
  • Issues the Gateway certificate with cert-manager. Because HTTP-01 solving goes through Istio, it creates an IngressClass named istio with controller istio.io/ingress-controller and sets PILOT_ENABLE_K8S_INGRESS=true on istiod.
  • Labels the gooddata-cn, pulsar, and observability namespaces with istio-injection=enabled and applies a PeerAuthentication named default with mtls.mode: STRICT in each.
  • Raises the sidecar resource requests for the Pulsar ZooKeeper, BookKeeper, and broker pods (sidecar.istio.io/proxyCPU: 100m, proxyCPULimit: 1, proxyMemory: 256Mi, proxyMemoryLimit: 1536Mi). Pulsar sidecars are memory-hungry; the Istio defaults are not enough.

If your Istio control plane runs on a private network, note that the module also opens TCP 15012 (istiod XDS) and TCP 15017 (istiod webhook) from the worker nodes to the control plane.

Switching an Existing Deployment To or From Istio

Sidecars are injected at pod creation time, so changing the ingress mode on a running deployment requires a restart of every workload in the injected namespaces:

kubectl -n <namespace> rollout restart deployment
kubectl -n <namespace> rollout restart statefulset

Repeat for each injected namespace (gooddata-cn, pulsar, observability).

Also verify before and after the switch:

  • External DNS records point to the Istio ingress gateway.
  • The TLS secret exists in the ingress gateway’s namespace and is referenced by the shared Gateway.
  • Dex is reachable on its separate hostname and certificate.

TLS and Wildcard Certificate Limitations

These limitations are inherited from Envoy (Istio’s underlying proxy):

  • No overlapping wildcard and specific certificates for the same domain hierarchy:
    • For example, you cannot use both *.something.com and a separate certificate for xyz.something.com on the same Gateway at the same time.
  • Dex endpoint and wildcard certificates:
    • If you use Istio Gateway mode, Dex IdP, and a wildcard certificate, do not host Dex on a domain covered by that wildcard certificate.
    • Dex must use its own certificate on a different hostname that does not conflict with the wildcard certificate used on the shared Gateway.
  • Wildcard certificate reuse across multiple Gateways:
    • The same wildcard certificate cannot be reused across multiple different Gateway servers.
    • In practice, do not share the same wildcard certificate between “managed” and “unmanaged” endpoints or across multiple separate GoodData.CN deployments. Use a single shared Gateway per deployment or distinct certificates.

Dex on a separate hostname

When running with Istio Gateway and wildcard TLS, deploy Dex on a separate hostname with its own certificate, outside the wildcard’s scope, to avoid certificate conflicts and failed handshakes.

Examples of Created Resources

The shared Gateway, in the shape the Terraform module creates it:

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: istio-public-gateway
  namespace: istio-ingress
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 443
        name: https
        protocol: HTTPS
      tls:
        mode: SIMPLE
        credentialName: gdcn-istio-gateway-tls # secret in the gateway's namespace
      hosts:
        - "auth.example.company"
        - "*.example.company"
    - port:
        number: 80
        name: http
        protocol: HTTP
      tls:
        httpsRedirect: true
      hosts:
        - "auth.example.company"
        - "*.example.company"

One VirtualService per organization, delegating to the shared routing definition:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: org-foo-routing
  namespace: gooddata-cn
spec:
  hosts:
    - foo.example.company
  gateways:
    - istio-ingress/istio-public-gateway
  http:
    - delegate:
        name: gooddata-cn
        namespace: gooddata-cn

Notes:

  • Names and exact shapes are illustrative; the organization controller manages the per-organization resources and they may differ across versions.
  • credentialName must reference a Kubernetes secret in the namespace where the Istio ingress gateway pods run, containing a certificate valid for the served hostnames (wildcard or SANs as appropriate).

Known Limitations

  • Certificate overlap conflicts cannot be worked around at the Envoy level. Plan your hostname strategy to avoid overlaps between wildcard and single-host certificates.
  • Do not reuse the same wildcard certificate across multiple Gateways. Prefer a single shared Gateway per deployment or use distinct certificates per Gateway.
  • With istio.gateway.existingGateway set, per-organization TLS through Organization.spec.tls is unavailable.

Troubleshooting

  • TLS handshake errors on specific hostnames:
    • Check for certificate overlap (wildcard vs specific cert) and remove the conflict.
    • Confirm the credentialName secret exists in the gateway pods’ namespace and has the correct certificate chain and private key.
  • Dex login fails or loops:
    • Ensure Dex is not served through the shared wildcard certificate when Istio Gateway mode is used.
    • Verify that the Dex hostname and certificate are independent of the shared Gateway’s wildcard domain.
  • 404 or 503 on an organization hostname:
    • Confirm a VirtualService exists for the hostname and references the shared Gateway.
    • Confirm the hostname is listed in the Gateway’s servers[].hosts.
  • Pods start but never become ready after enabling Istio:
    • Confirm the namespace carries istio-injection=enabled and that the workloads were restarted after the label was added.
    • Check the sidecar memory limits on Pulsar pods.