Kubernetes Deployment
This guide covers deploying FluxiQ PIX to a Kubernetes cluster for production workloads.
Prerequisites
- Kubernetes cluster >= 1.28
- kubectl configured with cluster access
- Helm >= 3.14 (for dependency charts)
- Container registry with built images
- TLS certificates (ICP-Brasil) stored as Kubernetes Secrets
Namespace Setup
bash
# Create the PIX namespace
kubectl create namespace pix
# Set as default context
kubectl config set-context --current --namespace=pixSecrets
Database Credentials
yaml
# k8s/secrets/database.yaml
apiVersion: v1
kind: Secret
metadata:
name: pix-database
namespace: pix
type: Opaque
stringData:
DATABASE_URL: "postgres://pix_user:secure-password@pix-postgres:5432/pix_prod"ICP-Brasil Certificates
bash
# Create secret from certificate files
kubectl create secret generic bacen-certs \
--namespace pix \
--from-file=client.pem=./certs/client.pem \
--from-file=client_key.pem=./certs/client_key.pem \
--from-file=ca_chain.pem=./certs/ca_chain.pem \
--from-file=bacen_ca.pem=./certs/bacen_ca.pemApplication Secrets
yaml
# k8s/secrets/app.yaml
apiVersion: v1
kind: Secret
metadata:
name: pix-app-secrets
namespace: pix
type: Opaque
stringData:
SECRET_KEY_BASE: "your-64-char-minimum-secret-key-base-here-for-production-use"
ISPB: "12345678"ConfigMap
yaml
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: pix-config
namespace: pix
data:
DICT_BASE_URL: "https://dict.pi.rsfn.net.br:16522/api/v2"
SPI_BASE_URL: "https://spi.pi.rsfn.net.br:16522"
REDIS_URL: "redis://pix-redis-master:6379/0"
NATS_URL: "nats://pix-nats:4222"
CERT_PATH: "/certs/client.pem"
KEY_PATH: "/certs/client_key.pem"
CA_PATH: "/certs/ca_chain.pem"
LOG_LEVEL: "info"
LOG_FORMAT: "json"Service Deployments
DICT Service
yaml
# k8s/dict-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dict-service
namespace: pix
labels:
app: dict-service
tier: backend
spec:
replicas: 3
selector:
matchLabels:
app: dict-service
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: dict-service
tier: backend
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9568"
spec:
serviceAccountName: pix-service
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: dict-service
image: registry.example.com/pix/dict-service:1.0.0
ports:
- containerPort: 4001
name: http
protocol: TCP
- containerPort: 9568
name: metrics
protocol: TCP
envFrom:
- configMapRef:
name: pix-config
- secretRef:
name: pix-database
- secretRef:
name: pix-app-secrets
env:
- name: PHX_PORT
value: "4001"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
volumeMounts:
- name: certs
mountPath: /certs
readOnly: true
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2000m"
memory: "2Gi"
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: http
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
volumes:
- name: certs
secret:
secretName: bacen-certs
defaultMode: 0400
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: dict-service
---
apiVersion: v1
kind: Service
metadata:
name: dict-service
namespace: pix
labels:
app: dict-service
spec:
selector:
app: dict-service
ports:
- name: http
port: 4001
targetPort: http
- name: metrics
port: 9568
targetPort: metrics
type: ClusterIPSPI Service
yaml
# k8s/spi-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spi-service
namespace: pix
labels:
app: spi-service
tier: backend
spec:
replicas: 3
selector:
matchLabels:
app: spi-service
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: spi-service
tier: backend
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9568"
spec:
serviceAccountName: pix-service
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: spi-service
image: registry.example.com/pix/spi-service:1.0.0
ports:
- containerPort: 4002
name: http
- containerPort: 9568
name: metrics
envFrom:
- configMapRef:
name: pix-config
- secretRef:
name: pix-database
- secretRef:
name: pix-app-secrets
env:
- name: PHX_PORT
value: "4002"
volumeMounts:
- name: certs
mountPath: /certs
readOnly: true
resources:
requests:
cpu: "1000m"
memory: "1Gi"
limits:
cpu: "4000m"
memory: "4Gi"
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: http
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: certs
secret:
secretName: bacen-certs
defaultMode: 0400
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: spi-service
---
apiVersion: v1
kind: Service
metadata:
name: spi-service
namespace: pix
spec:
selector:
app: spi-service
ports:
- name: http
port: 4002
targetPort: http
- name: metrics
port: 9568
targetPort: metrics
type: ClusterIPSettlement Service
yaml
# k8s/settlement-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: settlement-service
namespace: pix
labels:
app: settlement-service
tier: backend
spec:
replicas: 3
selector:
matchLabels:
app: settlement-service
template:
metadata:
labels:
app: settlement-service
tier: backend
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9568"
spec:
serviceAccountName: pix-service
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: settlement-service
image: registry.example.com/pix/settlement-service:1.0.0
ports:
- containerPort: 4003
name: http
- containerPort: 9568
name: metrics
envFrom:
- configMapRef:
name: pix-config
- secretRef:
name: pix-database
- secretRef:
name: pix-app-secrets
env:
- name: PHX_PORT
value: "4003"
- name: DICT_SERVICE_URL
value: "http://dict-service:4001"
- name: SPI_SERVICE_URL
value: "http://spi-service:4002"
volumeMounts:
- name: certs
mountPath: /certs
readOnly: true
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2000m"
memory: "2Gi"
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: http
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: certs
secret:
secretName: bacen-certs
defaultMode: 0400
---
apiVersion: v1
kind: Service
metadata:
name: settlement-service
namespace: pix
spec:
selector:
app: settlement-service
ports:
- name: http
port: 4003
targetPort: http
- name: metrics
port: 9568
targetPort: metrics
type: ClusterIPIngress
yaml
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pix-ingress
namespace: pix
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- pix.example.com
- admin.pix.example.com
- docs.pix.example.com
secretName: pix-tls
rules:
- host: pix.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: settlement-service
port:
number: 4003
- path: /
pathType: Prefix
backend:
service:
name: user-portal
port:
number: 8080
- host: admin.pix.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-portal
port:
number: 8080
- host: docs.pix.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: docs-site
port:
number: 8080Horizontal Pod Autoscaler
yaml
# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: dict-service-hpa
namespace: pix
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: dict-service
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 120
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: spi-service-hpa
namespace: pix
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: spi-service
minReplicas: 3
maxReplicas: 15
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Pods
value: 3
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: settlement-service-hpa
namespace: pix
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: settlement-service
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Pod Disruption Budgets
Ensure availability during cluster maintenance:
yaml
# k8s/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: dict-service-pdb
namespace: pix
spec:
minAvailable: 2
selector:
matchLabels:
app: dict-service
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: spi-service-pdb
namespace: pix
spec:
minAvailable: 2
selector:
matchLabels:
app: spi-service
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: settlement-service-pdb
namespace: pix
spec:
minAvailable: 2
selector:
matchLabels:
app: settlement-serviceInfrastructure Dependencies
PostgreSQL (via Helm)
bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install pix-postgres bitnami/postgresql \
--namespace pix \
--set auth.postgresPassword=secure-password \
--set auth.database=pix_prod \
--set primary.resources.requests.cpu=1000m \
--set primary.resources.requests.memory=2Gi \
--set primary.persistence.size=100Gi \
--set metrics.enabled=trueRedis (via Helm)
bash
helm install pix-redis bitnami/redis \
--namespace pix \
--set architecture=standalone \
--set auth.enabled=false \
--set master.resources.requests.cpu=500m \
--set master.resources.requests.memory=1Gi \
--set master.persistence.size=10Gi \
--set metrics.enabled=trueNATS (via Helm)
bash
helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm install pix-nats nats/nats \
--namespace pix \
--set config.jetstream.enabled=true \
--set config.jetstream.memStorage.size=1Gi \
--set config.jetstream.fileStorage.size=10GiMonitoring
ServiceMonitor for Prometheus
yaml
# k8s/monitoring/service-monitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: pix-services
namespace: pix
labels:
release: prometheus
spec:
selector:
matchLabels:
tier: backend
endpoints:
- port: metrics
interval: 15s
path: /metricsGrafana Dashboard
Import the PIX dashboard from k8s/monitoring/grafana-dashboard.json which includes:
- Transaction throughput (TPS) per service
- P50/P95/P99 latency histograms
- Error rates by endpoint
- Settlement position over time
- Pod resource utilization
- BACEN API latency and error rates
Deployment Commands
bash
# Apply all manifests
kubectl apply -f k8s/secrets/
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/dict-service.yaml
kubectl apply -f k8s/spi-service.yaml
kubectl apply -f k8s/settlement-service.yaml
kubectl apply -f k8s/ingress.yaml
kubectl apply -f k8s/hpa.yaml
kubectl apply -f k8s/pdb.yaml
# Check rollout status
kubectl rollout status deployment/dict-service -n pix
kubectl rollout status deployment/spi-service -n pix
kubectl rollout status deployment/settlement-service -n pix
# Verify pods are running
kubectl get pods -n pix -l tier=backend
# Run database migrations (one-time job)
kubectl create job --from=cronjob/pix-migrate pix-migrate-manual -n pixRolling Updates
bash
# Update a service image
kubectl set image deployment/dict-service \
dict-service=registry.example.com/pix/dict-service:1.1.0 \
-n pix
# Monitor the rollout
kubectl rollout status deployment/dict-service -n pix
# Rollback if needed
kubectl rollout undo deployment/dict-service -n pix