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

Project 95

Traefik Rate Limit

https://gitorko.github.io/spring-boot-traefik-rate-limit/

Version

Check version

1$java --version
2openjdk 17.0.3 2022-04-19 LTS
3
4helm version --short
5v3.9.1+ga7c043a
6
7kubectl version --short
8Client Version: v1.24.3
9Kustomize Version: v4.5.4

Postgres DB

1docker run -p 5432:5432 --name pg-container -e POSTGRES_PASSWORD=password -d postgres:9.6.10
2docker ps
3docker exec -it pg-container psql -U postgres -W postgres
4CREATE USER test WITH PASSWORD 'test@123';
5CREATE DATABASE "test-db" WITH OWNER "test" ENCODING UTF8 TEMPLATE template0;
6grant all PRIVILEGES ON DATABASE "test-db" to test;
7
8docker stop pg-container
9docker start pg-container

Docker

For docker on laptop we cant use localhost as the hostname, so add this entry to the /etc/hosts file.

1127.0.0.1 localhost.com

Build the project and docker image

1cd project95
2./gradlew bootRun
3./gradlew clean build
4docker build -f docker/Dockerfile --force-rm -t project95:1.0.0 .

If you want to deploy via docker compose.

1docker tag project95:1.0.0 gitorko/project95:1.0.0
2docker push gitorko/project95:1.0.0
3docker-compose -f docker/docker-compose.yml up 

Traefik

Deploy traefik via helm

1helm install traefik traefik/traefik

Traefik 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

1kubectl port-forward $(kubectl get pods --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000

Open the dashboard url

http://127.0.0.1:9000/dashboard/

Kubernetes

Now deploy the application on kubernetes

If you want a plain deployment without traefik, This will deploy the spring boot application along with postgres, run the below command

1kubectl apply -f docker/deployment.yaml

To test the api, run the curl command

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

Clean up

1kubectl delete -f docker/deployment.yaml

Kubernetes & Traefik Ingress

If you want traefik as the ingress controller, run the below command

1kubectl apply -f docker/deployment-traefik.yaml

To test the api, run the curl command

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

Clean up

1kubectl delete -f docker/deployment-traefik.yaml

Kubernetes & Traefik IngressRoute with Rate Limit

If you want traefik as the ingress & want to rate limit, run the below command

1kubectl apply -f docker/deployment-traefik-ratelimit.yaml

To test the api, run the curl command

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

Clean up

1kubectl delete -f docker/deployment-traefik-ratelimit.yaml

Few command to look at the services

 1kubectl get ingress
 2kubectl describe ingress
 3
 4kubectl get ingressroute 
 5kubectl describe ingressroute 
 6
 7kubectl get all
 8
 9k logs -f deployment.apps/project95 --all-containers=true
10
11helm uninstall traefik

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