Harbor as an ImageMirror destination
This documents the in-development version. Switch to v2.3 for the latest stable release.
Harbor refuses tag deletion through the registry API: any DELETE /v2/<name>/manifests/<tag> answers 400 (goharbor/harbor#24213). With the default cleanup of an ImageMirror, kuik therefore deletes nothing on Harbor: each deletion fails with an ImageDeletionFailed event, and Ready goes False with reason RegistryDeleteUnsupported. Pick one of the 2 setups below, depending on who else writes to the destination.
Who writes to the destination
Section titled “Who writes to the destination”The destination is the Harbor project, or the path inside it, that destination.path names: harbor.example.com/kuik-prod/ writes every copy under the project kuik-prod. kuik writes there with the manage credential, which needs to push, pull and delete in that project.
- The
ImageMirroris the only writer: one cluster, oneImageMirror, no other tool and no tag pushed by hand under that path. Use setup A. - Anything else writes there: several clusters sharing the destination, another
ImageMirroron the same path, a CI pipeline or a replication rule. Use setup B.
Setup A: the ImageMirror is the only writer
Section titled “Setup A: the ImageMirror is the only writer”Set cleanup.deleteByDigest: true. kuik then deletes a tag by deleting the manifest it points at, which Harbor accepts, and keeps a manifest another of its own tags still needs. See Deleting by digest for how it works.
apiVersion: kuik.enix.io/v1alpha1kind: ImageMirrormetadata: name: harbor-mirrorspec: destination: path: harbor.example.com/kuik-prod/ manage: auth: secretRef: name: harbor-manage cleanup: retention: 168h deleteByDigest: trueDeleting a manifest frees no space by itself: Harbor’s own garbage collection removes the blobs no manifest uses any more, so schedule it on the Harbor side.
Setup B: the destination is shared
Section titled “Setup B: the destination is shared”Turn kuik’s cleanup off, and let Harbor’s tag retention delete the tags of this cluster.
spec: cleanup: enabled: falseEvery tag an ImageMirror writes ends with _<clusterID>, the cluster ID of the cluster that wrote it: nginx:1.27 is copied as nginx:1.27_focused-turing-7f3a. That suffix tells this cluster’s tags apart from the others’. Read the ID of this cluster:
kubectl get configmap kuik-cluster-id -n kuik-system -o jsonpath='{.data.clusterID}'Then add tag retention rules to the Harbor project:
- A rule for this cluster’s tags: repositories matching the destination path inside the project (
**when theImageMirrorowns the whole project), tags matching*_<clusterID>, and a generous condition, such as “retain the most recently pushed N artifacts” per repository or a long “pushed within the last N days” window. Size it to how long a copy must outlive its origin. Retention rules cannot see whether pods still need a copy. A rule on pull time misses it: underrewritePolicy: OnFailure, pods pull the origin while it answers, so Harbor sees a pull of the copy only during an outage, and kuik’s own reads of the destination areHEADs, which Harbor does not record as pulls. Such a rule can delete the copy the next outage needs. - A rule for every other tag: Harbor retention rules list what to keep, and a tag no rule retains is deleted when the policy runs. Add a rule that always retains the tags excluding
*_<clusterID>, so the policy never touches the tags of the other clusters or tools.
The rules of a project add up: a tag is kept when any rule retains it. When several clusters share the project, they all take setup B, and the project holds one policy for all of them: one rule per cluster on its own suffix, each with its own condition, and one rule that always retains the tags carrying none of these suffixes. A rule for this cluster never matches another cluster’s suffix: only that cluster knows which of its tags its pods still use.
A tag the retention deletes while a pod of this cluster still runs its image is written again after the next destination pass (mirror.destinationScan.interval), only when its origin or an ImageAlternative covering it answers: kuik reads the image from that source, then writes the tag alone when its manifest is still at the destination, or a full copy with an ImageRecopied warning. A copy deleted while its origin is down is not restored until a source answers again: that outage is what the mirror was for. Until then, new pods are routed past the missing copy, and a node pulling it again for a pod already rewritten to it fails.
Deleting the ImageMirror
Section titled “Deleting the ImageMirror”With cleanup.enabled and neither setup, a deleted ImageMirror on Harbor stays Terminating: kuik keeps trying to delete its tags, and Harbor keeps refusing. Set cleanup.enabled: false on it to release it, leaving its tags to Harbor’s retention, or cleanup.deleteByDigest: true when it is the only writer of its destination. See An ImageMirror stays Terminating.