Skip to content

Commit 761bab3

Browse files
committed
Implement deployemnts using docker and k8s
1 parent 3a2f791 commit 761bab3

7 files changed

Lines changed: 208 additions & 6 deletions

File tree

Dockerfile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Stage 1: Build
2+
FROM golang:1.26-alpine AS builder
3+
4+
WORKDIR /app
5+
6+
# Copy go modules files and download deps
7+
COPY go.mod go.sum ./
8+
RUN go mod download
9+
10+
# Copy source code
11+
COPY . .
12+
13+
# Build binary
14+
RUN go build -o rate-limiter ./cmd/server
15+
16+
# Stage 2: Runtime
17+
FROM alpine:3.18
18+
19+
WORKDIR /app
20+
21+
# Copy built binary
22+
COPY --from=builder /app/rate-limiter .
23+
24+
# Expose port
25+
EXPOSE 8080
26+
27+
# Run the service
28+
CMD ["./rate-limiter"]

deploy/k8s/configmap.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,12 @@
1-
// deploy/k8s/configmap.yaml
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: rate-limiter-config
5+
data:
6+
# Default policy: 100 requests per minute
7+
DEFAULT_LIMIT: "100"
8+
DEFAULT_WINDOW: "60s"
9+
10+
# Example per-key overrides (extend in application config as needed)
11+
# POLICY_premium-key-001: "1000/60s"
12+
# POLICY_free-tier-key: "10/60s"

deploy/k8s/deployment.yaml

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,63 @@
1-
// deploy/k8s/deployment.yaml
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: rate-limiter
5+
labels:
6+
app: rate-limiter
7+
spec:
8+
replicas: 3
9+
selector:
10+
matchLabels:
11+
app: rate-limiter
12+
strategy:
13+
type: RollingUpdate
14+
rollingUpdate:
15+
maxUnavailable: 1 # at most 1 pod down during rollout
16+
maxSurge: 1 # at most 1 extra pod during rollout
17+
template:
18+
metadata:
19+
labels:
20+
app: rate-limiter
21+
annotations:
22+
# Prometheus scrape annotations — picked up by kube-prometheus-stack
23+
prometheus.io/scrape: "true"
24+
prometheus.io/port: "8080"
25+
prometheus.io/path: "/metrics"
26+
spec:
27+
containers:
28+
- name: rate-limiter
29+
image: rate-limiter:latest
30+
imagePullPolicy: IfNotPresent
31+
ports:
32+
- name: grpc
33+
containerPort: 50051
34+
- name: http
35+
containerPort: 8080
36+
env:
37+
- name: REDIS_ADDR
38+
value: "redis:6379"
39+
- name: GRPC_ADDR
40+
value: ":50051"
41+
- name: HTTP_ADDR
42+
value: ":8080"
43+
readinessProbe:
44+
httpGet:
45+
path: /metrics
46+
port: 8080
47+
initialDelaySeconds: 5
48+
periodSeconds: 5
49+
failureThreshold: 3
50+
livenessProbe:
51+
httpGet:
52+
path: /metrics
53+
port: 8080
54+
initialDelaySeconds: 10
55+
periodSeconds: 10
56+
failureThreshold: 3
57+
resources:
58+
requests:
59+
cpu: "100m"
60+
memory: "64Mi"
61+
limits:
62+
cpu: "500m"
63+
memory: "128Mi"

deploy/k8s/hpa.yaml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,28 @@
1-
// deploy/k8s/hpa.yaml
1+
# Horizontal Pod Autoscaler
2+
#
3+
# Scales replicas between 3 and 10 based on CPU utilisation.
4+
# Safe to scale horizontally because:
5+
# - service replicas are stateless (no in-process rate-limit state)
6+
# - all decisions are serialised through Redis (shared source of truth)
7+
# See docs/tradeoffs.md — "Stateless replicas" section.
8+
apiVersion: autoscaling/v2
9+
kind: HorizontalPodAutoscaler
10+
metadata:
11+
name: rate-limiter-hpa
12+
spec:
13+
scaleTargetRef:
14+
apiVersion: apps/v1
15+
kind: Deployment
16+
name: rate-limiter
17+
minReplicas: 3
18+
maxReplicas: 10
19+
metrics:
20+
- type: Resource
21+
resource:
22+
name: cpu
23+
target:
24+
type: Utilization
25+
averageUtilization: 70
26+
behavior:
27+
scaleDown:
28+
stabilizationWindowSeconds: 60 # avoid flapping on short traffic spikes

deploy/k8s/redis.yaml

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,57 @@
1-
// deploy/k8s/redis.yaml
1+
apiVersion: apps/v1
2+
kind: StatefulSet
3+
metadata:
4+
name: redis
5+
spec:
6+
serviceName: redis
7+
replicas: 1
8+
selector:
9+
matchLabels:
10+
app: redis
11+
template:
12+
metadata:
13+
labels:
14+
app: redis
15+
spec:
16+
containers:
17+
- name: redis
18+
image: redis:7-alpine
19+
imagePullPolicy: IfNotPresent
20+
ports:
21+
- containerPort: 6379
22+
command: ["redis-server", "--appendonly", "yes", "--save", "60", "1"]
23+
volumeMounts:
24+
- name: redis-data
25+
mountPath: /data
26+
resources:
27+
requests:
28+
cpu: "100m"
29+
memory: "128Mi"
30+
limits:
31+
cpu: "500m"
32+
memory: "256Mi"
33+
readinessProbe:
34+
exec:
35+
command: ["redis-cli", "ping"]
36+
initialDelaySeconds: 5
37+
periodSeconds: 5
38+
volumeClaimTemplates:
39+
- metadata:
40+
name: redis-data
41+
spec:
42+
accessModes: ["ReadWriteOnce"]
43+
resources:
44+
requests:
45+
storage: 1Gi
46+
---
47+
apiVersion: v1
48+
kind: Service
49+
metadata:
50+
name: redis
51+
spec:
52+
selector:
53+
app: redis
54+
ports:
55+
- port: 6379
56+
targetPort: 6379
57+
clusterIP: None # headless — allows StatefulSet DNS: redis-0.redis.default.svc.cluster.local

deploy/k8s/service.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
1-
// deploy/k8s/service.yaml
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: rate-limiter
5+
spec:
6+
selector:
7+
app: rate-limiter
8+
ports:
9+
- name: grpc
10+
port: 50051
11+
targetPort: 50051
12+
- name: http
13+
port: 8080
14+
targetPort: 8080
15+
type: ClusterIP

internal/http/handler.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"encoding/json"
66
stdhttp "net/http"
77
"time"
8+
9+
"github.com/prometheus/client_golang/prometheus/promhttp"
810
)
911

1012
type Limiter interface {
@@ -27,7 +29,7 @@ func NewRouter(lim Limiter) stdhttp.Handler {
2729
ctx, cancel := context.WithTimeout(r.Context(), 50*time.Millisecond)
2830
defer cancel()
2931

30-
// Read from header, matching what k6 sends
32+
// Read from header, matching what k6 sends
3133
apiKey := r.Header.Get("X-API-Key")
3234
if apiKey == "" {
3335
stdhttp.Error(w, "Missing X-API-Key", stdhttp.StatusBadRequest)
@@ -52,6 +54,8 @@ func NewRouter(lim Limiter) stdhttp.Handler {
5254
})
5355
})
5456

57+
mux.Handle("/metrics", promhttp.Handler())
58+
5559
return mux
5660
}
5761

0 commit comments

Comments
 (0)