When running an ingress controller in your Kubernetes cluster (ingress-nginx in this example) you can utilize this as a reverse proxy for services outside of Kubernetes. This is a complete reverse proxy using the ingress controller to be able to leverage things like TLS termination and Let’s Encrypt for a resource outside of Kubernetes. The outside resource can be listening on a different URL then the cluster is proxying and the proxy can do TLS termination for an external HTTP backend.

First we setup a ExternalName(docs) typed Service. This type of Service makes the in-cluster DNS return a CNAME containing the given externalName. We will make this Service the backend for our Ingress resource later.

---
apiVersion: v1
kind: Service
metadata:
  name: external-service-to-google
  namespace: default
spec:
  type: ExternalName
  externalName: google.com

When applied, this will be the result of DNS lookup in a pod on the cluster:

# nslookup external-service-to-google
Server:         10.96.0.10
Address:        10.96.0.10:53

external-service-to-google   canonical name = google.com

As you can see the in-cluster DNS now resolves the Service domain external-service-to-google with a CNAME to google.com.

To now setup the proxy we will need to create an Ingress resource which will setup the reverse proxy. Then we setup an Ingress pointing to this Service, the backend port number must be the backend port of the external backend where the ExternalName Service points to. If the backend listens on HTTPS set the nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" annotation. So for our exercise we set the annotation and the backend port to 443, the port used for HTTPS traffic since google.com is using TLS.

When the backend listens on a different host then the you will setup in the Ingress. You must set the nginx.ingress.kubernetes.io/upstream-vhost annotation, this will do a proxy_set_header Host <upstream-vhost> header rewrite on each incoming request. For SNI routing to work we must set the proxy-ssl-name annotation as well. If the server doesn’t do SNI you could omit this annotation.

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    # annotation to use cert-manager to request a Let's Encrypt certificate
    cert-manager.io/cluster-issuer: letsencrypt-production
    # Set this when the backend is listening on HTTPS
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    # Set this when the backend listens on a different URL
    nginx.ingress.kubernetes.io/upstream-vhost: www.google.com
  name: proxy-demo
  namespace: default
spec:
  rules:
    - host: proxy-demo.lansible.com
      http:
        paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: external-service-to-google
                port:
                  number: 443
  tls:
    - hosts:
        - proxy-demo.lansible.com
      secretName: proxy-demo.lansible.com

Now, as we can see in the browser https://proxy-demo.lansible.com is a full reverse proxy to google.com. Pretty cool!

Why?

Ryan Reynold scrubs gif, but why?

As stated in the intro, this can be useful to leverage automation that is already working on the Kubernetes clusters. For example the TLS termination with ingress-nginx and certificate management with cert-manager is a great example of this. No more need to manually renew or have that one hacky script to do the TLS renewal on the last VMs still in use.

Another reason could be a ‘stop-gap’ solution for apps that will move to Kubernetes but haven’t just yet (because legacy). By creating a ExternalName type Service you can communicate with these services like they are already on the cluster and resolve them by DNS instead of IP. This also gives you a single source of truth for this IP address when multiple apps connect to this application.