Skip to content

Registry pacing

This documents the in-development version. Switch to v2.3 for the latest stable release.

kuik reads source registries in the background: an ImageMonitor checks the images it tracks, an ImageMirror copies the images it owes and, under driftPolicy: Warn or Sync, re-reads the tags it copied. Every one of these reads is paced per registry host, by registries, so that kuik never adds to the rate limit of a registry that rations.

The webhook’s active check is not paced: it always runs, bounded by webhook.availabilityCheck.timeout. Nor is a mirror destination, which kuik owns: it is read whole once per mirror.destinationScan.interval.

Each host has 2 series of windows, one for checks (check.interval) and one for copies (copy.interval). A window takes one image.

Windows open at fixed times counted from the moment the reconciler takes its lease: leading from 13:32 with interval: 5m, it opens them at 13:37, 13:42, 13:47 and so on. The time an image takes never moves the next window, and a window that finds nothing to do, or opens while the previous image is still being read, is lost rather than saved for later. A host is therefore asked for at most one image per interval, whatever happens.

The first window is a full interval away, so a reconciler that restarts in a loop sends nothing, and a standby taking over never fires on the times of the leader it replaces.

The windows belong to the host, not to the resource. Every ImageMonitor tracking images on docker.io and every ImageMirror re-reading tags there take turns on the same check windows, and every mirror pulling from docker.io takes turns on its copy windows. Adding a resource shares the budget; it never raises it.

Each resource holds a ring per host: the images it reads there, in lexicographic order. An ImageMonitor under monitorAlternatives reads its alternatives there too, a reference that is both an origin and an alternative once. Each turn it gets takes the next image of its ring, so every image comes back once per lap.

The status reports each ring under checks.registries: its size (images), the image last checked (cursor), when the lap in progress started (cycleStarted), and how long the last one took (cycleDuration). A restart resumes after the cursor, and the lap counts the downtime.

cycleDuration is measured, not computed: it is the freshness the resource guarantees, every image checked at least once per cycleDuration. A resource alone on its host laps in images × interval, 60 images on interval: 1m in an hour. Sharing the host, restarts and images kept by unusedImageRetention all lengthen it. A lap that grows too long is a reason to lower the host’s check.interval.

When 2 resources track the same image, kuik asks the registry once. The response, verdict and digest, is kept, and the next resource to reach that image reuses it instead of spending a window.

2 monitors tracking the same 1000 images on interval: 1m lap in 1000 minutes, not 2000: the registry sees the 1000 images once per lap, whatever the number of resources tracking them. An ImageMonitor with driftDetection and an ImageMirror with driftPolicy: Sync on the same tags share the same requests too.

A resource reuses only a response newer than its own previous read of that image, so a reused answer is never older than its lap: the freshness its status reports still holds.

Copies drain a queue rather than turn a ring: each ImageMirror queues the images it still owes from a host, and the mirrors sharing that host take its copy windows in turn. An image whose copy fails is retried on a later turn and never holds back the images queued behind it.

copy.timeout: 0, the default, lets a copy take as long as it needs: a copy that outlasts its copy.interval already shows as copy windows going unused while images are pending.

The config is reloaded in place. A reload that changes the check.interval of a host restarts the check windows of that host only, the first one a full new interval after the reload, as a start does; copy.interval does the same for its copy windows. Every other series keeps its times, a changed timeout applies from the next image without moving any window, and no cursor moves.

Editing the config in a loop therefore never bursts a registry: each change pushes the next window further away.

An interval bounds what one cluster sends. A registry counts its quota per identity: the source IP for anonymous pulls, the account behind the credential otherwise.

The reconciler exports each ring’s lap, size and the pace of each host, listed in Scheduling health.

kuik_check_cycle_duration_seconds / (kuik_check_ring_images * on(registry) group_left kuik_registry_interval_seconds{operation="Check"}) is how much longer than its best case a lap takes: above 1, the ring shares its host or loses windows.