Reverse proxy with ingress-nginx to a non Kubernetes backend
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?
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.