Skip to content

fix(kind): route around Docker Hub's anonymous pull-rate limit - #486

Open
yoavkatz wants to merge 2 commits into
mainfrom
fix/kind-dockerhub-429-mirror
Open

fix(kind): route around Docker Hub's anonymous pull-rate limit#486
yoavkatz wants to merge 2 commits into
mainfrom
fix/kind-dockerhub-429-mirror

Conversation

@yoavkatz

@yoavkatz yoavkatz commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

What

Extracted from #408. Docker Hub's anonymous pull limit (100 pulls / 6h per
source IP) was surfacing as two different unrelated-looking failures on kind:

  1. Node base-image pulls. kind's own init containers (busybox:1.36)
    pull straight from docker.io. Behind a shared office/VPN IP, the node
    hits 429 and the pod sits in Init:ErrImagePull — with no Docker Hub
    account to log into to raise the limit.
  2. The in-cluster upstream probe. create.sh's connectivity probe pulls
    curlimages/curl from docker.io too. The same 429 made the probe report
    a cluster-egress or private-CA fault that didn't actually exist — the
    pull never got far enough to test either.

Fix:

  • Route docker.io through mirror.gcr.io (Google's public pull-through
    cache) for the node's own pulls: containerdConfigPatches sets
    config_path, and a hosts.toml drop-in is written into the node after
    create.sh provisions it. Transparent — image refs are unchanged, so
    release build-input hashing is unaffected.
  • Default PROBE_IMAGE to mirror.gcr.io/curlimages/curl:8.11.1 — identical
    manifest to docker.io/curlimages/curl:8.11.1, just not rate-limited.
    Overridable via PROBE_IMAGE for an internal mirror or an air-gapped node.
  • Distinguish "never obtained the probe image" from "connected but the
    exchange never completed" — both previously surfaced as an absent
    __probe__ trailer and were misread as an upstream fault. The kubelet's
    own ImagePullBackOff/ErrImagePull verdict is now reported as a distinct
    image-pull-failed sentinel, named explicitly as a probe-tooling problem
    rather than a verdict on the upstream. Pull failures are not retried (the
    image won't appear on its own, and retrying a throttled registry deepens
    the throttle); a 429 gets the mirror-override remedy instead.
  • Document both the node-pull mirror and PROBE_IMAGE override in
    docs/guides/podman-on-apple-silicon.md and deploy/kind/README.md.

Rules checked against

(.agents/gateways/RULES.md, .agents/RULES.md; declared per
.agents/contributing/RULES.md rule 3)

  • Contributing rule 2 — Rules or code, not both. Code + docs only; no
    .agents/ edits.
  • No credential or auth changes — this only redirects anonymous, unauthenticated
    base-image pulls to an equivalent public mirror.

Scope

Four files: deploy/kind/create.sh, deploy/kind/README.md,
docs/guides/podman-on-apple-silicon.md.

…429s

Docker Hub's anonymous pull limit (100/6h per IP) causes 429 on the node's
base-image pulls (busybox:1.36 init containers -> Init:ErrImagePull) with no
account to log into. Route docker.io through Google's public pull-through cache
mirror.gcr.io: add containerdConfigPatches to set config_path, then write a
docker.io hosts.toml drop-in into the node after create. Transparent -- image
refs are unchanged, so release build-input hashing is unaffected.

Document the mirror approach (node containerd + podman machine registries.conf)
in the podman-on-apple-silicon guide, alongside the existing ECR one-off.

Signed-off-by: Yoav Katz <katz@il.ibm.com>
…ll failure

The in-cluster upstream probe pulled curlimages/curl from docker.io. Docker Hub
rate-limits anonymous pulls per source IP, so behind a shared office or VPN
address the node's pull fails with 429 and the probe reported a cluster-egress
or private-CA fault that did not exist.

Two changes:

- Default PROBE_IMAGE to mirror.gcr.io/curlimages/curl:8.11.1 — Google's
  pull-through cache of Docker Hub, same manifest, no anonymous pull limit.
  Overridable for an internal mirror or an air-gapped node. Every other image
  this deployment uses is on ghcr.io, which has no such limit, so this was the
  one exposure.

- Distinguish "never obtained the probe image" from "connected but the exchange
  never completed". Both previously surfaced as an absent __probe__ trailer and
  were read as an upstream fault. Report the kubelet's own ImagePullBackOff /
  ErrImagePull verdict as a distinct image-pull-failed sentinel, and have the
  caller name it as a probe-tooling problem — explicitly not a verdict on the
  upstream, since the host leg has already passed by then. A 429 gets the
  mirror-override remedy. Pull failures are not retried: the image will not
  appear on its own, and retrying a throttled registry deepens the throttle.

The sentinel is handled before any arithmetic test, because bash's `-eq`
evaluates operands as arithmetic expressions and would abort under `set -u`
trying to resolve it as a variable name; the two numeric comparisons on that
path move to string form for the same reason.

Signed-off-by: Yoav Katz <katz@il.ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant