apiVersion: apps/v1 kind: Deployment metadata: name: prop-backfill namespace: prop spec: # Single replica. podAntiAffinity targets `tier: hot` (not `app: prop`) # so backfill only repels the web pods, not itself. An `app: prop` # selector would also match backfill pods — fine at steady state, but # during a rolling hot-pod rollout with HPA at max replicas the old # RS's requiredDuringScheduling/IgnoredDuringExecution stays pinned on # already-running hot pods, backfill's rule excludes the new hot pod # from every physical host backfill hasn't touched, and the rollout # deadlocks because maxUnavailable=1 can't bring an old hot pod down # until a new one is Ready. Matching only `tier: hot` avoids that: # backfill never constrains itself, and hot pods have no anti-affinity # of their own so they can co-locate freely if HPA scales past the # physical-host count. replicas: 1 minReadySeconds: 5 strategy: type: RollingUpdate rollingUpdate: maxSurge: 0 maxUnavailable: 1 selector: matchLabels: app: prop tier: backfill template: metadata: labels: # app=prop matches the libcluster selector in runtime.exs, so these # pods join the same Erlang cluster as the hot-path prop pods and # share Oban's queue ownership / leader election. app: prop tier: backfill spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: prop tier: hot topologyKey: topology.kubernetes.io/physical-host serviceAccountName: prop imagePullSecrets: - name: forgejo-registry securityContext: runAsUser: 65534 runAsNonRoot: true fsGroup: 65534 seccompProfile: type: RuntimeDefault containers: - name: prop-backfill image: git.mcintire.me/graham/prop:main-1777217856-021608d # {"$imagepolicy": "flux-system:prop"} imagePullPolicy: IfNotPresent env: - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP # No PHX_SERVER — endpoint stays inert. No PORT either. - name: PROP_ROLE value: "backfill" - name: PHX_HOST value: "prop.w5isp.com" - name: HRRR_BASE_URL value: "http://skippy.w5isp.com:8080" envFrom: - secretRef: name: prop-secrets # No HTTP probes (no web server). Use exec probe against the # running BEAM to confirm the VM is responsive. `rpc ':ok'` # has to spawn a release CLI and round-trip through distributed # Erlang; during an IEM 429 retry storm the scheduler is # responsive but slow. 15 s still tripped failureThreshold # every ~10 min, so the liveness probe now runs every 2 min # with a 30 s ceiling — the BEAM itself would have crashed # long before that if it were genuinely wedged, and rescue # relies on the BEAM's own crash-loop handling anyway. startupProbe: exec: command: ["/bin/sh", "-c", "/app/bin/microwaveprop rpc ':ok'"] initialDelaySeconds: 10 periodSeconds: 10 failureThreshold: 18 timeoutSeconds: 30 livenessProbe: exec: command: ["/bin/sh", "-c", "/app/bin/microwaveprop rpc ':ok'"] periodSeconds: 120 failureThreshold: 3 timeoutSeconds: 30 resources: # 8GB T620 box — leave ~3GB for kubelet/containerd/OS. requests: cpu: 100m memory: 512Mi limits: cpu: "2" memory: 4Gi securityContext: allowPrivilegeEscalation: false runAsNonRoot: true runAsUser: 65534 capabilities: drop: - ALL seccompProfile: type: RuntimeDefault volumeMounts: - name: data mountPath: /data volumes: - name: data nfs: server: 10.0.15.103 path: /data