Custom Resource Definitions
This documents the in-development version. Switch to v2.3 for the latest stable release.
kuik is configured with three cluster-scoped custom resources of the kuik.enix.io/v1alpha1 API group:
| Kind | Purpose |
|---|---|
ImageAlternative | Routes images to alternative repositories holding the same images, as fallbacks or ahead of the original |
ImageMirror | Copies images to a registry you own, and routes to the copy |
ImageMonitor | Checks that the images running in the cluster are still available upstream, and reports the ones that fail or drift |
Every field below is validated by the API server (schema, enums, CEL rules), so an invalid resource is rejected at kubectl apply with a message naming the rule it broke. The full schema is available with kubectl explain imagealternatives.kuik.enix.io --recursive and its two siblings.
Common fields
Section titled “Common fields”Selecting workloads
Section titled “Selecting workloads”A resource applies to pods, never to images directly. podSelector and namespaceSelector are standard label selectors, and an empty or absent selector matches everything. Restricting a resource to one namespace is done with the namespace’s name label:
spec: namespaceSelector: matchLabels: kubernetes.io/metadata.name: monitoringExcluding a workload is expressed on the resources that would otherwise apply to it; there is no operator-wide skip list.
Three gates sit ahead of every resource and cannot be configured: a container whose reference kuik itself produced is never rewritten again, a container with imagePullPolicy: Never is never rewritten, and a static pod (the kubelet’s mirror pod) is never rewritten.
rewritePolicy
Section titled “rewritePolicy”Where a resource’s candidates sit relative to the original image when the webhook probes them:
| Value | Meaning | Kinds |
|---|---|---|
OnFailure | Default. The original image is tried first, the resource’s candidates are fallbacks | ImageAlternative, ImageMirror |
Always | The resource’s candidates are tried ahead of the original, to bypass a quota, latency or network cost | ImageAlternative, ImageMirror |
None | Never routes. The mirror copies images without ever rewriting a pod | ImageMirror only |
When several resources cover one image, their candidates are merged in one list: Always mirrors, then Always alternatives, then the original, then OnFailure alternatives, then OnFailure mirrors, resources sorted by name within each band.
With webhook.availabilityCheck.demoteKnownFailures (on by default), the webhook then moves to the end of the list the candidates the background loops report failing:
- the original, while any
ImageMonitorlists it instatus.unavailableImages, whatever that monitor’s selectors - a mirror candidate, while its own
ImageMirrorlists the pod’s image instatus.failedImageCopies; another mirror of the same image keeps its place. A failed resync of a tag the mirror also lists instatus.driftedImagesdoes not count: the destination still serves the previous copy - an alternative candidate, while any
ImageMonitorlists its reference instatus.unavailableAlternativeswithvianaming theImageAlternativethat offered it, whatever itsderivedFromand that monitor’s selectors; the same reference offered by anotherImageAlternativekeeps its place
The moved candidates keep their relative order, after everything else, an original already sent last by an unavailable: true entry included. A moved candidate is still probed and served when everything above it fails, and a candidate reported available is never moved up. A moved Always candidate is served as a fallback, after the original failed, so it is recorded with policy: OnFailure, as an Always mirror is for a container with imagePullPolicy: Always. An entry counts as long as the status lists it, and the webhook only reads these statuses. Three limits:
- an entry beyond the cap of its status list (
truncated) is not seen, and its candidate keeps its place - a mirror candidate whose copy is still waiting, not yet failed, keeps its place: the first pods of a new image probe it and fall back as without the setting
vianames only the firstImageAlternativeoffering an alternative, in candidate order: when several offer the same reference, only the candidate of that one moves
What the webhook records on a pod
Section titled “What the webhook records on a pod”The webhook probes the candidates in order and places the first one that answers. It records its decision on the pod, in three annotations holding JSON objects keyed by container name. A container appears in at most one of them:
| Annotation | The container | Value |
|---|---|---|
| (none) | kept its image: the original answered first | |
kuik.enix.io/rewrites | was rewritten | {"by": "<kind>/<name>", "origin": ..., "rewrittenTo": ..., "policy": "Always" | "OnFailure"} |
kuik.enix.io/conceded-rewrites | was rewritten, then another mutating webhook replaced the image, and kuik stood down | the entry that was in rewrites, unchanged |
kuik.enix.io/no-alternatives | kept its image: no candidate answered | the resources that offered one, as ["<kind>/<name>", ...] |
When the retained candidate’s auth injects a pull secret, the pod’s imagePullSecrets also gets kuik-inject-<kind>-<name>. The webhook exports two counters, kuik_routing_rewrites_total{kind, name, policy} and kuik_routing_alternatives_exhausted_total{kind, name}, listed with the reconciler’s events and series in Events, metrics and logs.
The credential kuik reads a registry with, wherever credentials appear on a resource: ImageAlternative entries, and an ImageMirror’s destination.manage and destination.pull. It is a union, and exactly one of secretRef or provider must be set:
auth: secretRef: # a docker-registry Secret name: quay-pull injectPullSecret: true # default: true with secretRef# --- or ---auth: provider: # an ambient cloud identity name: aws # aws | gcp | azure serviceAccountRef: # optional: request a token for this ServiceAccount instead of the controller's identity name: mirror-pusher injectPullSecret: false # default: false with provider| Field | Description |
|---|---|
secretRef.name | Name of a kubernetes.io/dockerconfigjson Secret. It carries no namespace and always resolves in kuik’s install namespace, so referencing a Secret requires being able to write it there |
provider.name | aws, gcp or azure |
provider.serviceAccountRef.name | ServiceAccount in kuik’s install namespace whose token is requested, so a resource carries its own cloud role |
injectPullSecret | Whether kuik copies a pull secret into the namespaces of the pods this credential serves, so the kubelet can pull the image. Defaults to true with secretRef and false with provider. A provider credential is not injected yet, whatever this field says. See Injected pull secrets |
Without any auth, kuik injects nothing: the pod is expected to carry its own imagePullSecrets, or the operator’s fallbackAuth to cover the registry for kuik’s own reads.
Repository paths
Section titled “Repository paths”repository, repositoryGroup and destination.path are fully qualified repository paths: a registry host (with an optional port), then path components, with no tag and no digest. quay.io/acme/foo and registry.local:5000/mirror/acme/foo are valid; acme/foo, quay.io/acme/foo:v1 and quay.io/acme/foo@sha256:… are rejected. The host is recognised as the container runtime does: it contains a dot, carries a port, or is localhost. A host alone (docker.io) is valid for repositoryGroup and destination.path, never for repository: a repository is one image with all its tags, docker.io/library/nginx.
Image references are normalized before any comparison, so nginx:1.27 is matched as docker.io/library/nginx:1.27.
ImageAlternative
Section titled “ImageAlternative”Declares that several repositories hold the same images, so that a pod referencing one of them can be served from another.
apiVersion: kuik.enix.io/v1alpha1kind: ImageAlternativemetadata: name: acme-foospec: podSelector: {} namespaceSelector: {} rewritePolicy: OnFailure # OnFailure | Always alternatives: - repository: quay.io/acme/foo - repository: docker.io/acme-org/foo - repository: 123456.dkr.ecr.eu-west-3.amazonaws.com/repo/acme/foo auth: provider: name: aws serviceAccountRef: name: kuik-ecr-access - repository: registry.local:5000/mirror/acme/foo insecure: true unavailable: true auth: secretRef: name: local-registryFields
Section titled “Fields”| Field | Required | Description |
|---|---|---|
spec.podSelector | Pods this resource applies to. Empty matches every pod | |
spec.namespaceSelector | Namespaces this resource applies to. Empty matches every namespace | |
spec.rewritePolicy | OnFailure (default) or Always, see rewritePolicy | |
spec.alternatives[] | ✅ | Ordered list of equivalent repositories, at least one entry. Every entry uses the same form, repository or repositoryGroup |
spec.alternatives[].repository | one of | Matches that exact repository, whatever the tag or digest |
spec.alternatives[].repositoryGroup | one of | Matches every repository located under that path, at any depth |
spec.alternatives[].insecure | kuik probes and pulls this registry over HTTP. The nodes’ container runtime must be configured for it separately | |
spec.alternatives[].unavailable | Declares the source gone: the entry still matches images, so the resource applies to them, but it is never offered, never read as a copy source and never monitored. An original image matching such an entry is tried last | |
spec.alternatives[].auth | Credential for this repository, see auth |
Matching
Section titled “Matching”An entry matches at path segment granularity, never as a string prefix: quay.io/acme/foo matches quay.io/acme/foo:v1 but never quay.io/acme/foo-bar:v1. The tag or digest is always carried over to the candidate, and for a repositoryGroup the remainder of the path is too.
| Image | repository: quay.io/acme/foo | repositoryGroup: quay.io/acme/foo |
|---|---|---|
quay.io/acme/foo:latest | matches | no: the group itself is not a repository |
quay.io/acme/foo-bar:latest | no | no |
quay.io/acme/foo/bar:latest | no: deeper than the entry | matches, rewritten to <other>/bar:latest |
quay.io/acme/foo/bar/oni:latest | no | matches, rewritten to <other>/bar/oni:latest |
Several ImageAlternative may match the same image; they add fallbacks to each other rather than shadowing each other. Digest-pinned images are routed like any other: the digest is carried over, and a candidate that does not hold it answers not found.
Validation
Section titled “Validation”Rejected at admission:
- an entry carrying both
repositoryandrepositoryGroup, or neither; - a
repositoryorrepositoryGroupcarrying a tag or a digest, or not fully qualified with a registry host, and arepositorynaming a host alone; - a list mixing
repositoryandrepositoryGroupentries; - an empty
alternativeslist; - an
authcarrying bothsecretRefandprovider, or neither.
Status
Section titled “Status”| Field | Description |
|---|---|
status.pods.tracked / .rewritten | Pods selected, and pods carrying at least one standing rewrite by this resource. Neither sums across resources |
status.containers.* | Containers of the selected pods, partitioned into untouched, rewritten, conceded (another webhook overwrote kuik’s rewrite), stale (edited after admission) and noAlternatives |
status.activeFallbacks[] | Origin images this resource currently stands in for, with rewrittenTo, pods and since. Rewrites recorded OnFailure only, which includes an Always candidate moved last as a known failure (demoteKnownFailures) |
status.noAlternatives[] | Images no candidate could serve |
status.concededRewrites[] / status.staleRewrites[] | Rewrites something replaced, with replacedBy |
status.truncated | Per capped list, how many entries were left out, see bounded lists |
status.conditions | Ready, FallbackActive, AlternativesExhausted, ListCapacityPressure |
The same counts, the anomalies as series and the events on the pods are in Events, metrics and logs.
ImageMirror
Section titled “ImageMirror”Copies the images of the pods it selects to a destination registry, and routes to the copy according to rewritePolicy.
apiVersion: kuik.enix.io/v1alpha1kind: ImageMirrormetadata: name: prod-mirrorspec: podSelector: {} namespaceSelector: {} excludeImages: - ghcr.io/foo-bar/** rewritePolicy: OnFailure # OnFailure | Always | None destination: path: registry.tld/mirror/ insecure: false manage: auth: secretRef: name: mirror-write-credentials pull: auth: secretRef: name: mirror-read-credentials cleanup: enabled: true retention: 168h driftPolicy: Ignore # Ignore | Warn | SyncFields
Section titled “Fields”| Field | Required | Description |
|---|---|---|
spec.podSelector | Pods this resource applies to. Empty matches every pod | |
spec.namespaceSelector | Namespaces this resource applies to. Empty matches every namespace | |
spec.excludeImages[] | Globs of images to keep out of the mirror: no copy, no candidate, no tag at the destination. See excluding images | |
spec.rewritePolicy | OnFailure (default), Always or None, see rewritePolicy | |
spec.destination.path | ✅ | Registry path the full original reference is appended to, hostname included: registry.tld/mirror/ turns docker.io/library/nginx:1.27 into registry.tld/mirror/docker.io/library/nginx:1.27_<clusterID>. The registry must accept deeply nested repository paths. Immutable: the tags written under a previous path could never be cleaned up |
spec.destination.insecure | kuik pushes to and reads this registry over HTTP | |
spec.destination.manage.auth | Controller credential at the destination: pushes, self-check reads, tag listings and deletions. Needs read as well as write. Its injectPullSecret is ignored | |
spec.destination.pull.auth | Credential the kubelet pulls the mirrored images with, injected in the namespaces that need it | |
spec.cleanup.enabled | Default true. Deletes the destination tags no pod references any more once their retention elapsed. With false nothing is deleted and retention has no effect | |
spec.cleanup.retention | Default 168h. How long an unused tag is held before deletion, so a CronJob’s image survives between runs. A Go duration | |
spec.driftPolicy | What to do when the upstream digest of a copied tag moves: Ignore (default, copy once), Warn (re-read the upstream periodically and report), Sync (re-read and copy again) |
The tag of every copy carries the operator’s cluster ID as a _<clusterID> suffix, so that several clusters can share one destination: the second cluster to need an image uploads nothing, and each cluster only ever creates, verifies and deletes its own tags. The ImageMirror object is identical on every cluster.
Excluding images
Section titled “Excluding images”Each excludeImages entry is a glob matched whole against the normalized reference. A : after the last / splits a tag part from a repository part; * matches inside one path segment, ** across segments.
| Pattern | Excludes |
|---|---|
ghcr.io/foo-bar/** | everything under ghcr.io/foo-bar/, at any depth |
ghcr.io/foo-bar/* | one level under it: ghcr.io/foo-bar/app but not ghcr.io/foo-bar/team/app |
docker.io/library/nginx | that repository, whatever the tag or digest |
quay.io/acme/*-debug | quay.io/acme/foo-debug, quay.io/acme/bar-debug |
**:latest | every mutable latest, whatever the registry |
The mirror’s own destination.path is excluded on top of this list, which is what keeps two mirrors from feeding each other.
Destination requirements
Section titled “Destination requirements”The destination must conform to the OCI Distribution spec, accept arbitrarily nested repository paths, and refuse a manifest whose blobs it does not hold. With cleanup.enabled, it must also support tag deletion (DELETE /v2/<name>/manifests/<tag>) and run its own garbage collection of untagged manifests: kuik deletes tags only, never manifests or blobs. A registry that rejects tag deletion flips the Ready condition to False with reason RegistryDeleteUnsupported.
Deleting an ImageMirror
Section titled “Deleting an ImageMirror”A deleted ImageMirror stops routing new pods and copying images at once, then stays Terminating until kuik has released it:
- While a live pod runs an image under
destination.path, it waits: a node rescheduling that pod would find the copy gone. Roll those workloads onto their origin to release it. - With
cleanup.enabled, it then deletes every tag of this cluster instatus.repositories, whatever their retention. It keeps waiting while themanagecredential cannot be read, or while the destination refuses tag deletion: released, it would take with it the only inventory of the tags left behind.
It retries after 10 seconds, doubling up to 30 minutes, and sooner when a pod goes. A destination that never accepts tag deletion holds the mirror for good: set cleanup.enabled: false before deleting it to leave the tags in place.
Status
Section titled “Status”The copy side first, then the routing side, which is field for field an ImageAlternative’s.
| Field | Description |
|---|---|
status.images.copy.* | References held at the destination: tracked (= running + standby + retained), available, unavailable, drifted, missingSource, orphanTags |
status.driftedImages[] | Tags whose upstream digest moved away from the copy, under Warn or Sync |
status.failedImageCopies[] | Origins whose copy has not succeeded, with reason (SourceNotFound, PushRejected, Unauthorized, QuotaExceeded, SourceUnreachable, DestinationUnreachable), since and lastAttempt |
status.pendingDeletion[] | Destination tags waiting out cleanup.retention, with origin when known and unusedSince. Not capped |
status.selfChecked | End of the last full comparison against the destination |
status.repositories[] | Destination repositories this mirror wrote to, for the cleanup sweep. Not capped |
status.checks.registries[] | Health of the drift check rings, one per source host, under Warn or Sync. The destination never appears here |
status.pods, status.containers, status.activeFallbacks[], status.noAlternatives[], status.concededRewrites[], status.staleRewrites[] | The routing side, absent under rewritePolicy: None |
status.conditions | Ready, DestinationOutOfSync, FallbackActive, AlternativesExhausted, ListCapacityPressure |
ImageMonitor
Section titled “ImageMonitor”Checks the availability of the images of the pods it selects, and reports the ones that fail or drift. It never contributes a routing candidate.
apiVersion: kuik.enix.io/v1alpha1kind: ImageMonitormetadata: name: cluster-imagesspec: podSelector: {} namespaceSelector: {} unusedImageRetention: 168h driftDetection: true monitorAlternatives: falseFields
Section titled “Fields”| Field | Required | Description |
|---|---|---|
spec.podSelector | Pods this resource applies to. Empty matches every pod | |
spec.namespaceSelector | Namespaces this resource applies to. Empty matches every namespace | |
spec.unusedImageRetention | Default 168h. Keeps monitoring an image for this long after no pod declares it any more, so a CronJob’s image stays checked between runs. A Go duration | |
spec.driftDetection | Default true. Reports the tracked tags whose upstream digest differs from the one running in the cluster | |
spec.monitorAlternatives | Default false. Also monitors the alternatives kuik would offer for each tracked image, from ImageAlternative entries only. A mirror destination is verified by the mirror’s own self-check, and entries marked unavailable: true are never tracked |
A monitor tracks the origin reference of every container: the reference its spec declares, or, where a standing kuik rewrite put a different one there, the origin that rewrite recorded. It therefore never sees a mirror’s reference in place of the origin it replaced.
The pods that count are the live ones (Pending or Running) the selectors select, static pods included. Init containers count, ephemeral containers never do. An image several pods declare counts once: running when a container runs that exact reference, standby when every container declaring it was routed elsewhere.
When no pod declares an image any more, it moves to status.retainedImages for unusedImageRetention, with the digest its most recently started container ran, and leaves it as soon as a pod declares it again. The monitor learns that an image went unused by watching its pods: an image whose last pod goes away while no reconciler runs is not retained.
Under monitorAlternatives, the monitor also tracks every candidate an ImageAlternative would offer for each origin it tracks, as the webhook would propose it for that container: only the ImageAlternatives whose selectors apply to the pod count, never an ImageMirror destination, never an entry marked unavailable (an origin such an entry names is still tracked as an origin), and never an entry naming the origin’s own repository. A container the webhook never rewrites, under imagePullPolicy: Never or in a static pod, offers no alternative. An alternative is running while a container carries it in place of its origin through a standing rewrite, standby otherwise. A retained origin keeps the alternatives its pods were offered when a pod last declared it, as long as their ImageAlternative still offers them (an entry marked unavailable or removed, or a deleted ImageAlternative, drops them); they count as retained without entering retainedImages, and leave once the origin’s retention elapses. After a restart, or once monitorAlternatives is turned off and on again, the monitor no longer knows what those pods were offered: the alternatives of a retained origin are then matched against every ImageAlternative, whatever its selectors, until its retention elapses. A reference that is both an origin and an alternative counts in both populations and is checked once. An ImageAlternative edit is followed without waiting for a pod to change, and turning monitorAlternatives off removes images.alternatives.
An alternative is checked on the rings like an origin, on a ring of its own host when no origin uses it, and is never compared to what runs: it never drifts. A check tries the auth of every entry offering it, in candidate order, then the pull secrets of the pods of its origins, then the matching fallbackAuth entry, then anonymously; an entry Secret that is missing or malformed is skipped for the next credential and leaves Ready as it is. An alternative several origins stand for is checked once, with the auth of each of their entries in turn. Each entry’s auth goes only over its own transport: the check reads over HTTPS with the auth of the secure entries, then the shared credentials (the pull secrets of the pods, fallbackAuth); when the registry refuses them and an entry is insecure, it reads again over plain HTTP with the auth of the insecure entries alone. The shared credentials cross plain HTTP only when no secure entry offers the alternative and it is no origin. images.alternatives.available and unavailable count the alternatives by the verdict of their last check, never among the origins: an alternative failing never enters unavailableImages nor emits AnonymousAccessDenied. A failing alternative enters status.unavailableAlternatives once per origin it stands for, with derivedFrom, reason, since, and via naming the first ImageAlternative offering it in candidate order (the Always band first, then by name). AlternativesUnavailable is True with reason ChecksFailed while any alternative fails. An alternative whose failure becomes Unauthorized emits AlternativeUnusable; any other failure emits nothing: alert on kuik_image_alternative_unavailable. A restart reads the entries of status.unavailableAlternatives back with their since, and does not announce an Unauthorized one again; for an alternative beyond the cap of the list, since is stamped anew and AlternativeUnusable may be emitted again. Until the rings have checked every alternative again, truncated, ListCapacityPressure and images.alternatives.unavailable stay at least as the status left them.
Checks are paced per registry host by the operator’s registries configuration: one image per window, so every tracked image comes back once per lap, the cycleDuration reported in status. See Registry pacing. Retained images are checked like the others. A restart resumes each ring after the cursor its status holds.
A check sends a manifest HEAD to the image’s registry with each credential in turn until one answers: the pull secrets of the pods declaring it, then the matching fallbackAuth entry, or one anonymous HEAD when none applies (see Registry credentials). A registry refusing an anonymous check with Unauthorized emits AnonymousAccessDenied on the monitor, once while the image stays unavailable: the image is likely private and no credential covers it. An image a fallbackAuth entry matches never emits it, even when that entry gives no credential: its missing or malformed Secret shows in Ready instead. When several monitors track an image, one check serves them all, and only the monitor whose ring sent the request emits the event or reports a broken fallbackAuth Secret in Ready. available and unavailable count the images by the verdict of their last check, so they fall short of tracked by the images the ring has not reached yet. A restart forgets the available verdicts: they come back as the rings turn, within one cycleDuration. An image whose check fails enters status.unavailableImages, its since stamped once and kept while the failure lasts, across restarts too, and leaves it as soon as a check answers. A restart reads back only the entries the capped list holds: for an image beyond the cap, since is stamped anew and AnonymousAccessDenied may be emitted again. Until the rings have checked every image again, truncated, ListCapacityPressure and images.origin.unavailable stay at least as the status left them. ImagesUnavailable is True with reason ChecksFailed while any origin fails. No event announces it: read it from the status, or alert on kuik_image_origin_unavailable.
Under driftDetection, a tag drifts when a pod runs it at a digest other than the one its last check read upstream: drift reads the digest of the availability check’s HEAD and sends no request of its own. The tag enters status.driftedImages with that upstreamDigest, a since stamped once, and runningDigests: every digest the containers carrying the tag itself run, each with the number of pods running it (a pod counts once per digest, init containers count). A container kuik routed to another reference, a container whose image is not pulled yet, a digest-pinned reference (name:tag@sha256:… included) and a retained image never drift. When the upstream moves again under a drifted tag, the entry takes the new upstreamDigest and keeps its since; when its check fails, it keeps the last digest read. The entry leaves as soon as every pod runs the upstream digest. ImagesDrifted is True with reason UpstreamDigestMoved while a tag drifts. A tag starting to drift emits ImageTagDrifted, and one whose pods start running several digests emits ImageClusterSkew and gets a kuik_image_cluster_skew_digests series; a drift that ends emits nothing. A restart reads the entries of status.driftedImages back with their since and upstreamDigest, recounts runningDigests from the pods at once, and announces none of them again. For a tag beyond the cap of the list, since is stamped anew, ImageTagDrifted and ImageClusterSkew may be emitted again, and its kuik_image_drifted and kuik_image_cluster_skew_digests series come back once its ring reaches it. Until a check has read the upstream digest of every running tag again, a failed check not counting, truncated, ListCapacityPressure and images.origin.drifted stay at least as the status left them, as far as the running tags whose drift is not known yet can account for. driftDetection: false reports no drift and removes what was reported. The digest a pod runs is the one its container runtime reports in imageID: containerd reports the index digest of a multi-arch image pulled by tag, which is what the check reads, while a runtime reporting a platform manifest or a config digest makes such a tag show as drifted (kubernetes#115199). An image the node had before any pull (preloaded, or loaded with kind load or ctr import) reports no registry digest in imageID, only its config digest: its drift is not checked, its retainedImages digest is empty, and the reconciler logs it once per image.
Status
Section titled “Status”| Field | Description |
|---|---|
status.images.origin.* | Origin references: tracked (= running + standby + retained), available, unavailable, drifted |
status.images.alternatives.* | The same gauges for the monitored alternatives, under monitorAlternatives, without drifted: an alternative is checked, never compared to what runs |
status.retainedImages[] | References kept for unusedImageRetention, with unusedSince and digest. Not capped |
status.unavailableImages[] | Tracked origins whose last check failed, with reason (ManifestNotFound, Unauthorized, QuotaExceeded, Unreachable), since and pods |
status.unavailableAlternatives[] | Monitored alternatives whose last check failed, one entry per origin they stand for, with derivedFrom, via naming the ImageAlternative (ImageAlternative/<name>), reason and since |
status.driftedImages[] | Tracked tags whose running digest differs from the upstream one, with every runningDigests[] seen and its pod count |
status.checks.registries[] | One ring per registry host: images, cursor, cycleStarted, cycleDuration |
status.truncated | Per capped list, how many entries were left out, see bounded lists. images.origin.unavailable and kuik_image_origin_unavailable still count every failing image, images.origin.drifted and kuik_image_drifted every drifted tag. images.alternatives.unavailable and kuik_image_alternative_unavailable cover every failing alternative. After a restart, the series of an image beyond the cap comes back once its ring reaches it |
status.conditions | Ready, ImagesUnavailable, AlternativesUnavailable, ImagesDrifted, ListCapacityPressure |
Status conventions
Section titled “Status conventions”Ready is the only condition that is True when things are well. Every other condition names an anomaly and stays True as long as it lasts, so “is anything wrong with this resource?” is one query: a condition whose status is True and whose type is not Ready. Ready goes False when the resource cannot work as declared (InvalidConfig, SecretNotFound, SecretMalformed, TokenRequestFailed, RegistryDeleteUnsupported), never because of one image.
Anomaly lists are bounded. unavailableImages, unavailableAlternatives, driftedImages, failedImageCopies, activeFallbacks, noAlternatives, concededRewrites and staleRewrites carry a fixed cap on their number of entries; they are samples, and the matching metrics carry every affected image. The oldest entries are kept. ListCapacityPressure goes True from 80% of the cap, and status.truncated records, per list, how many entries were left out once it is reached. The operational inventories (repositories, pendingDeletion, retainedImages, checks.registries) are never capped: truncating them would lose correctness rather than visibility.