# 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