Phase 3 Stream C Rust side. Completes the HrrrFetchWorker port. Pipeline: - db::claim_next_hrrr_task — FOR UPDATE SKIP LOCKED on hrrr_fetch_tasks, newest valid_time first. Accepts the points JSONB directly. - hrrr_points::process_batch — fetch surface + pressure GRIB2 once per task (tokio::try_join), decode via the existing wgrib2 plumbing, then for each requested point pull the cell and UPSERT INTO hrrr_profiles (conflict on lat/lon/valid_time). - db::complete_hrrr_task / fail_hrrr_task — status transitions; Elixir backfill re-enqueues failed rows on next /30-min scan. Shipping pieces: - new bin src/bin/hrrr_point_worker.rs - new module src/hrrr_points.rs (process_batch, upsert_profile) - new Cargo [[bin]] entry; Dockerfile builds both binaries in one stage and ships them in the runtime image so a single CI pipeline covers the whole cluster - k8s/deployment-hrrr-point-rs.yaml (1 replica, 1 Gi limit, anti-affinity against prop-grid-rs so chain + point work don't fight for wgrib2 slots). Uses the same image; command: override picks the right binary. - kustomization.yaml: include the new deployment so flux applies it - deployment-grid-rs.yaml: bump readiness initialDelaySeconds 3→15 + failureThreshold 3→6 so a slow DB connect during startup can't race the first probe 119 Rust tests green.
153 lines
5.7 KiB
YAML
153 lines
5.7 KiB
YAML
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-1776640915-65f7963 # {"$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
|
||
# Generous initialDelaySeconds so a slow DB connect during
|
||
# startup (Postgres acquire_timeout is 10s) can't race the
|
||
# first probe. failureThreshold=6 keeps the pod from
|
||
# flapping if a single scrape times out during a long
|
||
# chain step.
|
||
initialDelaySeconds: 15
|
||
periodSeconds: 10
|
||
timeoutSeconds: 3
|
||
failureThreshold: 6
|
||
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
|