Disclaimer: samples provided in this post were tested both in Azure Container Services (AKS) and Kubernetes provided by Docker for Windows.
In previous posts I showed you how to Run a Precompiled .NET Core Azure Function in a Container and how to Deploy your first Service to Azure Container Services (AKS).
By now you should be able to run your own services in Kubernetes and starting to wonder about how can you give answers to questions such as:
- Is there an easy way add TLS support to my services?
- Can I add whitelisting and other nice features such as rate limits (“throtling”)?
Through this post I’ll walk you through a series of samples to show you how you can answer those questions by deploying a NGINX Ingress Controller to your Kubernetes cluster.
Prerequisites:
- Kubernetes deployed and working experience.
- kubectl installed
- helm
Expose a service with public IP
Let’s start by deploying the following service to your Kubernetes cluster, by saving the following content yaml content to a file named dni-function.yaml:
1apiVersion: apps/v1beta1
2kind: Deployment
3metadata:
4 name: dni-function
5spec:
6 replicas: 1
7 template:
8 metadata:
9 labels:
10 app: dni-function
11 spec:
12 containers:
13 - name: dni-function
14 image: cmendibl3/dni:1.0.0
15 ports:
16 - containerPort: 80
17---
18apiVersion: v1
19kind: Service
20metadata:
21 name: dni-function
22spec:
23 type: LoadBalancer
24 ports:
25 - name:
26 port: 9000
27 targetPort: 80
28 selector:
29 app: dni-function
Now deploy it to Kubernetes:
1kubectl apply -f ./dni-function.yaml
In a few seconds you’ll have a working Web API (Validates Spanish National Identification Numbers) with the service type set to LoadBalancer which means that the solution is exposed to the world using a public IP.
If you are running on localhost, you can try it executing:
1curl -k http://localhost:9000/api/validate?dni=88410248L
which should return true.
Make the service private (No public IP)
Now in order to secure the API let’s make the service private changing it’s type to ClusterIP. So update the contents of dni-function.yaml as follows:
1apiVersion: apps/v1beta1
2kind: Deployment
3metadata:
4 name: dni-function
5spec:
6 replicas: 1
7 template:
8 metadata:
9 labels:
10 app: dni-function
11 spec:
12 containers:
13 - name: dni-function
14 image: cmendibl3/dni:1.0.0
15 ports:
16 - containerPort: 80
17---
18apiVersion: v1
19kind: Service
20metadata:
21 name: dni-function
22spec:
23 type: ClusterIP
24 ports:
25 - name:
26 port: 80
27 selector:
28 app: dni-function
and redeploy:
1kubectl delete -f ./dni-function.yaml
2kubectl apply -f ./dni-function.yaml
Now you don’t have a public endpoint and therefore any attempt to query the service will result in a Service Unavailable response.
Deploy the NGINX Ingress Controller
It’s time to deploy the NGINX Ingress Controller: a daemon, deployed as a Kubernetes Pod which provides a simple yet effective way to configure features such as TLS, Whitelisting, rate limits, etc…
Let’s use Helm to deploy the ingress controller:
1helm install stable/nginx-ingress --name my-nginx `
2 --set controller.ingressClass=nginx `
3 --set controller.service.externalTrafficPolicy=Local `
4 --set controller.service.loadBalancerIP=127.0.0.1
If you test again with:
1curl -k http://localhost/api/validate?dni=88410248L
and because there is no rule specified to route the traffic from the ingress endpoint to the private service, you’ll get a response coming from a default backend!
Deploy the first Ingress Rule
Create a file named ingress_rules.yaml with the following contents and deploy:
1apiVersion: extensions/v1beta1
2kind: Ingress
3metadata:
4 name: ingress-rules
5 namespace: default
6 annotations:
7 kubernetes.io/ingress.class: nginx
8 nginx.ingress.kubernetes.io/rewrite-target: /
9spec:
10 rules:
11 - host: localhost
12 http:
13 paths:
14 - path: /
15 backend:
16 serviceName: dni-function
17 servicePort: 80
1kubectl appply -f ingress_rules.yaml
If everything is ok and you query the service again you should get a response from the Web API, so we are back to square one, but with a great difference: all traffic goes through the NGINX Ingress Controller and that fact will let us add new features to the solution.
Please note that with this rules you can use different paths to expose diferent services.
TLS
To add TLS to the service you’ll first need a certificate which you can generate with openssl. In order to speed things up save the following contents to a file named tls-secret.yaml, which provides a certificate for localhost (just for testing) and will add a secret to your Kubernetes cluster:
1apiVersion: v1
2data:
3 tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMrekNDQWVPZ0F3SUJBZ0lKQVBBVlNoS1d5NWw1TUEwR0NTcUdTSWIzRFFFQkN3VUFNQlF4RWpBUUJnTlYKQkFNTUNXeHZZMkZzYUc5emREQWVGdzB4T0RBeU1qVXhOelUzTURSYUZ3MHhPVEF5TWpVeE56VTNNRFJhTUJReApFakFRQmdOVkJBTU1DV3h2WTJGc2FHOXpkRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DCmdnRUJBT012c25IOEhmT2dSSnE5UDRLdGRudnNkWEtaa0tYMHpNclpNdURHWnppeXFucEMwVzdwRVZOV0lmK2kKcFNpcGU2RWNBeDlWakNmUWxZbHJuNnZBLzFLUTlLRW84SURicUF3ZTloVGFLK2dRV21IblJGaFhFcCs5ZmxqbQptcVRQS3JHNEgydnlZTWQwYnpacUlJQUZHMTV3NzZLODZNRzhxVUR0V0NRNWxvWFNBZUtUVzcwSVNMSXVNZ016CklpV2lsVjB4Ly9MS2RHZDJKRi9wZVVBUGhqT05SNUJ2cHArUXlhQ3gvTDNFaGFuWE56amZXUi8zZ084SUZwN0IKcmhpcWwzWWNCZHJES2xTaE8raTFDY3FnbGZUdEdkaXVENWZHYXB3QUQ5eVNwV081bTFSR1NTaHZmTktHR05XVApOeDczemQ4b0FMckFHbHR6K2I4VjlSaTMrNThDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRlB2S1BFT0U2VUFnCnVLTFA5QjRDczkzc2d3SnRNQjhHQTFVZEl3UVlNQmFBRlB2S1BFT0U2VUFndUtMUDlCNENzOTNzZ3dKdE1Bd0cKQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDYVM0eTFZMnVjdzRsOEJMVGJ0NW9QVApnQXpKSW5iYTVET3NWZElEcmZEa0FOOUJhcVh2Y011dzlmN0xYc0dlenNiMmdCSm1GbllpcUxLcTBYdmVBU2x2ClJoS0R5OVJhTThpeTV0eVdVZEJXSzJFZWtvUEJHOFhMVHhScnorbEpoQmpMNXhTS3dSMDN3MW45OEVQbWRFOUkKTS90OWdHKzdZYnZyaVZhdTFINURiZWtTZld0L2pIZG9UU2hwNEJFTVNxelJSaHpqbmNUamZoR0J2KzR2cGFCRAo1aHZ0WHFsekZHODdRR05LQWdHK01hdFFLY1JCTFppNndkRDk4djdMV2NROGhydWxGT2lxeXNmNDh3bzZodEhyCm14S3BnVWpHbE52dVdKZmhNWTZCdlkyeFlveHB2R0d3Z1hnNWsxQUFldzNjK0RRTWpUUjU3dnVTZ2tnTGZVaz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
4 tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRRGpMN0p4L0Izem9FU2EKdlQrQ3JYWjc3SFZ5bVpDbDlNeksyVExneG1jNHNxcDZRdEZ1NlJGVFZpSC9vcVVvcVh1aEhBTWZWWXduMEpXSgphNStyd1A5U2tQU2hLUENBMjZnTUh2WVUyaXZvRUZwaDUwUllWeEtmdlg1WTVwcWt6eXF4dUI5cjhtREhkRzgyCmFpQ0FCUnRlY08raXZPakJ2S2xBN1Zna09aYUYwZ0hpazF1OUNFaXlMaklETXlJbG9wVmRNZi95eW5SbmRpUmYKNlhsQUQ0WXpqVWVRYjZhZmtNbWdzZnk5eElXcDF6YzQzMWtmOTREdkNCYWV3YTRZcXBkMkhBWGF3eXBVb1R2bwp0UW5Lb0pYMDdSbllyZytYeG1xY0FBL2NrcVZqdVp0VVJra29iM3pTaGhqVmt6Y2U5ODNmS0FDNndCcGJjL20vCkZmVVl0L3VmQWdNQkFBRUNnZ0VCQUpqbEhza0xqZlRLSmFHbVA3bm9sOWJxMmxnWDlYdGE5d0NGa0hJcDFJb1oKNUJXSUpuN29LQnJYMnVXNlJrREpYMFNjSDVYVTh4QlFsbkwzbFd2MzVWMWg1T0VaTmxMaWdZUTJ5aEphaWpZUgoyMklNVExqUFVOOWtua1dpWE8wUjUzL1hsSDRIanc1czAvUGhGS0pUelltUHBCYjM0QVdTdkszUGpnUkRKWVJFCi9ybEF0SzZSaHFKNThBS0dPOGp3OEd5WEJhTzJRVkVJSTVVdUM1QTBhTGMvUjJxS1ZpdzJ6bGJ6TVRNUVpKc2QKVjRubklYS29oL1R6WWZjdk90OVZKOTRqWlRmSGtRdHBHbzgwbFVhNGtFMXY2b2gyZG5GcThtZ3M5a3ZDV2ZLSwpTTlVhNkU2UGlMenFzR01VRE85enVHRmpUQm9xMDdBamNSVHMvS1NaOEdFQ2dZRUEvUGlkTUtGQnRaMHp6TnpOClB0Q1FrMzZkZ3VMZ2xhSVl5Qk5JSXl5SG84WW5wS3dONWJ1ZjM2ZktIWmZMeUt3SnNFMkhiNWQ0aXI1RzNFaVAKYzBhYjJFQnpQMTdyaUVVb2htVkJZTUNjY1ZlMi9qWnduZ2ducjBpcFEvc3ZqSFdrYlBTcFozam9ySjV4Z1owYQpXM21yN0U1Mi85THQxTGFYeXdRMkIydDAvR2tDZ1lFQTVlZ01yVzIzMlIyS3NrQVRDWDV3UDNwYk9SUkJkaEU5Cmh0a2JDTXc0ZDFHU1hBOElWSEVKRkZVdkFjODhiY0ZlckxXQzZkd0V1N2pTRnUyRUwwcEdKWmFTQ2NoTllrUkIKQ1F1MFhOdDBzUzRoRzhoUFRiejZROVZ6RzBjVm91RUlsdlVEc0dCU3pOV2FYck5lSDY5MktPMWM5akd4RzVITgozTC9VdzM2STFzY0NnWUJYMUhHdkNxM253bmJUciszSzIxcjIrc1R4UnBnM0c1cURETDdGQjVib2M4b2IwR2phCjFIUERrVndKUGtUUW5YcVhyYk5TT1VMdTJQVjlVZXdNVi8yUDdZQ1dCZnk4eVZZeW8wRTV1R1lZckIycTBYZjAKUmx5UTdTZG5wUFJ6VGYwU256ZVo1MDdSY0FsMHVQa0h2WXpGZE5DNExhSEpjc1B0QnI5RGdEbVQwUUtCZ0NDbQpPcDZxZFRCdEpKUTV5enBPN1d2bVdXd2F0MDBvRjUrOTF6d0JuSWM5VzFhZGYrWldBeDhURmREZytFang3QnNFCnorbWNLRVBzZEZGek81Rm5yOXlJcklhZEhuZzFEek5VcVRHQ3JPaTRqMVVkdGoxbzkvV0lLNGVWS2JwdTBNUjMKV1NYRUdCNGt1MzUxWkltRlpuZGJkaGMwYVYxcjhGdElGdFFJZFRCakFvR0JBSXV1dGFzQjFFT2dUZUVtTGJqYgpKdWVlZVRtSUQvWHpLMldpSE1ydG5HOHN0TWF0ai9VWjZUcTJMQnh4SkJqdVRFOHF5dUtYeG1KTmUxSnRkbUdKClU2bkZmWXo4OW1QSmlWK2dtSWU1L1VNNGJRbnB2SFNJdEtyNHFCREgvTkY0Z21xdGQ3MDlnTm5XRS9jdk1sQWkKQndYNWh2TzFWdndzZmRNeE0rSGIwVUxPCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
5kind: Secret
6metadata:
7 name: tls-secret
8 namespace: default
9type: Opaque
Now modify the ingress_rules.yaml from previous steps to include the tls section as shown below:
1apiVersion: extensions/v1beta1
2kind: Ingress
3metadata:
4 name: ingress-rules
5 namespace: default
6 annotations:
7 kubernetes.io/ingress.class: nginx
8 nginx.ingress.kubernetes.io/rewrite-target: /
9spec:
10 tls:
11 - hosts:
12 - localhost
13 secretName: tls-secret
14 rules:
15 - host: localhost
16 http:
17 paths:
18 - path: /
19 backend:
20 serviceName: dni-function
21 servicePort: 80
Deploy both files:
1kubectl apply -f ./tls-secret.yaml
2kubectl apply -f ./ingress_rules.yaml
In this new state, if you query the service you should get redirects to the TLS endpoint, so try:
1https://localhost/api/validate?dni=88410248L;
and yes! the service is responding and only accesible through the TLS endpoint!
Whitelisting
To restrict the service in a way that only a list of IPs can access it, modify the ingress_rules.yaml to add the whitelist-source-range annotation:
1apiVersion: extensions/v1beta1
2kind: Ingress
3metadata:
4 name: ingress-rules
5 namespace: default
6 annotations:
7 kubernetes.io/ingress.class: nginx
8 nginx.ingress.kubernetes.io/whitelist-source-range: '192.168.65.3/32'
9 nginx.ingress.kubernetes.io/rewrite-target: /
10spec:
11 tls:
12 - hosts:
13 - localhost
14 secretName: tls-secret
15 rules:
16 - host: localhost
17 http:
18 paths:
19 - path: /
20 backend:
21 serviceName: dni-function
22 servicePort: 80
and deploy:
1kubectl apply -f ./ingress_rules.yaml
Feel free to try different ranges and understand how you can block or enable access to your service.
Rate limits
Now it’s time to protect the service applying some kind of throtling. Modify the ingress_rules.yaml to add the limit-connections and limit-rps annotations:
1apiVersion: extensions/v1beta1
2kind: Ingress
3metadata:
4 name: ingress-rules
5 namespace: default
6 annotations:
7 kubernetes.io/ingress.class: nginx
8 nginx.ingress.kubernetes.io/whitelist-source-range: '192.168.65.3/32'
9 nginx.ingress.kubernetes.io/limit-connections: '10'
10 nginx.ingress.kubernetes.io/limit-rps: '1'
11 nginx.ingress.kubernetes.io/rewrite-target: /
12spec:
13 tls:
14 - hosts:
15 - localhost
16 secretName: tls-secret
17 rules:
18 - host: localhost
19 http:
20 paths:
21 - path: /
22 backend:
23 serviceName: dni-function
24 servicePort: 80
and deploy:
1kubectl apply -f ./ingress_rules.yaml
You just limited the number of concurrent connections from a given client to 10 and it’s allowed number of requests per second to 1. You see the power here don’t you?
That one wraps it up for today. Hope you enjoyed the ride!
Please download all code and files here and be sure to check the online documentation to learn more about the annotations and available features.
Comments