Spring - Traefik (Rate Limit)

Overview

Application deployed on kubernetes, configured with Traefik ingress controller to rate limit.

Github: https://github.com/gitorko/project95

Traefik

Traefik is a reverse proxy and load balancer that makes deploying microservices easy.

We will deploy the spring rest application along with postgres db on kubernetes instance. Then we will configure Traefik as ingress controller and apply rate limit on it using Traefik Proxy Middleware. We will use docker desktop kubernetes instance.

Rate limiting is a technique for controlling the rate of requests to your application. It can save you from Denial-of-Service (DoS) or resource starvation problems. Without rate limits, a burst of traffic could bring down the whole service making it unavailable for everybody.

Code

  1apiVersion: apps/v1
  2kind: Deployment
  3metadata:
  4  name: project95
  5spec:
  6  selector:
  7      matchLabels:
  8        app: project95
  9  strategy:
 10    rollingUpdate:
 11      maxSurge: 1
 12      maxUnavailable: 1
 13    type: RollingUpdate
 14  replicas: 1
 15  template:
 16    metadata:
 17      labels:
 18        app: project95
 19    spec:
 20      containers:
 21        - name: project95
 22          image: project95:1.0.0
 23          imagePullPolicy: IfNotPresent
 24          ports:
 25            - containerPort: 8080
 26          resources:
 27            limits:
 28              cpu: "1"
 29              memory: "500Mi"
 30
 31---
 32apiVersion: v1
 33kind: ConfigMap
 34metadata:
 35  name: postgres-config
 36  labels:
 37    app: postgres
 38data:
 39  POSTGRES_DB: test-db
 40  POSTGRES_USER: test
 41  POSTGRES_PASSWORD: test@123
 42---
 43apiVersion: v1
 44kind: PersistentVolume
 45metadata:
 46  name: postgres-pv-volume
 47  labels:
 48    type: local
 49    app: postgres
 50spec:
 51  storageClassName: manual
 52  capacity:
 53    storage: 5Gi
 54  accessModes:
 55    - ReadWriteMany
 56  hostPath:
 57    path: "/tmp/data"
 58---
 59apiVersion: v1
 60kind: PersistentVolumeClaim
 61metadata:
 62  name: postgres-pv-claim
 63  labels:
 64    app: postgres
 65spec:
 66  storageClassName: manual
 67  accessModes:
 68    - ReadWriteMany
 69  resources:
 70    requests:
 71      storage: 5Gi
 72---
 73apiVersion: apps/v1
 74kind: Deployment
 75metadata:
 76  name: db-server
 77spec:
 78  replicas: 1
 79  template:
 80    metadata:
 81      labels:
 82        app: db-server
 83    spec:
 84      containers:
 85        - name: db-server
 86          image: postgres:9.6.10
 87          imagePullPolicy: "IfNotPresent"
 88          ports:
 89            - containerPort: 5432
 90          envFrom:
 91            - configMapRef:
 92                name: postgres-config
 93          volumeMounts:
 94            - mountPath: /var/lib/postgresql/data
 95              name: postgredb
 96      volumes:
 97        - name: postgredb
 98          persistentVolumeClaim:
 99            claimName: postgres-pv-claim
100  selector:
101    matchLabels:
102      app: db-server
103---
104apiVersion: v1
105kind: Service
106metadata:
107  name: db-server
108  labels:
109    app: db-server
110spec:
111  type: NodePort
112  ports:
113    - port: 5432
114  selector:
115    app: db-server
116---
117kind: Service
118apiVersion: v1
119metadata:
120  name: project95
121spec:
122  ports:
123  - port: 8080
124    targetPort: 8080
125    name: http
126  selector:
127    app: project95
128  type: LoadBalancer
  1apiVersion: apps/v1
  2kind: Deployment
  3metadata:
  4  name: project95
  5  labels:
  6    app: project95
  7spec:
  8  selector:
  9      matchLabels:
 10        app: project95
 11  strategy:
 12    rollingUpdate:
 13      maxSurge: 1
 14      maxUnavailable: 1
 15    type: RollingUpdate
 16  replicas: 1
 17  template:
 18    metadata:
 19      labels:
 20        app: project95
 21    spec:
 22      containers:
 23        - name: project95
 24          image: gitorko/project95:1.0.0
 25          imagePullPolicy: IfNotPresent
 26          ports:
 27            - containerPort: 8080
 28          resources:
 29            limits:
 30              cpu: "1"
 31              memory: "500Mi"
 32
 33---
 34apiVersion: v1
 35kind: ConfigMap
 36metadata:
 37  name: postgres-config
 38  labels:
 39    app: postgres
 40data:
 41  POSTGRES_DB: test-db
 42  POSTGRES_USER: test
 43  POSTGRES_PASSWORD: test@123
 44---
 45apiVersion: v1
 46kind: PersistentVolume
 47metadata:
 48  name: postgres-pv-volume
 49  labels:
 50    type: local
 51    app: postgres
 52spec:
 53  storageClassName: manual
 54  capacity:
 55    storage: 5Gi
 56  accessModes:
 57    - ReadWriteMany
 58  hostPath:
 59    path: "/tmp/data"
 60---
 61apiVersion: v1
 62kind: PersistentVolumeClaim
 63metadata:
 64  name: postgres-pv-claim
 65  labels:
 66    app: postgres
 67spec:
 68  storageClassName: manual
 69  accessModes:
 70    - ReadWriteMany
 71  resources:
 72    requests:
 73      storage: 5Gi
 74---
 75apiVersion: apps/v1
 76kind: Deployment
 77metadata:
 78  name: db-server
 79spec:
 80  replicas: 1
 81  template:
 82    metadata:
 83      labels:
 84        app: db-server
 85    spec:
 86      containers:
 87        - name: db-server
 88          image: postgres:9.6.10
 89          imagePullPolicy: "IfNotPresent"
 90          ports:
 91            - containerPort: 5432
 92          envFrom:
 93            - configMapRef:
 94                name: postgres-config
 95          volumeMounts:
 96            - mountPath: /var/lib/postgresql/data
 97              name: postgredb
 98      volumes:
 99        - name: postgredb
100          persistentVolumeClaim:
101            claimName: postgres-pv-claim
102  selector:
103    matchLabels:
104      app: db-server
105---
106apiVersion: v1
107kind: Service
108metadata:
109  name: db-server
110  labels:
111    app: db-server
112spec:
113  type: NodePort
114  ports:
115    - port: 5432
116  selector:
117    app: db-server
118---
119apiVersion: v1
120kind: Service
121metadata:
122  name: project95
123  labels:
124    app: project95
125spec:
126  type: ClusterIP
127  ports:
128    - port: 8080
129  selector:
130    app: project95
131---
132apiVersion: networking.k8s.io/v1
133kind: Ingress
134metadata:
135  name: my-ingress
136  annotations:
137    kubernetes.io/ingress.class: "traefik"
138spec:
139  rules:
140    - host: localhost.com
141      http:
142        paths:
143          - path: /rest
144            pathType: Prefix
145            backend:
146              service:
147                name:  project95
148                port:
149                  number: 8080
150---
  1apiVersion: apps/v1
  2kind: Deployment
  3metadata:
  4  name: project95
  5  labels:
  6    app: project95
  7spec:
  8  selector:
  9      matchLabels:
 10        app: project95
 11  strategy:
 12    rollingUpdate:
 13      maxSurge: 1
 14      maxUnavailable: 1
 15    type: RollingUpdate
 16  replicas: 1
 17  template:
 18    metadata:
 19      labels:
 20        app: project95
 21    spec:
 22      containers:
 23        - name: project95
 24          image: gitorko/project95:1.0.0
 25          imagePullPolicy: IfNotPresent
 26          ports:
 27            - containerPort: 8080
 28          resources:
 29            limits:
 30              cpu: "1"
 31              memory: "500Mi"
 32
 33---
 34apiVersion: v1
 35kind: ConfigMap
 36metadata:
 37  name: postgres-config
 38  labels:
 39    app: postgres
 40data:
 41  POSTGRES_DB: test-db
 42  POSTGRES_USER: test
 43  POSTGRES_PASSWORD: test@123
 44---
 45apiVersion: v1
 46kind: PersistentVolume
 47metadata:
 48  name: postgres-pv-volume
 49  labels:
 50    type: local
 51    app: postgres
 52spec:
 53  storageClassName: manual
 54  capacity:
 55    storage: 5Gi
 56  accessModes:
 57    - ReadWriteMany
 58  hostPath:
 59    path: "/tmp/data"
 60---
 61apiVersion: v1
 62kind: PersistentVolumeClaim
 63metadata:
 64  name: postgres-pv-claim
 65  labels:
 66    app: postgres
 67spec:
 68  storageClassName: manual
 69  accessModes:
 70    - ReadWriteMany
 71  resources:
 72    requests:
 73      storage: 5Gi
 74---
 75apiVersion: apps/v1
 76kind: Deployment
 77metadata:
 78  name: db-server
 79spec:
 80  replicas: 1
 81  template:
 82    metadata:
 83      labels:
 84        app: db-server
 85    spec:
 86      containers:
 87        - name: db-server
 88          image: postgres:9.6.10
 89          imagePullPolicy: "IfNotPresent"
 90          ports:
 91            - containerPort: 5432
 92          envFrom:
 93            - configMapRef:
 94                name: postgres-config
 95          volumeMounts:
 96            - mountPath: /var/lib/postgresql/data
 97              name: postgredb
 98      volumes:
 99        - name: postgredb
100          persistentVolumeClaim:
101            claimName: postgres-pv-claim
102  selector:
103    matchLabels:
104      app: db-server
105---
106apiVersion: v1
107kind: Service
108metadata:
109  name: db-server
110  labels:
111    app: db-server
112spec:
113  type: NodePort
114  ports:
115    - port: 5432
116  selector:
117    app: db-server
118---
119apiVersion: v1
120kind: Service
121metadata:
122  name: project95
123  labels:
124    app: project95
125spec:
126  type: ClusterIP
127  ports:
128    - port: 8080
129  selector:
130    app: project95
131---
132apiVersion: traefik.containo.us/v1alpha1
133kind: Middleware
134metadata:
135  name: ratelimiter
136spec:
137  rateLimit:
138    average: 3
139    burst: 5
140---
141apiVersion: traefik.containo.us/v1alpha1
142kind: IngressRoute
143metadata:
144  name: myingressroute
145spec:
146  entryPoints:
147    - web
148  routes:
149    - match: Host(`localhost.com`) && PathPrefix(`/rest`)
150      kind: Rule
151      services:
152        - kind: Service
153          name: project95
154          port: 8080
155      middlewares:
156        - name: ratelimiter

Setup

  1# Project 95
  2
  3Traefik Rate Limit
  4
  5[https://gitorko.github.io/spring-boot-traefik-rate-limit/](https://gitorko.github.io/spring-boot-traefik-rate-limit/)
  6
  7### Version
  8
  9Check version
 10
 11```bash
 12$java --version
 13openjdk version "21.0.3" 2024-04-16 LTS
 14
 15helm version --short
 16v3.9.1+ga7c043a
 17
 18kubectl version --short
 19Client Version: v1.24.3
 20Kustomize Version: v4.5.4
 21```
 22
 23### Postgres DB
 24
 25```
 26docker run -p 5432:5432 --name pg-container -e POSTGRES_PASSWORD=password -d postgres:9.6.10
 27docker ps
 28docker exec -it pg-container psql -U postgres -W postgres
 29CREATE USER test WITH PASSWORD 'test@123';
 30CREATE DATABASE "test-db" WITH OWNER "test" ENCODING UTF8 TEMPLATE template0;
 31grant all PRIVILEGES ON DATABASE "test-db" to test;
 32
 33docker stop pg-container
 34docker start pg-container
 35```
 36
 37### Docker
 38
 39For docker on laptop we cant use localhost as the hostname, so add this entry to the /etc/hosts file.
 40
 41```bash
 42127.0.0.1 localhost.com
 43```
 44
 45Build the project and docker image
 46
 47```bash
 48cd project95
 49./gradlew bootRun
 50./gradlew clean build
 51docker build -f docker/Dockerfile --force-rm -t project95:1.0.0 .
 52```
 53
 54If you want to deploy via docker compose. 
 55
 56```bash
 57docker tag project95:1.0.0 gitorko/project95:1.0.0
 58docker push gitorko/project95:1.0.0
 59docker-compose -f docker/docker-compose.yml up 
 60```
 61
 62### Traefik
 63
 64Deploy traefik via helm
 65
 66```bash
 67helm install traefik traefik/traefik
 68```
 69
 70Traefik comes with the dashboard to visualize the config that is not exposed so run port forward command. If you dont need to visualize the config then you can skip this step as it is not mandatory
 71
 72```bash
 73kubectl port-forward $(kubectl get pods --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000
 74```
 75
 76Open the dashboard url
 77
 78[http://127.0.0.1:9000/dashboard/](http://127.0.0.1:9000/dashboard/)
 79
 80### Kubernetes
 81
 82Now deploy the application on kubernetes
 83
 84If you want a plain deployment without traefik, This will deploy the spring boot application along with postgres, run the below command
 85
 86```bash
 87kubectl apply -f docker/deployment.yaml
 88```
 89
 90To test the api, run the curl command
 91
 92```bash
 93curl --request GET 'http://localhost.com:8080/rest/time'
 94```
 95
 96Clean up
 97
 98```bash
 99kubectl delete -f docker/deployment.yaml
100```
101
102### Kubernetes & Traefik Ingress
103
104If you want traefik as the ingress controller, run the below command
105
106```bash
107kubectl apply -f docker/deployment-traefik.yaml
108```
109
110To test the api, run the curl command
111
112```bash
113curl --request GET 'http://localhost.com/rest/time'
114```
115
116Clean up
117
118```bash
119kubectl delete -f docker/deployment-traefik.yaml
120```
121
122### Kubernetes & Traefik IngressRoute with Rate Limit
123
124If you want traefik as the ingress & want to rate limit, run the below command
125
126```bash
127kubectl apply -f docker/deployment-traefik-ratelimit.yaml
128```
129
130To test the api, run the curl command
131
132```bash
133curl --request GET 'http://localhost.com/rest/time'
134```
135
136Clean up
137
138```bash
139kubectl delete -f docker/deployment-traefik-ratelimit.yaml
140```
141
142Few command to look at the services
143
144```bash
145kubectl get ingress
146kubectl describe ingress
147
148kubectl get ingressroute 
149kubectl describe ingressroute 
150
151kubectl get all
152
153k logs -f deployment.apps/project95 --all-containers=true
154
155helm uninstall traefik
156```

Testing

Deploy the image to kubernetes

The dashboard will show the HTTP Routers & the middleware rate limit config

You can also look at success rate

To test the rate limit functionality open the RateLimit.jmx file in JMeter and run the test

Create a user, the data is persisted in the postgres db.

1curl --request POST 'http://localhost.com/rest/customer' \
2--header 'Content-Type: application/json' \
3--data-raw '{
4    "firstName" : "John",
5    "lastName" : "Doe",
6    "city": "NY"
7}'

Get the user

1curl --request GET 'http://localhost.com/rest/customer'

References

https://traefik.io/

comments powered by Disqus