With 1 replica, the old pod was torn down too quickly after the new pod passed its first health check. Increase successThreshold to 3 (15s of consecutive checks) and minReadySeconds to 30 to ensure Traefik has fully propagated the new endpoint before the old pod is terminated.
234 lines
7.4 KiB
YAML
234 lines
7.4 KiB
YAML
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: towerops
|
|
namespace: towerops
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
type: RollingUpdate
|
|
rollingUpdate:
|
|
maxSurge: 1 # Allow 3rd pod during rollout to avoid downtime
|
|
maxUnavailable: 0 # Keep all pods running during rollout
|
|
minReadySeconds: 30 # Wait 30s after pod is ready before considering it available (endpoint propagation buffer)
|
|
selector:
|
|
matchLabels:
|
|
app: towerops
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: towerops
|
|
spec:
|
|
# Use system-cluster-critical priority to ensure towerops starts after CNI is ready
|
|
priorityClassName: system-cluster-critical
|
|
terminationGracePeriodSeconds: 90 # Allow 90s for graceful shutdown and connection draining
|
|
imagePullSecrets:
|
|
- name: forgejo-registry
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 65534
|
|
fsGroup: 65534
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
initContainers:
|
|
- name: migrate
|
|
image: git.mcintire.me/graham/towerops-web:main-1777403311-23304e1
|
|
imagePullPolicy: IfNotPresent
|
|
command: ["/app/bin/migrate"]
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsNonRoot: true
|
|
runAsUser: 65534
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
env:
|
|
- name: MIX_ENV
|
|
value: "prod"
|
|
- name: RELEASE_COOKIE
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-secrets
|
|
key: RELEASE_COOKIE
|
|
- name: SECRET_KEY_BASE
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-secrets
|
|
key: SECRET_KEY_BASE
|
|
- name: CLOAK_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-secrets
|
|
key: CLOAK_KEY
|
|
- name: STRIPE_SECRET_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-billing
|
|
key: STRIPE_SECRET_KEY
|
|
optional: true
|
|
- name: STRIPE_WEBHOOK_SECRET
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-billing
|
|
key: STRIPE_WEBHOOK_SECRET
|
|
optional: true
|
|
- name: STRIPE_PRICE_ID
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-billing
|
|
key: STRIPE_PRICE_ID
|
|
optional: true
|
|
- name: STRIPE_METER_ID
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-billing
|
|
key: STRIPE_METER_ID
|
|
optional: true
|
|
envFrom:
|
|
- secretRef:
|
|
name: towerops-db
|
|
- secretRef:
|
|
name: towerops-aws
|
|
resources:
|
|
requests:
|
|
memory: "512Mi"
|
|
cpu: "100m"
|
|
limits:
|
|
memory: "1Gi"
|
|
cpu: "500m"
|
|
containers:
|
|
- name: towerops
|
|
image: git.mcintire.me/graham/towerops-web:main-1777403311-23304e1
|
|
imagePullPolicy: IfNotPresent
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
runAsNonRoot: true
|
|
runAsUser: 65534
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
ports:
|
|
- containerPort: 4000
|
|
name: http
|
|
- containerPort: 4369
|
|
name: epmd
|
|
- containerPort: 9000
|
|
name: dist
|
|
lifecycle:
|
|
preStop:
|
|
exec:
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
# Stop accepting new connections by failing readiness probe
|
|
# Give load balancer time to remove pod from rotation
|
|
# Allow existing connections to complete
|
|
sleep 20
|
|
env:
|
|
- name: POD_IP
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: status.podIP
|
|
- name: POD_NAME
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.name
|
|
- name: POD_NAMESPACE
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: metadata.namespace
|
|
- name: ETCD_ENABLED
|
|
value: "true"
|
|
- name: RELEASE_DISTRIBUTION
|
|
value: "name"
|
|
- name: RELEASE_NODE
|
|
value: "towerops@$(POD_IP)"
|
|
- name: PORT
|
|
value: "4000"
|
|
- name: PHX_HOST
|
|
value: "towerops.net"
|
|
- name: MIX_ENV
|
|
value: "prod"
|
|
- name: RELEASE_COOKIE
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-secrets
|
|
key: RELEASE_COOKIE
|
|
- name: SECRET_KEY_BASE
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-secrets
|
|
key: SECRET_KEY_BASE
|
|
- name: CLOAK_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-secrets
|
|
key: CLOAK_KEY
|
|
- name: STRIPE_SECRET_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-billing
|
|
key: STRIPE_SECRET_KEY
|
|
optional: true
|
|
- name: STRIPE_WEBHOOK_SECRET
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-billing
|
|
key: STRIPE_WEBHOOK_SECRET
|
|
optional: true
|
|
- name: STRIPE_PRICE_ID
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-billing
|
|
key: STRIPE_PRICE_ID
|
|
optional: true
|
|
- name: STRIPE_METER_ID
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: towerops-billing
|
|
key: STRIPE_METER_ID
|
|
optional: true
|
|
envFrom:
|
|
# Redis connection configured via towerops-redis secret
|
|
- secretRef:
|
|
name: towerops-redis
|
|
- secretRef:
|
|
name: towerops-db
|
|
- secretRef:
|
|
name: towerops-aws
|
|
resources:
|
|
requests:
|
|
memory: "1.5Gi"
|
|
cpu: "500m"
|
|
limits:
|
|
memory: "3Gi"
|
|
cpu: "2000m"
|
|
startupProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: 4000
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 5
|
|
timeoutSeconds: 3
|
|
failureThreshold: 24 # 10s + (24 * 5s) = 130s max startup time
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: 4000
|
|
periodSeconds: 10
|
|
timeoutSeconds: 3
|
|
failureThreshold: 3
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: 4000
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
timeoutSeconds: 2
|
|
successThreshold: 3 # Require 3 consecutive successes (15s) before marking ready
|
|
failureThreshold: 2 # Allow 1 failure before marking not ready
|