fix(workspace-minio): make minio-sync backup run and mirror real data - #1119
Conversation
minio-sync has failed on every run since it was authored and has never once succeeded (it is absent from kube_cronjob_status_last_successful_time; the CronJob status carries only lastScheduleTime). Root causes, all confirmed in-cluster with disposable probe Jobs: - The minio/mc image ENTRYPOINT is `mc`. The manifest set `args:` but not `command:`, so Kubernetes ran `mc /bin/sh -c "<script>"` and the shell never executed. Running as uid 1000 it failed even earlier, at `mkdir /.mc: permission denied` (HOME defaulted to `/`). - Credentials came via `envFrom` on a Secret whose keys are hyphenated (access-key / secret-key) — not valid env-var identifiers, so they were silently dropped and $MINIO_ROOT_USER/$MINIO_ROOT_PASSWORD were always empty. - The mirror targeted `local/workspace` (a bucket that does not exist) and `s3/prophet-workspace-backup` (an alias never configured; no S3 backup secret exists in the namespace), the whole line masked by `|| true` — so even a credential-fixed job would exit green having copied nothing. Fix: override `command:` so the shell runs; set HOME=/tmp; map credentials via explicit secretKeyRef (as the workspace-minio Deployment already does); mirror every real bucket to a dedicated workspace-minio-backup PVC; drop `|| true` so a mirror failure fails the job. fsGroup:1000 is scoped to the destination PVC only — the source is read over S3 and is never mounted, so it cannot rewrite source ownership. Verified in-cluster (socioprophet): with these fixes a run authenticates, enumerates the real buckets (zot ~20G, lifecycle-warden) and mirrors real objects S3->filesystem (lifecycle-warden audit chunks, 70.50 KiB). The full 20G copy onto the 100Gi PVC was not run in-session to keep the cluster clean and within the spend cap.
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Fixes the workspace-minio nightly minio-sync CronJob so it actually executes and mirrors real MinIO bucket data into an in-cluster backup PVC (instead of silently “succeeding” while copying nothing).
Changes:
- Override container
commandto run the shell script, set writableHOME, and map MinIO credentials viasecretKeyRef. - Mirror all discovered buckets from the MinIO endpoint to a mounted backup PVC and fail loudly on mirror errors.
- Add a dedicated
workspace-minio-backupPVC (100Gi) with ArgoCD prune disabled.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # NOTE: the minio/mc image ships no awk/find, so bucket parsing is POSIX shell only. | ||
| mc ls local > /tmp/buckets.txt | ||
| buckets=0 | ||
| while read -r line; do | ||
| b=${line##* }; b=${b%/} | ||
| [ -n "$b" ] || continue | ||
| echo "mirroring bucket: $b" | ||
| mc mirror --overwrite --remove "local/$b" "/backup/minio/$b" | ||
| buckets=$((buckets + 1)) | ||
| done < /tmp/buckets.txt |
| # namespace), the whole line masked by `|| true`, so it could only ever exit | ||
| # green having moved nothing. There is deliberately no `|| true` below: a mirror | ||
| # failure now fails the job loudly. | ||
| # NOTE: the minio/mc image ships no awk/find, so bucket parsing is POSIX shell only. |
| mc mirror --overwrite --remove "local/$b" "/backup/minio/$b" | ||
| buckets=$((buckets + 1)) | ||
| done < /tmp/buckets.txt | ||
| echo "MinIO mirror complete: ${buckets} bucket(s); backup now $(du -sh /backup/minio | cut -f1) at /backup/minio" |
|
Merge-gate disposition (Copilot 3-channel + adversarial pass) Core fix verified: the old manifest ran Rejected — Acknowledged, non-blocking: Verdict: MERGE-READY. |
Summary
minio-sync(the nightlyworkspace-miniobackup CronJob) has failed on every run since it was authored and never once succeeded — it is absent fromkube_cronjob_status_last_successful_time, and its CronJob status carries onlylastScheduleTime, nolastSuccessfulTime. Nothing alerted, same class as themail-backupregression.Root cause (all confirmed in-cluster with disposable probe Jobs, socioprophet ns)
Three stacked defects, plus a phantom destination:
minio/mcimageENTRYPOINTismc. The manifest setargs:but notcommand:, so Kubernetes ranmc /bin/sh -c "<script>"— mc treats/bin/shas a subcommand and the shell never runs. As uid 1000 it failed even earlier:mc: <ERROR> Unable to save new mc config. mkdir /.mc: permission denied(HOME defaults to/).$HOME/.mc; uid 1000 cannot write/. Fixed withHOME=/tmp(verified writable by uid 1000).envFromonminio-credentials, whose keys are hyphenated (access-key/secret-key) — invalid env-var identifiers, silently dropped — so$MINIO_ROOT_USER/$MINIO_ROOT_PASSWORDwere always empty. Theworkspace-minioDeployment already maps them correctly viasecretKeyRef; this now does the same.local/workspace(no such bucket — real buckets arezot~20G andlifecycle-warden) ands3/prophet-workspace-backup(ans3alias never configured; there is no S3 backup secret anywhere in the namespace), all masked by|| true. Even a credential-fixed job would have exited green having copied nothing.Fix
command: ["/bin/sh","-c"]so the shell runs.HOME=/tmp.secretKeyRef(access-key→MINIO_ROOT_USER,secret-key→MINIO_ROOT_PASSWORD).workspace-minio-backupPVC; POSIX-shell bucket loop (the image ships noawk/find).|| trueso a mirror failure fails the job loudly, and print bucket count +du -shof the backup so a green-over-nothing run is impossible.fsGroup: 1000scoped to the destination PVC only — the MinIO source is read over S3 and never mounted, so fsGroup cannot rewrite source ownership.workspace-minio-backupPVC sized 100Gi to match the source PVC (a backup smaller than its source is a latent ENOSPC/silent-fail as the source grows).Verification (cluster evidence; CI is spend-capped)
Disposable probe Jobs (all deleted afterward) reproduced the failure and proved the fix:
mkdir /.mc: permission denied.command:override +HOME=/tmp+secretKeyRefcreds →mc alias set ... Added 'local' successfully, enumerated real buckets (zot,lifecycle-warden), and mirrored real objects S3→filesystem (lifecycle-warden/audit/chunk-*.json,Total: 70.50 KiB, Transferred: 70.50 KiB).zotcopy onto the 100Gi PVC was not run in-session, to keep the cluster clean and within the spend cap. The transfer mechanism is identical to the verified run;fsGroup:1000writability on a fresh GCE PD is independently proven by the siblingmail-backupjob on this same cluster.kubectl kustomizebuilds for base +p0-lab, andkubectl apply --dry-run=serveraccepts the CronJob and the new PVC.Notes for reviewers
mail-backuplane: that lane must keep itsfsGroupoff the source (mount source read-only, write only to the destination PVC). This minio-sync design already follows that rule (source via S3, never mounted).