Kubernetes Deployment
Deploy Rift in Kubernetes for production mock services and chaos engineering.
Quick Start
Basic Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: rift
labels:
app: rift
spec:
replicas: 1
selector:
matchLabels:
app: rift
template:
metadata:
labels:
app: rift
spec:
containers:
- name: rift
image: zainalpour/rift-proxy:latest
ports:
- name: admin
containerPort: 2525
- name: metrics
containerPort: 9090
env:
- name: MB_PORT
value: "2525"
- name: MB_ALLOW_INJECTION
value: "true"
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: admin
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: admin
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: rift
spec:
selector:
app: rift
ports:
- name: admin
port: 2525
targetPort: admin
- name: metrics
port: 9090
targetPort: metrics
Probes and --api-key
With MB_APIKEY / --api-key set, every admin API path — /health included — answers 401 without the key, so a plain httpGet probe on the admin port fails. Use rift healthcheck as an exec probe instead: it presents the key the container already holds from MB_APIKEY (issue #1154), so the secret stays in the pod’s environment rather than being copied into the manifest the way an httpGet httpHeaders entry would put it:
livenessProbe:
exec:
command: ["rift", "healthcheck"]
timeoutSeconds: 3
readinessProbe:
exec:
command: ["rift", "healthcheck"]
timeoutSeconds: 3
Set timeoutSeconds explicitly: an exec probe defaults to 1s, while rift healthcheck waits up to 2s by default, so without it a slow /health is killed by the kubelet instead of being reported by the probe. The key must come from the pod’s environment (MB_APIKEY, e.g. from a Secret) — the probe is a separate process and does not see the container’s args.
The image’s own Docker HEALTHCHECK is ignored by Kubernetes, so declare the probe explicitly as above. Probing the metrics port with httpGet still works too, but it only proves the metrics listener is up, not that the admin API is healthy.
Shutdown
A pod stops promptly: rift handles the SIGTERM the kubelet sends, including as the container’s PID 1 (issue #1155), so the default terminationGracePeriodSeconds is ample — the server’s own shutdown is bounded at about three seconds. It exits 0 and leaves any --datadir state on its volume. No init process and no shortened grace period are needed.
Configuration with ConfigMap
ConfigMap for Imposters
apiVersion: v1
kind: ConfigMap
metadata:
name: rift-imposters
data:
imposters.json: |
{
"imposters": [
{
"port": 4545,
"protocol": "http",
"name": "User Service Mock",
"stubs": [
{
"predicates": [{ "equals": { "path": "/health" } }],
"responses": [{ "is": { "statusCode": 200, "body": "OK" } }]
},
{
"predicates": [{ "equals": { "path": "/users" } }],
"responses": [{
"is": {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": [{ "id": 1, "name": "Test User" }]
}
}]
}
]
}
]
}
Deployment with ConfigMap
apiVersion: apps/v1
kind: Deployment
metadata:
name: rift
spec:
template:
spec:
containers:
- name: rift
image: zainalpour/rift-proxy:latest
args: ["--configfile", "/config/imposters.json"]
ports:
- name: admin
containerPort: 2525
- name: imposter
containerPort: 4545
volumeMounts:
- name: config
mountPath: /config
readOnly: true
volumes:
- name: config
configMap:
name: rift-imposters
TLS Configuration
TLS Secret
apiVersion: v1
kind: Secret
metadata:
name: rift-tls
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>
HTTPS Imposter
<%- stringify('…') %> inlines a file’s contents into the JSON string when the config is loaded; an absolute path is used as-is, a relative one is resolved against the config file’s directory.
apiVersion: v1
kind: ConfigMap
metadata:
name: rift-https-config
data:
imposters.json: |
{
"imposters": [{
"port": 4545,
"protocol": "https",
"key": "<%- stringify('/tls/tls.key') %>",
"cert": "<%- stringify('/tls/tls.crt') %>",
"stubs": [...]
}]
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rift
spec:
template:
spec:
containers:
- name: rift
image: zainalpour/rift-proxy:latest
args: ["--configfile", "/config/imposters.json"]
volumeMounts:
- name: config
mountPath: /config
- name: tls
mountPath: /tls
readOnly: true
volumes:
- name: config
configMap:
name: rift-https-config
- name: tls
secret:
secretName: rift-tls
Sidecar Pattern
Application with Rift Sidecar
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
# Main application
- name: app
image: my-app:latest
env:
- name: EXTERNAL_API_URL
value: "http://localhost:4545"
# Rift sidecar
- name: rift
image: zainalpour/rift-proxy:latest
args: ["--configfile", "/config/imposters.json"]
ports:
- containerPort: 4545
volumeMounts:
- name: mock-config
mountPath: /config
volumes:
- name: mock-config
configMap:
name: my-app-mocks
High Availability
Multi-Replica Deployment
Each replica is an independent Rift: an imposter created through the admin API exists only on the replica that received the request, and so do recorded requests. Behind a Service, admin calls and verification land on arbitrary pods. Replicate only when every pod loads the same imposters at startup (--configfile from a ConfigMap) and nothing mutates them at runtime; a Redis flowState backend shares flow state between pods, not imposters.
apiVersion: apps/v1
kind: Deployment
metadata:
name: rift
spec:
replicas: 3
template:
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: rift
topologyKey: kubernetes.io/hostname
containers:
- name: rift
image: zainalpour/rift-proxy:latest
Horizontal Pod Autoscaler
The same caveat applies: a pod the autoscaler adds starts with only the startup config.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: rift
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: rift
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Monitoring
ServiceMonitor for Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: rift
spec:
selector:
matchLabels:
app: rift
endpoints:
- port: metrics
interval: 15s
path: /metrics
PodMonitor
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: rift
spec:
selector:
matchLabels:
app: rift
podMetricsEndpoints:
- port: metrics
interval: 15s
Namespace Isolation
Dedicated Namespace
apiVersion: v1
kind: Namespace
metadata:
name: test-mocks
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rift
namespace: test-mocks
spec:
# ... deployment spec
Network Policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: rift-policy
namespace: test-mocks
spec:
podSelector:
matchLabels:
app: rift
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: test-runners
ports:
- port: 2525
- port: 4545
Helm Chart (Example)
Rift does not publish a Helm chart. The values below are a starting point for a chart of your own; map config.allowInjection to MB_ALLOW_INJECTION, config.logLevel to MB_LOGLEVEL, and imposters to a ConfigMap passed with --configfile.
values.yaml
replicaCount: 1
image:
repository: zainalpour/rift-proxy
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
adminPort: 2525
metricsPort: 9090
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 250m
memory: 128Mi
config:
allowInjection: true
logLevel: info
imposters: |
{
"imposters": []
}
Troubleshooting
Check Pod Status
kubectl get pods -l app=rift
kubectl describe pod -l app=rift
kubectl logs -l app=rift
Port Forward for Testing
kubectl port-forward svc/rift 2525:2525
curl http://localhost:2525/imposters
Debug Container
kubectl exec -it deployment/rift -- /bin/sh
The default image has a shell but no curl; the -static image has neither. For the static image, use an ephemeral debug container instead:
kubectl debug -it <rift-pod-name> --image=busybox --target=rift