Skip to content

Save cached container images with nerdctl instead of ctr on the control node - #13403

Open
Payback159 wants to merge 1 commit into
kubernetes-sigs:masterfrom
Payback159:fix/13235-containerd-localhost-image-save
Open

Save cached container images with nerdctl instead of ctr on the control node#13403
Payback159 wants to merge 1 commit into
kubernetes-sigs:masterfrom
Payback159:fix/13235-containerd-localhost-image-save

Conversation

@Payback159

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind bug

What this PR does / why we need it:

When images are downloaded/cached on the Ansible control node (download_run_once + download_localhost: true) with container_manager_on_localhost == 'containerd', the image export step intermittently fails with:

ctr: failed to get reader: content digest sha256:<digest>: not found

The root cause is that two different tools are used for the two halves of the same operation:

  • Pull uses nerdctl: image_pull_command_on_localhost resolves to nerdctl_image_pull_command
    ({{ bin_dir }}/nerdctl -n k8s.io pull --quiet) — see roles/kubespray_defaults/defaults/main/download.yml.
  • Save uses ctr: image_save_command_on_localhost was
    {{ containerd_bin_dir }}/ctr -n k8s.io image export --platform linux/{{ image_arch }} ...
    — see roles/download/tasks/set_container_facts.yml.

nerdctl pull fetches only the blobs it needs for the requested platform and records that in the content store via the containerd transfer service. ctr image export --platform ... then walks the index/manifest list and demands every descriptor for the selected platform to be present as local content. With containerd 2.1+ the two paths no longer agree on which manifest/layer digests must be materialized for a multi-arch image, so the export aborts on the first descriptor that was never pulled — the content digest ...: not found above. This is exactly what is reported in the upstream issue.

Kubespray already solves this correctly for remote nodes, where both the pull and the save are done with nerdctl (image_save_command uses nerdctl -n k8s.io image save). This PR simply applies the same, already-proven logic to the localhost path.

Before

- name: Set image save/load command for containerd on localhost
  set_fact:
    image_save_command_on_localhost: "{{ containerd_bin_dir }}/ctr -n k8s.io image export --platform linux/{{ image_arch }} {{ image_path_cached }} {{ image_reponame }}"
  when: container_manager_on_localhost == 'containerd'

After

- name: Set image save/load command for containerd on localhost
  set_fact:
    image_save_command_on_localhost: "{{ bin_dir }}/nerdctl -n k8s.io image save -o {{ image_path_cached }} {{ image_reponame }}"
  when: container_manager_on_localhost == 'containerd'

The image is now pulled and exported by the same tool, so the archive is written from exactly the content nerdctl placed in the store. The output path (image_path_cached), the k8s.io namespace and the resulting Docker-archive format are unchanged, so the downstream copy to the nodes and the nerdctl image load on the remote side keep working as before — the produced tarball is the same format the remote image_save_command already produces.

Which issue(s) this PR fixes:

Fixes #13235

Special notes for your reviewer:

  • The change is a one-liner in roles/download/tasks/set_container_facts.yml and only affects
    container_manager_on_localhost == 'containerd'. The docker and crio localhost branches, and all
    remote-node branches, are untouched.
  • nerdctl is used because it is the tool Kubespray already installs and uses for containerd image
    handling; no new dependency is introduced. {{ bin_dir }} is the same prefix used by
    nerdctl_image_pull_command and by the remote image_save_command.
  • On the dropped --platform linux/{{ image_arch }}: the flag could not do what it appeared to do.
    The pull on the control node (nerdctl pull, no --all-platforms/--platform) only ever fetches the
    control node's own platform, so requesting a different platform at export time is precisely the
    situation that triggers the reported error. nerdctl image save exports what was actually pulled, so
    the common same-architecture case is byte-for-byte equivalent and the cross-architecture case goes from
    "hard failure" to "exports the platform that was downloaded". Making the localhost cache genuinely
    multi-arch (i.e. nerdctl pull --all-platforms combined with a platform-aware save) is a separate
    feature and intentionally out of scope here.

Testing done:

  • pre-commit passes on the change (yamllint, misspell, collection build, ci-matrix, checksum
    ordering, …). The ansible-lint hook reports a jinja[invalid] violation in
    roles/kubernetes/client/tasks/main.yml, but it reproduces identically on an unmodified master
    checkout, so it is pre-existing and unrelated to this change.
  • Rendered roles/download/tasks/set_container_facts.yml in isolation with ansible-playbook and
    asserted the resulting facts:
    • tag form → <bin_dir>/nerdctl -n k8s.io image save -o <download_cache_dir>/images/registry.k8s.io_pause_3.10.tar registry.k8s.io/pause:3.10
    • digest form (repo@sha256:...) renders correctly for both the archive name and the image reference
    • image_save_command (remote) and image_save_command_on_localhost now resolve to the same binary
    • the docker and crio localhost branches are unchanged

Does this PR introduce a user-facing change?:

Fixed container image export on the Ansible control node when using containerd. Images are now saved with `nerdctl image save` instead of `ctr image export`, matching the tool used to pull them and resolving `ctr: failed to get reader: content digest sha256:<digest>: not found` failures with containerd 2.1+.

@kubernetes-prow kubernetes-prow Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. kind/bug Categorizes issue or PR as related to a bug. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Jul 31, 2026
@kubernetes-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Payback159
Once this PR has been reviewed and has the lgtm label, please assign yankay for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow kubernetes-prow Bot added size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Jul 31, 2026
@kubernetes-prow

Copy link
Copy Markdown
Contributor

Hi @Payback159. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Tip

We noticed you've done this a few times! Consider joining the org to skip this step and gain /lgtm and other bot rights. We recommend asking approvers on your previous PRs to sponsor you.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@kubernetes-prow
kubernetes-prow Bot requested review from ErikJiang and yankay July 31, 2026 17:26
Images cached on the Ansible control node are pulled with nerdctl
(image_pull_command_on_localhost -> nerdctl_image_pull_command) but were
exported with "ctr image export --platform linux/<arch>". nerdctl only
fetches the blobs for the platform it pulled, while ctr export walks the
manifest list and requires every descriptor of the requested platform to
be present in the content store. With containerd 2.1+ the two paths no
longer agree, so the export aborts with:

  ctr: failed to get reader: content digest sha256:<digest>: not found

Use "nerdctl image save" instead, which is what the remote node path
(image_save_command) already does, so the image is pulled and exported
by the same tool.
@Payback159
Payback159 force-pushed the fix/13235-containerd-localhost-image-save branch from 9fca7e3 to 8c5e17e Compare July 31, 2026 17:26
@kubernetes-prow kubernetes-prow Bot removed the do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. label Jul 31, 2026
@yankay

yankay commented Aug 3, 2026

Copy link
Copy Markdown
Member

/ok-to-test

@kubernetes-prow kubernetes-prow Bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/bug Categorizes issue or PR as related to a bug. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XS Denotes a PR that changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Container image pull/export issue using nerdctl (pull) and ctr (export)

2 participants