deployment tracker · hybrid-cloud compute grid

symphonygrid / AWS ⇄ ROKS

IBM Spectrum Symphony grid with Windows Server compute VMs on OpenShift Virtualization — primary on self-hosted OpenShift on AWS (IPI), bursting into ROKS (IBM Cloud). Fully automated via GitOps; built and operated by Claude agents with human decision gates.

TRACK B DONE · ~$5.2/hr · TRACK C C1 BASE-PORT GATE PASSED (CE master on 7869) · RHSI/SKUPPER LIVE ON SYM-AWS (private KEDA metric path)

updated 2026-07-13 · head 4cc3850 · branch main · repo git.arsalan.io/anaeem/grid

phase
2 ✓ of 5 · exit proven
decisions locked
13 D1–D13
cross-cluster join
~8m KEDA 0→2 → NTX64 ok, hands-off
wg tunnel overhead
~0ms remote vs local: −6 to +2 ms
eval license
172d to 2026-12-31
cloud burn
~$5.2/hrback to single-cluster baseline · sym-burst destroyed

● Happening now

  • RHSI (Skupper v2) stood up on sym-aws as the private KEDA metric link — Submariner-vs-Skupper question resolved in favor of Skupper for this one job. Installed the Red Hat Operators skupper-operator (channel stable-2, "Red Hat Service Interconnect") and applied a Site/Connector/AccessGrant live in ns symphony-metrics — router pod Running, AccessGrant minting real redemption tokens. Proved the whole Connector→Listener→backend chain intra-cluster (a scratch namespace stood in for sym-burst, which is currently torn down), confirmed traffic genuinely transits the router mesh, then cleanly reverted every live probe back to the committed linkAccess: none. A real finding changed the sym-burst design mid-flight: the plan assumed a Skupper router Pod could live inside the grid's UDN namespace with no ingress-island problem — tested live in a throwaway UDN namespace and found false (same restriction VMs have, just not previously proven for plain Pods); the sym-burst side now gets its own dedicated plain namespace instead. sym-burst's own Site/Listener are authored, wired into gitops, and correctly inert (no cluster to sync against); the KEDA ScaledObject is re-pointed at the private Listener URL and the public metric Route is retired. The one new billable (an internal-scheme AWS NLB) is deliberately deferred to the Track B rebuild, not created speculatively. Full writeup: infra/interconnect/README.md's "Service plane" section.
  • Real Symphony grid metrics now land in Prometheus. Enabled OpenShift User Workload Monitoring (cluster-monitoring-config, enableUserWorkload: true) and pointed a ServiceMonitor at symphony-metrics-server's existing /metrics endpoint (an additive second ServicePort, the live metrics-api-trigger port untouched). Extended the metric set with symphony_done_tasks and three symphony_resources_* gauges — free, they were already on the symrest payload the shim was polling, just not forwarded. Verified live end-to-end: UWM's prometheus-user-workload/thanos-ruler-user-workload pods Running, a token-authed thanos-querier query for symphony_pending_tasks returned the same live value as the JSON endpoint, and up{job="symphony-metrics-server"} == 1. Documented (not wired) a KEDA prometheus-trigger as an alternative to metrics-api for later. Zero new billables (UWM at stock defaults).
  • Connectivity-survey part 3 (MetalLB) landed — with a live correction to our own facts. MetalLB is a wholesale no on AWS (Red Hat: unsupported on clouds; upstream: "AWS: No, use EKS" — emulated ARP breaks L2, VIP=DNAT breaks identity, shared VIPs can't be dialed-back to a specific clone). But the adversarial pass caught our 2026-07-11 "UDN ingress-island" result contradicting current docs, and a live retest proved NodePort ingress into primary-UDN VMs WORKS on 4.20.27 (ClusterIP/direct-IP still blocked by design; port-security/no-relay finding stands). Consequences: the dormant NLB master-exposure design revives, and two spikes registered — an in-cluster wg-hub behind a UDP NodePort (would delete the EC2 hub + Terraform → interconnect fully GitOps), and OVN-K RouteAdvertisements + AWS VPC Route Server (GA 2025-04 managed BGP speaker — yesterday's "AWS can't BGP" wall is gone; unproven combo, watch item). Writeup: docs/research/metallb-lb-exposure-investigation.md.
  • Connectivity-survey part 2 landed: Connectivity Link + service meshes evaluated (19 agents, adversarially verified). The WireGuard verdict is now family-wide: every mesh/interconnect product (RHCL/Kuadrant, OSSM 3/Istio, Skupper/RHSI, Linkerd, Consul) is a connection-terminating proxy — breaks EGO's IP-anchored identity, carries no generic UDP (EGO needs 7869 TCP+UDP, IBM-doc-verified), can't model master dial-back — and Cilium ClusterMesh would delete the primary-UDN construct itself. For the service plane (the public KEDA metric Route), the ranked menu is: harden-the-Route (null option) → Submariner (if the ACM hub lands) → Skupper/RHSI (lightest: dial-out mTLS links, no CIDRs, extends to the home lab) → RHCL only if multiple HTTP APIs ever need uniform policy. Full writeup: docs/research/mesh-connectivity-investigation.md.
  • Submariner/ACM investigation landed (31-agent research + adversarial verify). Verdict: Submariner cannot carry the grid — its OVN-K integration only routes the default pod network, and the Symphony VMs live on primary layer2 UDNs that are invisible to it (no roadmap either; the cross-cluster-UDN future is OVN-K BGP/EVPN, not Submariner). Per-VM WireGuard stays for grid traffic. What is viable: the home virt cluster as an ACM hub (spoke→hub reachability probed green from inside sym-aws), Hive ClusterDeployments for git-driven cluster create/destroy/hibernate, and golden Windows images pushed to oci.arsalan.io + pulled everywhere by DataImportCron. Full writeup: docs/research/submariner-acm-investigation.md.
  • Track B is DONE — cross-cluster autonomous burst proven end-to-end. A ~6000-task backlog on the AWS grid drove KEDA on the second cluster to scale its Windows pool 0→2 reading the demand metric over a cross-cluster Route; both clones booted, brought up their WireGuard tunnels, and joined the AWS masters as NTX64 ok in ~8 min with zero manual steps, then drained and scaled back to 0. symping on the remote host runs at local-host speed (the tunnel adds no measurable latency).
  • sym-burst torn down; back to ~$5.2/hr. The burst cluster is destroyed (verified: no 10.1/16 VPC, 0 NAT gateways, peering gone), the wg-hub kept (~$0.02/hr) for a rebuild. All of Track B is committed and reproducible from git.
  • Track C C1 base-port gate — PASSED. C0 found IBM's archived operator dead on 4.20 (no source in the repo, bundle catalog-absent) but the containerized-master pattern viable via a hand-built StatefulSet on the CE image. C1 tested the one open question: the CE master, with egoconfig setbaseport 7869 in the image's own pre-start hook, comes up cleanly on the grid's base port 7869 (LIM/KD/PEM/RS/SD/SNMP all remapped off the baked-in 17869, zero stray listeners, no crash loop) — proven in an isolated namespace, $0. Remaining for full C1: join one Windows VM to it + one symping.

■ Needs the human

  • D14–D16 call (both connectivity investigations now in): D14 keep WireGuard grid plane — now verified against the entire mesh/interconnect family; the service-plane sub-question for this specific job (the KEDA metric) has been implemented with Skupper/RHSI (see "Happening now" above) rather than left as a future weigh-in, but the broader D14 architecture call (and whether Submariner still gets adopted for anything else once the ACM hub exists) is still owed; D15 adopt the home virt cluster as ACM hub (Hive + Crossplane + GitOpsCluster); D16 registry-driven golden images via oci.arsalan.io. Also owed: oc login api.virt.na-launch.com (both saved kubeconfigs expired — needed to read the installed ACM version), a registry front-proxy check for 15–20 GB layer pushes before D16 gets wired, and verifying Argo's prune actually removes a Skupper-generated RouterAccess/Service/Route on a real gitops-driven linkAccess flip (only tested via direct oc patch so far).
  • Track C — finish C1 or park? The base-port gate passed, so the containerized-master pattern is proven viable. The remaining step is joining one Windows VM to the containerized master (cross-namespace clone + a copied firstboot pointed at it) for the full NTX64 ok + symping gate — a bounded follow-up, or park here with the base-port result banked.
  • Standing: Phase 3 (IBM/ROKS) parked on the IBM Cloud API key + VNI allowlist ticket.

Phase rail

Phase 0prereqs

Accounts, quotas, media done (AWS scope)

Everything that has lead time, before any money is spent.

  • ✓Repo scaffolded — plan, Terraform, GitOps tree, imaging workflow, agents (47 files)
  • ✓Symphony 7.3.2 Advanced Edition eval obtained — Windows + Linux installers + entitlement
  • ✓AWS checks green: 7/7 CLIs, credentials, vCPU quota 1152 ≥ 48 (no increase), rosa login + quota/SCP verify
  • ✓WS2019 eval ISO downloaded (build 17763.3650, 5 GiB); installer checksums 4/4 OK
  • ✓IBM-side prereqs parked by user decision — deferred to Phase 3 lead-time (API key, VNI ticket)
  • ✓ROSA-HCP enablement — mooted by the D9 pivot (self-hosted IPI needs no marketplace link)
Exit: phase0-checks.sh fully green — met for AWS scope (IBM checks parked).
Phase 1platform·AWS

OpenShift 4.20 (self-hosted IPI) + OCP Virt exit criteria met

IPI cluster + 1× m5zn.metal MachineSet; bootstrap Argo; operators, layer2 UDN — all reconciled from git.

  • ✓ROSA path exhausted honestly: two fail-fast applies isolated the AWS-Marketplace billing wall → D9 pivot to self-hosted IPI
  • ✓IPI install complete in 44m35s: sym-aws 4.20.27, 3+3 nodes Ready, pull secret minted from OCM token
  • ✓virt-metal MachineSet applied — m5zn.metal Machine running (backported to git); gitops tree adapted for D9 (75a8c72), Argo + app-of-apps registered
  • ✓Break-glass converge: CNV CSV Succeeded, HyperConverged Available=True, UDN allocated, metal node labeled + Ready
  • ✓Smoke VM on the metal node: create→Running 18 s, live-migratable, deleted clean
  • ✓Argo ⇄ git.arsalan.io repo access resolved via HTTPS + PAT — full GitOps loop proven by the Phase-2 pool scaling (git push → synced in ~30 s)
Exit: HyperConverged Available on the metal node; test VM boots via virtctl — MET 2026-07-09 (install 44m35s · metal→Ready ~20m · VM boot 18s).
Phase 2grid·AWS

Symphony + Windows golden image exit criteria met

RHEL master VMs silent-install Symphony from the in-cluster media repo. Two-stage image pipeline bakes WS2019 + virtio + Symphony (unjoined, sysprep-generalized); clones join at first boot.

  • ✓Management hosts up (symgrid: primary + candidate), entitlement applied, 7869–7875 verified (SSM pinning deferred to Phase 3 by design)
  • ✓Golden image ws2019-symphony DataSource READY — pre-extracted MSIs, one deterministic pipeline run
  • ✓Pool clones join unattended as NTX64 ok — clone→joined ~9.5 min, firstboot 12 s, six stacked root causes fixed + baked (join, WMI-in-specialize, LIM name, MTU, LIM DNS-identity, exec user)
  • ✓symping: 48/48 tasks on the Windows hosts, 16 SIs, 10.8 ms avg roundtrip, zero on masters
  • ✓Pool 2→1→2 via git: scale-down 31 s, clean unavail leave; fresh clone schedulable in 8m42s and took tasks immediately
Exit: clone→joined time measured (becomes the burst SLA); N VMs join/leave cleanly — MET 2026-07-10 20:57Z (join ~9.5 min · schedulable 8m42s · 10.8 ms roundtrip · 2→1→2 clean).
Phase 3burst site

ROKS + VPN + manual burst proof pending

ROKS 4.20+ (OVN chosen at create), 2–3 metal workers, OCP Virt manual install. IPsec VPN, DNS forwarding, MTU verified. One WS2019 VM on ROKS joins the AWS master over the tunnel — the single most important milestone in the plan.

Exit: stable cross-cloud join ≥24 h; task turnaround at real RTT recorded.
Phase 4burst·auto

KEDA burst demo pending

Metrics shim polls Symphony REST for demand; KEDA scales the ROKS Windows pool via /scale. Accepted gap: scale-in doesn't drain tasks — documented live in the demo.

Exit: backlog → scale-out → drain → cooldown scale-in, end to end.
Phase 5production

Hardening + procurement pending

Custom Host Factory KubeVirt provider replaces KEDA scale-in; Direct Link decision; Advanced Edition BYOL before eval expiry; licensing sign-offs; reference architecture v1.0 with everything measured.

Decision log

D1 · 2026-07-09
Staged minimal footprint

Prove the whole stack on AWS with 1 metal node before any IBM spend — metal is the dominant cost.

D2 · 2026-07-09
Windows Server 2019

Works on the eval entitlement (WS2022 needs a Fix Central fix the eval can't obtain); certified OCP Virt guest.

D3 · 2026-07-09
IPsec VPN first, Direct Link later

Hours to stand up, near-zero cost, and deliberately stresses Symphony's MTU/keepalive sensitivity early.

D4 · 2026-07-09
KEDA for the burst demo

Scale-out works via /scale; scale-in is not grid-aware (no task drain) — accepted for demo, Host Factory provider is the production path.

D5 · 2026-07-09
Advanced Edition eval license

Full product (Host Factory, Windows, multicluster) free until 2026-12-31; BYOL procurement before production.

D6 · 2026-07-09
OpenShift GitOps (Argo CD)

App-of-apps per cluster; no hand-applied cluster state outside bootstrap and break-glass.

D7 · 2026-07-09
Windows as VMs, never container nodes

Symphony needs full Windows Server; nodes stay Linux — golden image, sysprep-cloned, identity injected at first boot.

D8 · 2026-07-09
Masters as RHEL VMs on-cluster

Same layer2 UDN as compute (no NAT). Revisit native-EC2 placement if NLB exposure gets awkward in Phase 3.

D9 · 2026-07-09
Self-hosted OCP IPI replaces ROSA

ROSA HCP cluster create hard-blocked on the AWS-Marketplace billing link (console-only). IPI needs only AWS creds + a pull secret mintable from the OCM token; metal worker becomes a plain MachineSet.

Development log