prop/k8s/deployment-grid-rs.yaml

147 lines
5.4 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

apiVersion: apps/v1
kind: Deployment
metadata:
name: prop-grid-rs
namespace: prop
# Phase 2 cutover: Rust writes directly to /data/scores. Elixir only
# runs the f00 analysis-hour step (which carries enrichment Rust
# doesn't own yet — native-level duct merge, NEXRAD composite,
# commercial-link degradation, ProfilesFile write). f01..f18 land
# here from the Rust worker.
spec:
# Two replicas for HA. FOR UPDATE SKIP LOCKED lets both pods claim
# from grid_tasks without coordination. Anti-affinity spreads them
# across hosts so talos5 going down doesn't halt the hourly chain.
replicas: 2
minReadySeconds: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
selector:
matchLabels:
app: prop-grid-rs
template:
metadata:
labels:
app: prop-grid-rs
tier: grid-rs
annotations:
# Scraped by the Prometheus at 10.0.15.25. Metrics endpoint is
# on container port 9100 (see METRICS_ADDR env below).
prometheus.io/scrape: "true"
prometheus.io/port: "9100"
prometheus.io/path: "/metrics"
spec:
# Primary replica prefers talos5 (32 GB NUC with NVMe). Secondary
# replica lands anywhere else the scheduler allows — the
# required anti-affinity rule below guarantees the two don't
# stack on the same host.
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: prop-grid-rs
topologyKey: kubernetes.io/hostname
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: prop-grid-rs
operator: In
values: ["primary"]
tolerations:
- key: workload
operator: Equal
value: grid-rs
effect: NoSchedule
imagePullSecrets:
- name: forgejo-registry
securityContext:
runAsUser: 65534
runAsNonRoot: true
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: prop-grid-rs
# Image built by a new CI pipeline from rust/prop_grid_rs/.
# Multi-arch (amd64 + arm64) — reuses the repo's existing
# buildx wiring.
image: git.mcintire.me/graham/prop-grid-rs:main-1776639366-22f5828 # {"$imagepolicy": "flux-system:prop-grid-rs"}
imagePullPolicy: IfNotPresent
env:
# Elixir secrets bundle carries DATABASE_URL already. Rust
# reads it directly (sqlx connects with the same URL).
- name: HRRR_BASE_URL
value: "http://skippy.w5isp.com:8080"
- name: PROP_SCORES_DIR
value: "/data/scores"
- name: RUST_LOG
value: "info"
# talos5 is 4c/4t (i5-7260U). Each replica claims up to this
# many forecast hours in parallel. With 2 replicas × 3 → 6
# concurrent tasks, the 18-hour chain completes in ~3 min.
# Per-task peak RAM is ~250 Mi post-streaming refactor, so
# 3×250 ≈ 750 Mi per pod, well under the 2 Gi limit. The
# secondary replica may land on a smaller node — 3 keeps it
# honest.
- name: PROP_GRID_RS_PARALLELISM
value: "3"
# Pool size = parallelism + 2 (NOTIFY + retry slack). Kept in
# sync with PROP_GRID_RS_PARALLELISM when either changes.
- name: PROP_GRID_RS_PG_CONNS
value: "5"
- name: METRICS_ADDR
value: "0.0.0.0:9100"
envFrom:
- secretRef:
name: prop-secrets
ports:
- name: metrics
containerPort: 9100
protocol: TCP
# Liveness is "the process is still running"; let the kubelet
# restart us if we crash. Claiming work from grid_tasks is the
# implicit readiness signal — an idle pod is still healthy
# (the queue is simply empty). A minimal readiness check on
# /health confirms the metrics listener is up, which doubles
# as evidence the tokio runtime is alive.
readinessProbe:
httpGet:
path: /health
port: 9100
initialDelaySeconds: 3
periodSeconds: 10
timeoutSeconds: 2
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: "4"
# OOM at 1 Gi during active chain work (92k points × 23
# bands ≈ 2.1M scores, plus decoded GRIB2 buffers, plus
# wgrib2 subprocess memory). 2 Gi leaves headroom; we're
# still an order of magnitude below the 6 Gi Elixir budget.
memory: 2Gi
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