Integrate third-party Gateway and Ingress controllers

Integrate third-party Gateway and Ingress controllers

While Istio comes with its own Gateway implementation, which can be used to control ingress and egress traffic, you may already have an alternative implementation in your cluster. This can be integrated into the mesh with a few simple steps.

In this example, you will deploy ingress-nginx. Unless otherwise noted, these steps should apply to any ingress implementation that runs as in-cluster workloads.

⚠️
Cloud load balancers cannot be added to the mesh using this method.

Set up the legacy ingress

Using Helm, install the ingress-nginx chart:

$ helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace

Apply a simple routing rule pointing to the service that you wish to expose:

$ kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: bookinfo-ingress
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: productpage
      port:
        number: 9080
EOF

In this example, the productpage deployment is already in the mesh.

If you send requests to the ingress, you will find ztunnel log output that shows a successful connection (some fields are elided):

access  connection complete     src.workload="ingress-nginx-controller-cbcf8bf58-95vh2" dst.workload="productpage-v1-dffc47f64-gdqdz" direction="inbound" 

While the requests work, you can see there is no mutual TLS between ingress-nginx and productpage. Additionally, since only the productpage pod is in the mesh, you will only see direction="inbound" logs from it (rather than an additional direction="outbound" log coming from ingress-nginx).

Add the ingress into the mesh

In order to get mTLS between the ingress and our workloads, the ingress workload can be added to the ambient mesh – just like any other workload.

$ kubectl label ns ingress-nginx istio.io/dataplane-mode=ambient

With this done, the logs now show mTLS is enabled:

access  connection complete     src.workload="ingress-nginx-controller-cbcf8bf58-95vh2" src.identity="spiffe://cluster.local/ns/ingress-nginx/sa/ingress-nginx" dst.workload="productpage-v1-dffc47f64-gdqdz" dst.identity="spiffe://cluster.local/ns/default/sa/bookinfo-productpage" direction="outbound" 

Note that the log shows a dst.workload, not a dst.service. This is because ingress-nginx does its own internal load balancing and sends traffic to specific Pods, rather than to a Service. While this is fine for many cases, if you have a waypoint attached to the destination service, it will not be used.

Configure the ingress to send to Services (optional)

If you want the ingress to send to Services, rather than Pods, you will need to configure the ingress controller itself. Since this configuration is not part of any standardized API, you will need to consult your ingress provider documentation for more information.

ingress-nginx supports this model with the nginx.ingress.kubernetes.io/service-upstream annotation. Add that to your Ingress:

$ kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: bookinfo-ingress
  annotations:
    # NEW: added to tell nginx to send to the service, rather than pod, of the backend.
    nginx.ingress.kubernetes.io/service-upstream: "true"
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: productpage
      port:
        number: 9080
EOF

Checking the logs again, you see the requests are now hitting the destination service, as indicate by the dst.service logs:

access  connection complete     src.workload="ingress-nginx-controller-cbcf8bf58-95vh2" src.identity="spiffe://cluster.local/ns/ingress-nginx/sa/ingress-nginx" dst.service="productpage.default.svc.cluster.local" dst.workload="productpage-v1-dffc47f64-gdqdz" dst.identity="spiffe://cluster.local/ns/default/sa/bookinfo-productpage" direction="outbound" 

Implementation-specific configurations

Below is an assortment of implementation-specific configuration to configure sending traffic to Services.

Implementation Configuration
ingress-nginx Set the nginx.ingress.kubernetes.io/service-upstream: "true" annotation in the Ingress resources
Emissary-Ingress no changes required; sending to Services is the default behavior
F5 NGINX Set the use-cluster-ip field in the VirtualServer configuration

Skip inbound capture (optional)

In step 2, you added the ingress workload to the mesh in order to ensure outbound traffic from the pod was a part of the mesh. However, this additionally captures inbound traffic.

While ztunnel can efficiently handle this traffic, typically there is little value to doing so, especially since ingress workloads are commonly the most performance-sensitive.

Ingress capture can be disabled. This is done by setting the ambient.istio.io/bypass-inbound-capture": "true" annotation on the ingress controller pods.

When this is enabled, only outbound traffic is captured; any inbound traffic will bypass the mesh.

It is strongly recommended to only configure this annotation if the workload exclusively receives traffic from clients that are outside of the mesh.