Conversation
Restructure deployment pipeline to use immutable SHA tags during build/deploy and only promote to floating tags after successful rollouts. This prevents partial image mix-ups from pod restarts. Key changes: - Build matrix pushes only to SHA tags (<floating>-<sha12>), not floating tags - Deploy verifies staged images exist before applying overlays - Rollouts must succeed before promotion to floating tag - Failed builds/deploys auto-cleanup staged tags via cleanup-on-failure job - New utility scripts: artifactory-login.sh (with retries), wait-for-rollouts.sh (with quota/diagnostic checks), plg-pre-delete.sh (delete immutable StatefulSets) - Artifactory API calls now have --connect-timeout 30 --max-time 120 - Improved rollout diagnostics: pod status, FailedScheduling events, resource quota reporting - Updated documentation with staging model diagram and local/CI parity examples
check_namespace_quota compared hard/used quota values with
float(str(v).rstrip('m')), which is unsound because ResourceQuota
status does not guarantee matching units between `hard` and `used`.
Verified against live fd34fb-test:
- requests.cpu hard=4 used=3230m was read as 3230/4 = 80750%, a false
positive that failed the "Restart deployments and wait" step (and via
cleanup-on-failure could delete the just-applied SHA tag) on every
deploy.
- requests.memory (Gi/Mi) and requests.storage (64Gi/64Gi, genuinely
100% full) raised ValueError and were silently skipped, so the actual
resource-exhaustion case this check targets went undetected.
Replace with a proper resource.Quantity parser handling binary
(Ki..Ei) and decimal SI (n,u,m,k,M,G,T,P,E) suffixes, normalizing both
sides to a common base before comparing. Unknown/unparseable values are
skipped rather than miscompared.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed a fix for the namespace quota parser ( Bug:
Fix: proper This is scoped to the parsing bug only. Two related items from my review are still open and I left them untouched here:
|
cleanup-on-failure deleted the run's SHA tag whenever build OR deploy failed. But the deploy job runs `oc apply` early (step ~3 of ~12), pinning the live Deployments to `:<sha-tag>`. A failure in any later step (PLG helm timeout, rollout timeout, promote, secret creation) then triggered deletion of the tag those applied Deployments reference — causing ImagePullBackOff on the next pod restart with no floating fallback, since Deployments no longer reference the floating tag. Restrict cleanup to build failures. A build failure means `deploy` was skipped, so `oc apply` never ran and the SHA tag is referenced by nothing in the cluster — safe to delete (this is also the primary Problem-2 case: orphan manifests from a partial matrix push). Deploy failures are excluded: the tag is either in use (must keep) or, for a pre-apply verify failure, reclaimed by the routine --keep rotation on the next successful run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed a fix for the second issue from the review — Problem: the deploy job runs Fix: gate cleanup to build failures only: needs: [metadata, build]
if: always() && needs.build.result == 'failure'A build failure means Both review fixes are now on the branch:
Still open (lower priority, untouched):
|
check_namespace_quota gated the deploy on every ResourceQuota in the namespace being under 95%. In the shared fd34fb-test namespace this produces false blocks: a quota can be at its limit because of other instances (e.g. storage-quota at 64Gi/64Gi), and a rollout-restart of already-sized deployments requests no new storage — so the gate would fail a deploy for resources it never consumes. Rely on `oc rollout status --timeout=300s` instead: genuine resource exhaustion (pods stuck Pending / FailedScheduling) surfaces as a hard rollout-timeout failure with full diagnostics, which is the correct and sufficient fix for the "fails silently" problem. This also removes the quota-quantity parser added earlier, since the function it lived in is gone. Right-sizing namespace capacity (HPA tuning so instances don't cap out and don't scale unnecessarily) is tracked separately. Docs updated to drop the pre-check claim while keeping the rollout-timeout / diagnostics behaviour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Removed the pre-rollout namespace quota check ( Why: even with correct quantity parsing, Instead: rely on This also supersedes the earlier quota-quantity parser fix ( Follow-up: the real capacity fix (right-sizing HPAs so instances don't cap out the namespace, and tuning them so they don't scale up unnecessarily) is tracked in AI-1700. Docs ( Remaining open item from the review (lower priority): retry hardening only wraps |
Resolve docs-md/operations/AUTO_DEPLOY.md (relocated from openshift-deployment/ on develop): keep the staged-image deploy flow, fold in develop's accurate operational detail (secret names, alert-rule generation, migrate-db init container, DEPLOY_PLG gating), and correct the cleanup-on-failure description + staging diagram to match the build-only cleanup gate.
Problem 4 (intermittent Artifactory `Client.Timeout`) previously only wrapped `docker login`. The deploy job's staged-image existence check and the `docker buildx imagetools create` promotion still made un-retried registry calls, so a transient timeout there failed the deploy. Add a shared `with_retries` helper (scripts/lib/retry.sh, 3 attempts / 15s backoff) and use it for: - the verify-images check (accepts only HTTP 200; retries transient 000/5xx, still fails fast-enough on a genuinely-missing 404 after attempts are exhausted), - the CI promote step, and - the local `oc-build-push.sh --promote` path (CI/local parity). Docs updated to describe the broadened retry coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Finished Problem 4 — the retry hardening now covers the remaining un-retried registry calls ( Previously only
Verified the helper with unit-style tests (immediate success, permanent failure propagating the real exit code, and flaky fail-twice-then-succeed) — which caught a bug in the first draft where the exit status was captured after With this, all five ticket problems are addressed in code. Remaining items are the two the PR body explicitly defers (Problem 3 pvcSize-change guard; Problem 5 |
Restructure deployment pipeline to use immutable SHA tags during build/deploy and only promote to floating tags after successful rollouts. This prevents partial image mix-ups from pod restarts.
Key changes:
Summary
AI-1607
Changes
Deploy Pipeline Hardening Plan
This ticket covers four independent problems in the same pipeline (
.github/workflows/deploy-instance.yml). They share a common theme: partial or silent failure leaves the cluster or registry in an inconsistent state.flowchart TD subgraph current [Current flow - risky] B1[Build matrix pushes floating tag] --> D1[Deploy uses floating tag] D1 --> R1[Rollout timeout = warning only] R1 --> C1[Cleanup only on full success] end subgraph proposed [Proposed flow] B2[Build pushes SHA tag only] --> D2[Deploy uses SHA tag] D2 --> R2[Rollout must succeed] R2 --> P2[Promote SHA to floating tag] P2 --> C2[Scheduled cleanup + rotation] B2 -.->|on failure| F2[Delete run SHA tags + orphan cleanup] D2 -.->|on failure| F2 R2 -.->|on failure| F2 endProblem 1: Images should be staged, not immediately become the pull target
Root cause
Today, the build matrix pushes to the floating tag (
bcgov-di,bcgov-di-test, or branch-derived tag) during build:Deploy then references the same floating tag via Kustomize (
--image-tag "${IMAGE_TAG}"at line 268). BecauseimagePullPolicy: Alwaysis set on app containers, any pod restart during or after a partial build can pull a mix of new and old images across services. Prod already creates immutable SHA tags, but test/dev do not, and the floating tag is still overwritten before deploy succeeds.Proposed staging model: build-to-SHA, deploy-from-SHA, promote-on-success
<instance>-<sha12>only<instance>-<sha12>chesAdapter.image.tag<instance>(floating)<instance>-<sha12>tagsoc set image(existing docs)Metadata changes (
deploy-instance.ymlmetadata job, lines 47–95):sha-tagfor every environment:develop→bcgov-di-test-${GITHUB_SHA::12}main→bcgov-di-${GITHUB_SHA::12}(unchanged pattern)workflow_dispatch→${IMAGE_TAG}-${GITHUB_SHA::12}image-tagas the floating/live tag name (used only for promotion).Build job changes:
sha-tagduringdocker/build-push-action(remove floating tag from build tags).verify-buildsjob (or deploy precondition step) confirms all four services (backend-services,frontend,temporal,ches-adapter) exist in Artifactory at the SHA tag before deploy proceeds.Deploy job changes:
needs.metadata.outputs.sha-tagtogenerate_instance_overlayand Helm--set chesAdapter.image.taginstead ofimage-tag.New step: Promote images to floating tag (after successful rollout, before existing cleanup):
Requires
docker/setup-buildx-actionin the deploy job (or a dedicated promote job with registry login).Local script parity:
scripts/oc-build-push.sh: add--sha-tagor default to appending-$(git rev-parse --short=12 HEAD)for staging pushes; document--promoteto update floating tag after manual deploy.scripts/lib/image-tag.sh: add helper for SHA tag computation mirroring CI.scripts/oc-deploy-instance.sh: accept--image-tagas deploy tag (already does); document that callers should pass SHA tag and promote separately.Docs: Update
docs-md/openshift-deployment/AUTO_DEPLOY.mdtagging table and flow diagram.Problem 2: Cleanup Artifactory artifacts on failure
Root cause
Cleanup runs only on full success:
Failed or partial builds leave SHA manifests, orphan
sha256__*folders, and stale_uploads/blobs until a later successful run.Proposed changes
New script
scripts/artifactory-delete-run-tags.sh:--tag <sha-tag>,--delete, service list (default: all four images).${image}:${sha-tag}via Artifactory Docker API (reuse DELETE pattern fromscripts/artifactory-cleanup.shlines 200–207).artifactory-cleanup.sh --delete(orphan + uploads phases) to reclaim unreferenced layers.New workflow job:
cleanup-on-failure:continue-on-error: trueso cleanup itself does not mask the original failure.Keep existing success-path cleanup (prod rotation
--keep 3 --match) unchanged, running after promotion.Docs: Add section to
scripts/ARTIFACTORY_README.md.Problem 3: "Delete immutable PLG StatefulSets before upgrade" — logging disruption?
The step at lines 446–463 deletes
lokiandprometheusStatefulSets with--cascade=orphan:helm upgrade --wait, Loki may restart; Promtail buffers or backs offRecreatestrategyThe delete step itself is low-disruption (orphan keeps pods running); disruption occurs during Helm reconciliation, not at delete time.
Proposed changes (documentation + small hardening, not removal)
docs-md/PLG_DEPLOYMENT_INTEGRATION.mda new "Upgrade impact on logging" section with the table above and expected duration (Helm--timeout 300sbound).alertmanagerto the pre-delete loop — it also has immutablevolumeClaimTemplatesindeployments/openshift/helm/plg/templates/alertmanager-statefulset.yamlbut is currently omitted.scripts/oc-deploy-instance.shbefore its Helm upgrade (local deploys currently lack this workaround and can fail on PVC size changes).loki.pvcSizeorprometheus.pvcSizevs current StatefulSet spec — avoids unnecessary Loki restarts on no-op upgrades. Defer unless scope allows.No change to PLG being required for deploy; the ticket asks whether it causes disruption
Problem 4: Intermittent "Login to Artifactory" timeouts
Root cause
docker/login-action(line 135) has no retry and no explicit timeout. The error (Client.Timeout exceeded while awaiting headers) is a transient network/registry issue.Proposed changes
CI — wrap login with retry using
nick-fields/retry(or equivalent shell loop):Apply the same pattern to the deploy job if/when it needs registry access for
imagetools createpromotion.Local — add retry loop (3 attempts, 15s backoff) around
docker logininscripts/oc-build-push.sh.Artifactory API scripts — add
--connect-timeout 30 --max-time 120tocurlcalls inscripts/artifactory-cleanup.shandscripts/artifactory-usage.shfor consistency.Problem 5: Rollout failures fail silently when namespace lacks resources
Root cause
Same pattern in
scripts/oc-deploy-instance.shlines 451–452. Timeouts and quota exhaustion (FailedScheduling, Pending pods) produce warnings only; the job exits 0 and prints success URLs — contradicting US-007 acceptance criteria.Proposed changes
Extract shared rollout waiter — new
scripts/lib/wait-for-rollouts.sh:Diagnostics on failure (reuse patterns from
scripts/oc-list-instances.shdetermine_instance_status):oc get pods -l app.kubernetes.io/instance=<instance> -o wideoc get events --field-selector reason=FailedSchedulingoc describe resourcequotawhen quota-related events found::error::(not just::warning::) with deploy nameWire into:
.github/workflows/deploy-instance.yml— replace inline rollout loop; fail the step on any rollout failure.scripts/oc-deploy-instance.sh— same; exit non-zero before printing "Deploy finished".scripts/rotate-prod-secrets.sh— add rollout wait and include restart failures in exit guard (lower priority, separate from this ticket).Pre-deploy quota check (recommended, lightweight):
oc describe resourcequotaand warn/fail if any resource is at ≥95% of hard limit (CPU, memory, persistentvolumeclaims).docs-md/openshift-deployment/MANUAL_LOAD_TEST_INSTANCE.md.Ordering with image staging: Promotion to floating tag must happen after rollout succeeds, so a failed rollout never updates the live pull tag.
Testing plan
develop, confirm build pushes onlybcgov-di-test-<sha>, deploy uses SHA, floating tag unchanged until rollout succeeds, then promotedoc delete podon one deployment; confirm it pulls previous floating tag, not staged SHA::error::and scheduling events in logDEPLOY_PLG=true; confirm Loki brief unavailability documented; verify alertmanager included in pre-deleteoc-build-push.sh+oc-deploy-instance.shwith SHA tag flowChecklist
By submitting this pull request, I acknowledge that I have attempted to meet the following: