Per Q-SO-1 operator resolution 2026-05-11 PM, Redis SignalR backplane lands
in Phase A (was Phase C deferral). Treats Redis as a managed FC infrastructure
component, not a deferred scaling escalation.
Lands the minimal Phase A surface:
- Namespace fc-redis
- Single Redis 7-alpine pod with 1Gi Longhorn RWO PVC
- ConfigMap with AOF persistence (everysec), 256Mi maxmemory, allkeys-lru
- ClusterIP Service `redis.fc-redis.svc.cluster.local:6379` (in-cluster only)
- No AUTH Phase A (Phase B add via 1Password Connect rotation)
- No IngressRoute (backplane is server-to-server)
Consumers (Phase A IMPL across FC services) add:
services.AddSignalR().AddStackExchangeRedis(
"redis.fc-redis.svc.cluster.local:6379",
opts => opts.Configuration.ChannelPrefix =
StackExchange.Redis.RedisChannel.Literal("fc-opsconsole"));
Phase B/C follow-ons (not in this commit): Sentinel for HA, AUTH password
from 1Password, redis_exporter sidecar for Prometheus, network policies.
See FlowerCore.Notes/docs/signage/operations-console-phase-2-design.md
section 3.5 (rewritten) and decisions-waiting.html Q-SO-1 (RESOLVED).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
172 lines
4.5 KiB
YAML
172 lines
4.5 KiB
YAML
# fc-redis — SignalR backplane for cross-product event bus
|
|
#
|
|
# Lands per Q-SO-1 resolution (2026-05-11 PM): SignalR backplane in Phase A,
|
|
# not Phase C as originally drafted. Operator directive: "Redis can be
|
|
# deployed just fine as it's another FlowerCore technology we'll want to
|
|
# manage."
|
|
#
|
|
# Phase A scope (this file):
|
|
# - Single Redis 7.x Alpine pod
|
|
# - 1Gi Longhorn RWO PVC for AOF persistence
|
|
# - ClusterIP Service at `redis.fc-redis.svc.cluster.local:6379`
|
|
# - No AUTH (in-cluster only; not exposed externally)
|
|
# - No IngressRoute (backplane is server-to-server only)
|
|
#
|
|
# Consumers (Phase A IMPL across FC services):
|
|
# - FlowerCore.Signage.Web (OpsConsoleHub)
|
|
# - FlowerCore.Scoreboard.Web (ScoreboardHub)
|
|
# - FlowerCore.SignalControl.Web
|
|
# - FlowerCore.DMS.Web
|
|
# - Any other product joining the cross-product event bus
|
|
#
|
|
# Each consumer adds:
|
|
# services.AddSignalR()
|
|
# .AddStackExchangeRedis(
|
|
# "redis.fc-redis.svc.cluster.local:6379",
|
|
# opts => opts.Configuration.ChannelPrefix =
|
|
# StackExchange.Redis.RedisChannel.Literal("fc-opsconsole"));
|
|
#
|
|
# Phase B / C follow-ons (out of scope here):
|
|
# - Redis Sentinel for HA (3-node)
|
|
# - AUTH password from 1Password Connect (rotate via /rotate-password)
|
|
# - redis_exporter sidecar for Prometheus scrape
|
|
# - Network policies restricting which namespaces can dial 6379
|
|
#
|
|
# Design: docs/signage/operations-console-phase-2-design.md §3.5
|
|
# Decision: Q-SO-1 (RESOLVED 2026-05-11 PM)
|
|
# Memory: feedback_blooming_ui_pattern_no_iframes
|
|
---
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: fc-redis
|
|
labels:
|
|
app.kubernetes.io/part-of: flowercore
|
|
app.kubernetes.io/managed-by: argocd
|
|
---
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: fc-redis-data
|
|
namespace: fc-redis
|
|
spec:
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
storageClassName: longhorn
|
|
resources:
|
|
requests:
|
|
storage: 1Gi
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: fc-redis-config
|
|
namespace: fc-redis
|
|
data:
|
|
redis.conf: |
|
|
# Phase A — minimal config; no AUTH, no replication.
|
|
bind 0.0.0.0
|
|
protected-mode no
|
|
port 6379
|
|
tcp-backlog 511
|
|
timeout 0
|
|
tcp-keepalive 300
|
|
|
|
# Persistence: AOF (fsync every second is the standard SignalR-backplane
|
|
# durability sweet spot — the backplane only needs to survive Redis
|
|
# restarts, not absolute zero loss).
|
|
appendonly yes
|
|
appendfsync everysec
|
|
auto-aof-rewrite-percentage 100
|
|
auto-aof-rewrite-min-size 64mb
|
|
|
|
# Reasonable defaults — let Redis pick most things.
|
|
maxmemory-policy allkeys-lru
|
|
maxmemory 256mb
|
|
|
|
# Logging
|
|
loglevel notice
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: fc-redis
|
|
namespace: fc-redis
|
|
labels:
|
|
app: fc-redis
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
type: Recreate # RWO PVC; do not do rolling update
|
|
selector:
|
|
matchLabels:
|
|
app: fc-redis
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: fc-redis
|
|
spec:
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 999 # redis:7-alpine default uid
|
|
runAsGroup: 999
|
|
fsGroup: 999
|
|
containers:
|
|
- name: redis
|
|
image: redis:7-alpine
|
|
imagePullPolicy: IfNotPresent
|
|
command: ["redis-server", "/etc/redis/redis.conf"]
|
|
ports:
|
|
- name: redis
|
|
containerPort: 6379
|
|
resources:
|
|
requests:
|
|
cpu: "50m"
|
|
memory: "128Mi"
|
|
limits:
|
|
cpu: "500m"
|
|
memory: "384Mi"
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: /data
|
|
- name: config
|
|
mountPath: /etc/redis
|
|
readOnly: true
|
|
livenessProbe:
|
|
tcpSocket:
|
|
port: 6379
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
readinessProbe:
|
|
exec:
|
|
command: ["redis-cli", "ping"]
|
|
initialDelaySeconds: 2
|
|
periodSeconds: 5
|
|
securityContext:
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: [ALL]
|
|
volumes:
|
|
- name: data
|
|
persistentVolumeClaim:
|
|
claimName: fc-redis-data
|
|
- name: config
|
|
configMap:
|
|
name: fc-redis-config
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: redis
|
|
namespace: fc-redis
|
|
spec:
|
|
type: ClusterIP
|
|
selector:
|
|
app: fc-redis
|
|
ports:
|
|
- name: redis
|
|
port: 6379
|
|
targetPort: 6379
|
|
protocol: TCP
|