select navigate esc close

Dynamic reverse proxy using nginx in Kubernetes

meain/blog ·

OK, first of all, let me make sure that you understand what we are trying to do here.

Let us say that I have a lot of kubernetes services with names like below. This list may grow or shrink dynamically and is controlled by some other script.

I am spinning up a dev instances for each use as they login. Something new I am working on.

exper-0
exper-1
exper-2
exper-3
exper-4

Now if I do not wanna create separate ingress for each item, how would I go about doing this?

Heads Up. You cannot use default nginx ingress for this.

Architecture #

Here is an overview of how we do this.

  • Create a custom nginx deployment with a specific nginx.conf injected using a configmap.
  • Create a service which will expose this custom nginx deployment
  • Define an ingress with *.mydomain.com mapped to this service

With this setup we will have exper-0.mydomain.com pointing to exper-0.

Code #

Now, going about actually doing it.

nginx.conf and configmap.yaml #

As we discussed above we need to define a custom nginx conf. This part is expained here. The essence of your nginx conf is:

 1server {
 2  listen 80;
 3
 4  server_name ~^(?<subdomain>.*?)\.;
 5  resolver kube-dns.kube-system.svc.cluster.local valid=5s;
 6
 7  location / {
 8    proxy_pass http://$subdomain.mynamespace.svc.cluster.local;
 9    proxy_set_header Host $host;
10  }
11}

Replace mynamespace with the namespace you have this thing in

Let me explain what is going on.

  • You listen on port 80. Basic.
  • For servername, you have a regex that will pull the first block of hostname to subdomain. This gets used a few lines down.
  • You have to specify a resolver as nginx has to resolve this into and actual IP(internal IP of svc).
  • Now, inside location block you grab everything and pass it over to http://$subdomain.mynamespace.svc.cluster.local which will resolve to the IP of the service.
  • You also need to change Host as otherwise it will be set as http://$subdomain.mynamespace.svc.cluster.local instead of exper-0.mydomain.com.

We actually need a bit more sutff to support websocket.

1proxy_set_header Upgrade $http_upgrade;
2proxy_set_header Connection "Upgrade";

Add in a healthcheck.

1location /healthz {
2  return 200;
3}

With all that in, it will look something like this.

 1server {
 2  listen 80;
 3
 4  server_name ~^(?<subdomain>.*?)\.;
 5  resolver kube-dns.kube-system.svc.cluster.local valid=5s;
 6
 7  location /healthz {
 8    return 200;
 9  }
10
11  location / {
12    proxy_set_header Upgrade $http_upgrade;
13    proxy_set_header Connection "Upgrade";
14    proxy_pass http://$subdomain.msce0.svc.cluster.local;
15    proxy_set_header Host $host;
16  }
17}

We will also have to add some root config as we have to write a complete nginx.conf. Pull all that together and drop it into a configmap.yaml and we have our first kubernetes object.

configmap.yaml

 1apiVersion: v1
 2kind: ConfigMap
 3metadata:
 4  name: confnginx
 5data:
 6  nginx.conf: |
 7    user  nginx;
 8    worker_processes  1;
 9    error_log  /var/log/nginx/error.log warn;
10    pid        /var/run/nginx.pid;
11    events {
12        worker_connections  1024;
13    }
14    http {
15      include       /etc/nginx/mime.types;
16      default_type  application/octet-stream;
17      log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
18                          '$status $body_bytes_sent "$http_referer" '
19                          '"$http_user_agent" "$http_x_forwarded_for"';
20      access_log  /var/log/nginx/access.log  main;
21      sendfile        on;
22      keepalive_timeout  65;
23      server {
24        listen 80;
25
26        server_name ~^(?<subdomain>.*?)\.;
27        resolver kube-dns.kube-system.svc.cluster.local valid=5s;
28
29        location /healthz {
30          return 200;
31        }
32
33        location / {
34          proxy_set_header Upgrade $http_upgrade;
35          proxy_set_header Connection "Upgrade";
36          proxy_pass http://$subdomain.msce0.svc.cluster.local;
37          proxy_set_header Host $host;
38          proxy_http_version 1.1;
39        }
40      }
41    }

deployment.yaml and service.yaml #

There is nothing fancy in here. Just a plain old deployment.yaml and service.yaml.

Inside deployment.yaml we have to make sure to load the configmap we setup. See the volumeMounts section.

deployment.yaml

 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: nginx
 5  labels:
 6    app: nginx
 7spec:
 8  selector:
 9    matchLabels:
10      app: nginx
11  replicas: 1
12  template:
13    metadata:
14      labels:
15        app: nginx
16    spec:
17      containers:
18        - name: nginx
19          image: nginx:alpine
20          ports:
21          - containerPort: 80
22          volumeMounts:
23            - name: nginx-config
24              mountPath: /etc/nginx/nginx.conf
25              subPath: nginx.conf
26      volumes:
27        - name: nginx-config
28          configMap:
29            name: confnginx

service.yaml

 1kind: Service
 2apiVersion: v1
 3metadata:
 4  name: nginx-custom
 5spec:
 6  selector:
 7    app: nginx
 8  ports:
 9  - protocol: TCP
10    port: 80
11    targetPort: 80
12    name: nginx

ingress.yaml #

Now with the service setup, we can use an ingress to give it a hostname.

ingress.yaml

 1apiVersion: networking.k8s.io/v1beta1
 2kind: Ingress
 3metadata:
 4  name: ingress-nginx-custom
 5  annotations:
 6    kubernetes.io/ingress.class: nginx
 7spec:
 8  rules:
 9  - host: '*.mydomain.com'
10    http:
11      paths:
12      - path: /
13        backend:
14          serviceName: nginx-custom
15          servicePort: 80

Deploy #

Well, I am pretty sure you know how to do this. But essentially

1k apply -f configmap.yaml
2k apply -f deployment.yaml
3k apply -f service.yaml
4k apply -f ingress.yaml

K. THX. BYE