Dynamic reverse proxy using nginx in Kubernetes
Follow into
Save into
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 ingressfor this.
Architecture #
Here is an overview of how we do this.
- Create a custom nginx deployment with a specific
nginx.confinjected using aconfigmap. - Create a service which will expose this custom nginx deployment
- Define an ingress with
*.mydomain.commapped 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.localwhich will resolve to the IP of the service. - You also need to change
Hostas otherwise it will be set ashttp://$subdomain.mynamespace.svc.cluster.localinstead ofexper-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.yamlwe have to make sure to load theconfigmapwe setup. See thevolumeMountssection.
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