Install phases
Imperative bootstrap, then GitOps. You run openshift-install, one MachineSet, and two
bootstrap scripts by hand; everything after that reconciles from gitops/ via Argo CD.
This is exactly how the reference cluster sym-aws was built — not an idealized rewrite.
|
Honesty up front
Phase 0 (prereqs) and Phase 1 (OpenShift + OpenShift Virtualization) are proven — IPI install, HyperConverged, the metal MachineSet, and the CPU Manager What is NOT yet proven: the Windows compute VMs joining the EGO grid. Four compounding root causes were found and fixed (see Build traps) and the join was demonstrated live against running clones, but as of the last logged handoff a WinRM password-desync regression was blocking re-verification. Phases 3-5 (IBM Cloud burst, the KEDA demo, the custom Host Factory provider) are designed, not built. Reference cluster: |
At a glance — the order that works
§1 phase0-checks.sh -> §2 openshift-install create cluster (IPI) -> §3 virt-metal MachineSet + CPU Manager KubeletConfig -> §4 bootstrap-gitops.sh -> §5 create-secrets.sh -> §6 upload-installers.sh -> §7 Linux management hosts (masters) -> §8 golden image pipeline (2 stages + 1 one-time MSI extract) -> §9 traps (read before you hit them -- xref:41-traps.adoc[full catalog]) -> §10 Windows compute pool + join (open item)
1. Prerequisites
Run until green:
scripts/phase0-checks.sh
It checks local tooling (aws, rosa, oc, virtctl, terraform, kustomize,
ibmcloud), AWS credentials and EC2 standard-vCPU quota >= 48 (an m5zn.metal alone
is 48 vCPU — request an increase before Phase 1 if this fails), ROSA/OCM login
(harmless — see the D9 note below), IBM Cloud login and bx2d.metal visibility (a
Phase 3 lead item — open the VNI-allowlist ticket with IBM now, it is the longest
lead-time item in the whole plan even though the burst cluster comes later), and local
install media in installers/ (the Symphony eval installers, entitlement file, and the
WS2019 Evaluation ISO, all gitignored, never uploaded anywhere).
Secrets that must exist out-of-band before you start, never in git: OCM token, AWS credentials, IBM API key, Windows local-admin password, Symphony admin password, the entitlement file, and (Phase 3) a VPN PSK.
|
D9 platform note
The plan originally targeted ROSA HCP. |
2. OpenShift on AWS (IPI)
mkdir -p ~/clusters/sym-aws
openshift-install create cluster --dir ~/clusters/sym-aws
# stable-4.20, cluster name sym-aws, base domain a public Route53 zone,
# us-east-1, installer defaults (3 control-plane + 3 worker), OVN-Kubernetes CNI.
Measured: 44m35s end to end (3 masters + 3 workers Ready, console up). Install
artifacts stay in ~/clusters/sym-aws/ — this directory is outside the repo and must
never be committed.
export KUBECONFIG=~/clusters/sym-aws/auth/kubeconfig
oc whoami # confirm cluster-admin (kubeadmin)
3. Bare-metal MachineSet + CPU Manager
OpenShift Virtualization requires bare metal. The metal MachineSet is committed manifest, not templated Terraform — it was hand-derived from the installer’s own worker MachineSet after IPI created the cluster:
oc apply -f gitops/clusters/rosa/virt-metal-machineset.yaml
oc get machineset -n openshift-machine-api -w # wait for 1/1 Ready
m5zn.metal, replicas: 1 by design (roughly $4/hr — don’t scale without a cost
decision). Measured Machine-provision-to-node-Ready: ~20 min. The template labels
the node node-role.symphonygrid/virt: "" — everything downstream selects on that
label, not on instance type directly.
Once the operators land (via GitOps, §4), also apply the CPU Manager KubeletConfig — this is a hard scheduling prerequisite, not an optimization:
oc apply -f gitops/clusters/rosa/virt-metal-cpumanager.yaml
KubeVirt’s d1. instance types (used for every Windows compute VM, §10) bake in
dedicatedCPUPlacement: true, which hard-requires the node’s kubelet to run
cpuManagerPolicy: static. Without it, every VM using a d1. instancetype is
permanently ErrorUnschedulable — while the Argo Application still shows
Synced/Healthy, because the VMs silently never schedule (see
Build traps). It is scoped to a dedicated virt-metal MachineConfigPool
(selecting only the metal-labeled node), specifically so it does not force a rolling
reboot of the three non-metal workers.
4. Bootstrap GitOps
export REPO_PAT_PATH=~/clusters/git-pat # a file containing a PAT for the repo, chmod 600
scripts/bootstrap-gitops.sh rosa
(The argument is rosa — a naming legacy from before the D9 pivot; it now means "the
AWS-side cluster," not the ROSA platform.) The script installs the OpenShift GitOps
operator, grants the Argo application controller cluster-admin (needed to manage
Operators/HyperConverged/VMs), creates the git repository credential over HTTPS+PAT
(Argo’s SSH access hit a source-IP restriction on this project — HTTPS+PAT was the
durable fix), and applies the rosa app-of-apps.
oc get applications -n openshift-gitops -w
Argo then syncs gitops/clusters/rosa/: the OpenShift Virtualization operator
HyperConverged, the Pipelines operator, the symphony namespace, the layer2 UDN
(no NAT, base port 7869), the StorageProfile override (see Build traps), and
the CPU Manager KubeletConfig/MachineConfigPool from §3.
Exit: HyperConverged Available on the metal node; virtctl can start a test VM
(measured boot time: 18s create-to-Running).
5. Secrets
Nothing in §4/§6/§7 works without secrets, and none of them are in git.
scripts/create-secrets.sh renders and creates every Secret/ConfigMap that must not
live in the repo, from environment variables — names only, values never committed,
never printed in this doc:
| Env var | Required | Purpose |
|---|---|---|
|
yes |
Symphony OS-level admin account password |
|
yes |
Windows local Administrator password (baked into sysprep |
|
no (defaults |
primary master hostname |
|
yes |
primary master’s grid-UDN IP |
|
no |
candidate master, if present |
|
no (auto-generated if unset) |
EGO’s built-in web/REST |
export SYM_ADMIN_PASSWORD=... WIN_ADMIN_PASSWORD=... SYM_MASTER_IP=10.10.0.31 \
SYM_MASTER2_NAME=sym-master2 SYM_MASTER2_IP=10.10.0.34
scripts/create-secrets.sh
This creates: symphony-entitlement (the eval entitlement file), symphony-admin /
win-admin (passwords), sym-master1-cloudinit / sym-master2-cloudinit (rendered
cloud-init), and win-sysprep-join (the per-VM sysprep ConfigMap — rendered
unattend.xml + firstboot-symphony.ps1).
|
Regeneration hazard
If |
6. Upload installer media
scripts/upload-installers.sh
Copies the Symphony eval installers and the WS2019 eval ISO from the gitignored local
installers/ directory onto the installer-repo PVC (an in-cluster nginx pod) — the
media server every VM pulls install bits from over the grid UDN, since none of these
hosts have outside internet or a Red Hat subscription. installer-repo must already be
synced by Argo (§4) before this script has a pod to copy into. A RHEL9 net-tools RPM
is also served this way — not an IBM artifact, required because the Symphony 7.3.2
Linux installer hard-requires netstat, absent by default on RHEL9.
7. Linux management hosts (masters)
gitops/apps/symphony-mgmt/ (synced automatically once create-secrets.sh has
produced the two *-cloudinit Secrets it depends on) creates 2x RHEL 9 VMs on the grid
UDN: sym-master1 (primary) and sym-master2 (candidate). Their cloud-init:
-
Pulls the Symphony Linux installer from
installer-repo. -
Silent-installs with the license accepted and the eval entitlement, under a dedicated
egoadminOS account. -
Applies
symphony/config/fragments (keepalives + dynamic-host settings, pinned SSM port ranges) viaapply-config.sh. -
sym-master2joinssym-master1.
Exit: egosh resource list shows both sym-master1 ok and sym-master2 ok with
live load metrics, not dashes. Port matrix verified live via ss: sym-master1
listens the full 7-port block TCP 7869-7875 + UDP 7869; sym-master2 (non-primary)
listens TCP 7869 + 7871 + UDP 7869 only — expected, not drift. If `sym-master2’s
resource row shows all dashes, check master-to-master DNS first — see
Build traps.
8. Windows golden image pipeline
The contract: the golden image is fully baked except identity — Windows Server 2019, virtio, qemu-guest-agent, Symphony 7.3.2 compute host installed but not joined, sysprep-generalized. Per-VM identity (hostname, master addresses, join) happens at clone time in the sysprep specialize pass, never baked into the image.
WS2019 eval ISO ---+
virtio-win ISO -----+ Stage 1: windows-efi-installer Tekton pipeline (upstream
autounattend.xml ---+ KubeVirt community example, resolved via the Tekton hub
resolver -- NOT a Red Hat-productized artifact)
-> vanilla golden: WS2019 + virtio + qemu-guest-agent
(measured 15m56s)
output: DataSource ws2019-base
ws2019-base --------+ Stage 2: customize task (authored in this repo)
egocore/soamcore msi boot temp VM from ws2019-base
(pre-extracted, -> install-symphony.ps1 (msiexec /qn against
served by installer-repo) pre-extracted MSIs)
-> sysprep /generalize /oobe /shutdown
output: DataSource ws2019-symphony
# render + serve the sysprep ConfigMaps the pipelines point at (never an external URL):
scripts/render-image-pipeline-media.sh
# stage 1 (deliberately NOT in the app's kustomization -- Argo must never
# auto-submit a pipeline run; each PipelineRun documents its own prereqs):
oc create -f gitops/apps/image-pipeline/pipelinerun-stage1.yaml
oc get pipelinerun -n symphony -w
# stage 2, once ws2019-base is Ready:
oc create -f gitops/apps/image-pipeline/pipelinerun-stage2.yaml
oc get pipelinerun -n symphony -w
One-time MSI extract
symeval-7.3.2.0.exe --extract is a real 3-click InstallShield wizard, not a headless
extraction — automating it inside every stage-2 run proved too flaky (see
Build traps). The durable fix, executed once: extract the two compute-host MSI
packages on a throwaway scratch VM and publish them to installer-repo:
scripts/extract-symphony-msi.sh
Publishes egocore-4.0.0.0.msi (117 MiB) and soamcore-7.3.2.0.msi (221 MiB) to
installer-repo via a WebDAV PUT endpoint. install-symphony.ps1 then just
msiexec /qn`s the two pre-published files directly — zero self-extraction, zero UI
automation, in the standing pipeline from here on. Re-run only if `symeval-7.3.2.0.exe
itself changes (e.g. a fix pack). Measured: extract-VM run ~13 min wall clock
(launch to clean shutdown, one manual click required); egocore upload ~63s, soamcore
upload ~118s.
Verify
oc get datasource ws2019-base -n symphony
oc get datasource ws2019-symphony -n symphony
virtctl console <stage-2-vm> # install-symphony.ps1's progress lines live off COM1
|
Not a Red Hat-productized artifact
The |
9. Traps
Read Build traps before you run any of the above for the first time — every row in that catalog cost real hours on the reference build.
10. Windows compute pool + join (open item)
gitops/apps/symphony-compute-win/ is a VirtualMachinePool cloning the
ws2019-symphony golden DataSource, using the d1 instancetype family (dedicated
CPU — required both for AWS Windows License-Included compliance and to satisfy §3’s
CPU Manager KubeletConfig prerequisite). Each replica’s sysprep specialize pass runs
firstboot-symphony.ps1: enable WinRM first, open inbound EGO firewall, rewrite
EGO_MASTER_LIST in ego.conf directly, start the LIM service.
Measured on real reclones: VM Running phase reached ~2-3 min from delete;
AgentConnected=True (qemu-guest-agent up) by ~5 min. Full clone-to-joined
timing is the actual Phase-2 exit measurement and is genuinely open — four compounding
root causes were fixed and the join was demonstrated live against running clones, but a
WinRM password-desync regression was blocking re-verification as of the last logged
handoff. This is the one unproven step in an otherwise fully-verified pipeline.
Timings and costs measured on the live build
| Step | Measured | Source |
|---|---|---|
IPI cluster install (§2) |
44m35s |
phase-log "Phase 1 workflow run 1" |
Metal Machine provision → node |
~20 min |
phase-log "Phase 1 EXIT CRITERIA MET" |
HyperConverged converge |
~5 min |
same |
Test VM boot (create → |
18s |
same |
Golden image stage 1 (§8) |
15m56s |
phase-log "stage-1 GREEN" |
MSI-extract scratch-VM run (§8, one-time) |
~13 min |
phase-log "MSI-carve pivot EXECUTED" |
Compute VM clone → |
~2-3 min |
phase-log "compute VMs recloned…" |
Compute VM |
~5 min |
same |
Running cluster cost (3+3 |
~$5.2/hr |
phase-log, HANDOFF-windows-join.md |
Scale |
saves ~$4/hr, re-provisions in ~20 min |
same |
Continue to Build traps — the catalog every gotcha above links back to.