|
| 1 | +# Debug & regression notes — `main` |
| 2 | + |
| 3 | +Entry point for this folder. Everything below happened against branch |
| 4 | +`debug/regression-test-main` (commit `bd32c5b`), which carries **no Go changes |
| 5 | +relative to `main`** — so all findings apply to `main`'s reconciler logic. |
| 6 | + |
| 7 | +## Timeline |
| 8 | + |
| 9 | +Read top to bottom; this is the order in which everything happened. |
| 10 | + |
| 11 | +| # | When | Cluster | What | Documents | |
| 12 | +| --- | --- | --- | --- | --- | |
| 13 | +| **0** | 2026-07-31 … 08-03 | `stackit-workload` | Initial bug investigation, before any structured run | [deletion-bug.md](deletion-bug.md) | |
| 14 | +| **1** | 2026-08-05 | `stackit-workload` | **Run 1** — bootstrapping → HA → deletion | [run1-1-bootstrapping.md](run1-1-bootstrapping.md) · [run1-2-ha-controlplane.md](run1-2-ha-controlplane.md) · [run1-3-deletion.md](run1-3-deletion.md) | |
| 15 | +| **1e** | 2026-08-07 (earlier) | `stackit-capi-test` | **Run 1, extra** — bastion, on its own cluster | [run1-4-bastion.md](run1-4-bastion.md) | |
| 16 | +| **2** | 2026-08-07 (later) | `stackit-capi-test` | **Run 2** — same three packages, from scratch | [run2-1-bootstrapping.md](run2-1-bootstrapping.md) · [run2-2-ha-controlplane.md](run2-2-ha-controlplane.md) · [run2-3-deletion.md](run2-3-deletion.md) | |
| 17 | +| **2e** | 2026-08-07 (later) | `stackit-capi-test` | **Run 2, extra** — bastion | [run2-4-bastion.md](run2-4-bastion.md) | |
| 18 | +| **3** | 2026-08-10 | — | Code review of both runs; defects written up | [bastion-bug.md](bastion-bug.md) · [machine-recreate-bug.md](machine-recreate-bug.md) | |
| 19 | + |
| 20 | +Run 1 and run 2 are **independent full runs**, not a plan and a report. Each |
| 21 | +`run*-*.md` is a complete protocol of its own run. Where the review later |
| 22 | +corrected a conclusion, the protocol is left as recorded and an |
| 23 | +`## Addendum` at the end points to the relevant bug document. |
| 24 | + |
| 25 | +## Status matrix |
| 26 | + |
| 27 | +| Topic | Run 1 (08-05/07) | Run 2 (08-07) | |
| 28 | +| --- | --- | --- | |
| 29 | +| Bootstrapping & networking | ✅ works — but cluster pre-existed, creation not evidenced | ✅ works — created from scratch, full evidence | |
| 30 | +| HA control-plane | ⚠️ partial — manual remediation needed | ⚠️ partial — same, **plus** silent VM recreate found | |
| 31 | +| Deletion & cleanup | ✅ works | ✅ works | |
| 32 | +| Bastion / jump host | ✅ works — full access path verified | ⚠️ blocked at SSH — but found a template bug | |
| 33 | + |
| 34 | +Where the runs differ, both are right about what they saw: run 2 bootstrapped |
| 35 | +from zero and so evidences cluster creation that run 1 could not; run 1 got |
| 36 | +through the whole bastion path that run 2 could not reach. Neither document |
| 37 | +supersedes the other. |
| 38 | + |
| 39 | +## Open bugs |
| 40 | + |
| 41 | +| Document | Status | Summary | |
| 42 | +| --- | --- | --- | |
| 43 | +| [deletion-bug.md](deletion-bug.md) | ⚠️ open | Deleting `Cluster`+`StackitCluster`+`Machine`s simultaneously strands machines and orphans VMs. Root cause identified, fix specified, **not implemented**. Not reproduced in either run — the safe path (`kubectl delete cluster` alone) was used and works. | |
| 44 | +| [bastion-bug.md](bastion-bug.md) | ⚠️ open | Four code-confirmed defects: security group attached twice (causes both "transient" `BastionError`s and delays the public IP), `allowedCIDRs` rules never removed (**security-relevant**), `bastionNeedsRecreate` only watches cloud-init, and `cluster-template-bastion.yaml` hardcodes `replicas: 3`. | |
| 45 | +| [machine-recreate-bug.md](machine-recreate-bug.md) | ⚠️ open | `ensureServer()` recreates a missing server unconditionally, even for a machine that had already joined; the replacement can never rejoin and just consumes a VM + volume until an operator intervenes. | |
| 46 | + |
| 47 | +**Not a code defect, but a gap:** no cluster template ships a |
| 48 | +`MachineHealthCheck`, so a node whose VM dies out-of-band is never |
| 49 | +automatically remediated at the Kubernetes level. Remediation is intentionally |
| 50 | +opt-in upstream — this is a decision to make, not a bug. Seen in both runs. |
| 51 | + |
| 52 | +**Unresolved, environment-related:** bastion SSH failed in run 2 on 3 VMs |
| 53 | +across 3 IPs while working in run 1 from the same environment. Both runs |
| 54 | +together point at certain STACKIT public IP ranges being unreachable here, |
| 55 | +independent of port — see |
| 56 | +[bastion-bug.md](bastion-bug.md#open-not-a-code-defect-the-ssh-failures) for |
| 57 | +the evidence and a falsifiable test. This also retro-explains the outbound |
| 58 | +network failure that forced run 2's bootstrapping package to be restarted. |
| 59 | + |
| 60 | +## What is confirmed working |
| 61 | + |
| 62 | +Each claim notes which run evidences it. |
| 63 | + |
| 64 | +- **Bootstrapping** (run 2, from scratch; run 1 consistent): cluster |
| 65 | + provisions, control-plane and worker nodes join and reach `Ready`, Cilium |
| 66 | + installs cleanly, deployments and pod scheduling work. |
| 67 | +- **Networking** (both runs): cross-node pod-to-pod communication works with |
| 68 | + 0% packet loss; in-cluster DNS resolves service names. |
| 69 | +- **Worker scaling** (both runs): `MachineDeployment` scales 1↔2 with no |
| 70 | + leftover Machine/StackitMachine/Node objects. Which machine a scale-down |
| 71 | + removes is arbitrary — run 1 lost the original node, run 2 the newest. |
| 72 | +- **HA control-plane** (both runs): 3-node scale-up works one machine at a |
| 73 | + time; killing a control-plane VM does not take the API server down; after |
| 74 | + the dead `Machine` is deleted manually, the LB target is removed, no |
| 75 | + VM/volume leaks, KCP creates a replacement and a new leader is elected. |
| 76 | +- **Deletion** (both runs): `kubectl delete cluster` tears down machines in the |
| 77 | + right order (workers, then control-plane) before `StackitCluster` drops its |
| 78 | + finalizer; all VMs, volumes, load balancer, security groups and public IPs |
| 79 | + are removed with no leaks, in well under a minute. Run 2 confirmed this even |
| 80 | + after extra VMs and two forced bastion recreates. |
| 81 | +- **Bastion access path** (run 1 only): SSH to the bastion, jump to a workload |
| 82 | + node, kube-api through the tunnel, and a full remote CNI install; access |
| 83 | + from a network outside the allowed CIDR is refused. |
| 84 | + |
| 85 | +## Shared prerequisites |
| 86 | + |
| 87 | +Apply to every run document; package-specific extras are noted in each. |
| 88 | + |
| 89 | +- Management cluster per |
| 90 | + [../docs/src/getting-started/management-cluster.md](../docs/src/getting-started/management-cluster.md). |
| 91 | +- STACKIT resources (network, security group, image, credentials secret) per |
| 92 | + [../docs/src/getting-started/cloud-resources.md](../docs/src/getting-started/cloud-resources.md) |
| 93 | + and [../docs/src/getting-started/credentials.md](../docs/src/getting-started/credentials.md). |
| 94 | +- `stackit` CLI authenticated **once per shell** — `.envrc` sets the key path |
| 95 | + but does not do this itself: |
| 96 | + ``` |
| 97 | + stackit auth activate-service-account --service-account-key-path "${STACKIT_SERVICE_ACCOUNT_KEY_PATH}" |
| 98 | + ``` |
| 99 | +- `.envrc` sourced **directly, never through a pipe** — `source .envrc | tail` |
| 100 | + runs it in a subshell and silently discards every export. |
| 101 | +- `CLUSTER_NAME` must be a lowercase RFC 1123 subdomain. |
| 102 | +- `.envrc` ships `STACKIT_SSH_KEY_NAME=""` — must be set explicitly before the |
| 103 | + bastion package. |
| 104 | +- Workload kubeconfig: |
| 105 | + ``` |
| 106 | + export KUBECONF_WORKERCLUSTER=/tmp/"${CLUSTER_NAME}".kubeconfig |
| 107 | + clusterctl get kubeconfig "${CLUSTER_NAME}" -n "${NAMESPACE}" > "${KUBECONF_WORKERCLUSTER}" |
| 108 | + ``` |
| 109 | + |
| 110 | +**CLI papercuts** (so they are not rediscovered): `stackit key-pair create |
| 111 | +--public-key` needs an `@`-prefixed path; `STACKIT_BASTION_SSH_KEY_NAME="${STACKIT_SSH_KEY_NAME}"` |
| 112 | +is a snapshot, not a live reference — re-export both together after any change. |
| 113 | + |
| 114 | +## Next steps |
| 115 | + |
| 116 | +1. Decide whether the unconditional server recreate in `ensureServer()` is |
| 117 | + intended; guard it so an already-joined machine is not silently replaced |
| 118 | + ([machine-recreate-bug.md](machine-recreate-bug.md)). Highest impact — it |
| 119 | + currently hides the moment an operator needs to step in. |
| 120 | +2. Remove the duplicate security-group attach in `EnsureBastion` |
| 121 | + ([bastion-bug.md](bastion-bug.md#1-the-bastion-security-group-is-attached-twice)) — |
| 122 | + eliminates both recurring `BastionError`s and a reconcile of public-IP delay. |
| 123 | +3. Make `allowedCIDRs` reconcile in both directions |
| 124 | + ([bastion-bug.md](bastion-bug.md#2-changing-allowedcidrs-never-revokes-the-old-access)) — |
| 125 | + security-relevant, and required before the CIDR-narrowing test is meaningful. |
| 126 | +4. Fix `replicas: 3` → `${WORKER_MACHINE_COUNT}` in |
| 127 | + `templates/cluster-template-bastion.yaml`. |
| 128 | +5. Land the deletion-order fix in |
| 129 | + [deletion-bug.md#fix-plan-not-yet-implemented](deletion-bug.md#fix-plan-not-yet-implemented); |
| 130 | + until then document `kubectl delete cluster` as the only supported teardown. |
| 131 | +6. Decide whether to ship a default `MachineHealthCheck`. |
| 132 | +7. Re-run the bastion access path from a network without the IP-range |
| 133 | + restriction found here, to re-verify run 1's result on current `main`. |
| 134 | + |
| 135 | +## Convention for this folder |
| 136 | + |
| 137 | +One flat Markdown document per topic, no subfolders. |
| 138 | + |
| 139 | +- `run<N>-<M>-<topic>.md` — test-run protocols. `<N>` is the run, `<M>` the |
| 140 | + position within that run, so alphabetical order equals chronological order. |
| 141 | + A third run goes in as `run3-*`. |
| 142 | +- `*-bug.md` — investigations of a specific defect; they belong to no run. |
| 143 | +- `SUMMARY.md` — this file: timeline, status, open bugs, prerequisites. |
| 144 | + |
| 145 | +Command blocks use a fenced block without a language tag containing `$ command`, |
| 146 | +a blank line, then the raw output, followed by a bolded `**Result:**` paragraph |
| 147 | +and a `---` separator. |
0 commit comments