Skip to content

fix(k3s): cap parallel image pulls - #2123

Merged
shunkakinoki merged 2 commits into
mainfrom
codex/cap-k3s-parallel-image-pulls
Jul 19, 2026
Merged

fix(k3s): cap parallel image pulls#2123
shunkakinoki merged 2 commits into
mainfrom
codex/cap-k3s-parallel-image-pulls

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • install a Kyber kubelet configuration drop-in through Home Manager
  • retain parallel pulls but cap them at four concurrent images
  • restart K3s once when either the server config or kubelet drop-in changes

Root cause addressed

K3s 1.35 generated serializeImagePulls: false without maxParallelImagePulls. During cold runtime recovery, more than 40 image downloads ran concurrently, saturated the single disk, and caused CRI deadlines, name reservations, and pull-QPS failures. Kubernetes documents maxParallelImagePulls specifically to bound network and disk consumption while retaining parallel pulls.

Validation

  • shellspec spec/activate_k3s_spec.sh (16 examples)
  • shellcheck config/k3s/activate.sh
  • make nix-format-check
  • Kyber Home Manager evaluation confirms the drop-in is present

Summary by cubic

Caps k3s image pull concurrency at 4 using a kubelet config drop-in to prevent disk saturation and CRI timeouts during cold recovery. The activation script installs the drop-in and restarts k3s once when config changes.

  • Bug Fixes
    • Add kubelet drop-in with serializeImagePulls: false and maxParallelImagePulls: 4.
    • Compare installed configs with sudo; copy server and kubelet configs only on change; restart k3s once.
    • Provision drop-in via Home Manager; add spec to assert presence; shellspec and shellcheck pass.

Written for commit 17524cc. Summary will update on new commits.

Review in cubic

@indent-zero

indent-zero Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Caps parallel container image pulls on the kyber k3s node to avoid saturating containerd/disk/CRI on cold starts by shipping a kubelet drop-in and syncing it via the existing k3s activation script. Behavior only changes on kyber (isKyber), and the activate script now restarts k3s when either the k3s config or the new kubelet drop-in drifts.

  • Add config/k3s/kubelet.conf with serializeImagePulls: false and maxParallelImagePulls: 4.
  • Install it via home-manager as ~/.config/k3s/kubelet.conf.d/10-kyber.conf when isKyber.
  • Extend config/k3s/activate.sh to sudo cp the drop-in into /var/lib/rancher/k3s/agent/etc/kubelet.conf.d/10-kyber.conf, create the parent dir, and use a K3S_CONFIG_CHANGED accumulator so k3s is restarted only when either config actually changed.
  • Tighten the existence guard to require both the k3s config and the kubelet drop-in before performing any sudo work.
  • Add a spec assertion in spec/activate_k3s_spec.sh that the activate script references the kubelet drop-in target path.

Issues

All clear! No issues remaining. 🎉

1 issue already resolved
  • kubelet drop-in only takes effect on k3s v1.32.1+k3s1 or newer — older k3s builds don't scan /var/lib/rancher/k3s/agent/etc/kubelet.conf.d/, so the file installs cleanly and k3s restarts but maxParallelImagePulls silently has no effect; confirm kyber's k3s version, or add kubelet-arg: "config-dir=/var/lib/rancher/k3s/agent/etc/kubelet.conf.d" to config/k3s/config.yaml as a belt-and-suspenders fallback.

CI Checks

Waiting for CI checks...

@cursor

cursor Bot commented Jul 19, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@mesa-dot-dev

mesa-dot-dev Bot commented Jul 19, 2026

Copy link
Copy Markdown

You do not have enough credits to review this pull request. Please purchase more credits to continue.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The k3s activation script now deploys a KYBER kubelet drop-in, conditionally copies both configuration files, and restarts k3s only when either file changes. Home-manager provides the drop-in, and the activation spec verifies its wiring.

Changes

K3s kubelet configuration

Layer / File(s) Summary
Kubelet configuration source and deployment
config/k3s/kubelet.conf, config/k3s/default.nix
Defines kubelet image-pull concurrency settings and installs the drop-in for KYBER hosts.
Activation and restart handling
config/k3s/activate.sh, spec/activate_k3s_spec.sh
Validates and conditionally copies both configuration files, tracks changes, gates the k3s restart, and tests the kubelet drop-in references.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Poem

A rabbit hops through configs bright,
KYBER pulls four images right.
Drop-ins bloom where kubelets run,
k3s restarts when changes are done.
“Hop hop!” says Bun, “the setup’s spun!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: capping K3s parallel image pulls.
Description check ✅ Passed The description is directly related to the change set and explains the kubelet drop-in and restart behavior.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/cap-k3s-parallel-image-pulls

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a custom kubelet configuration drop-in (10-kyber.conf) for k3s to optimize cold starts by configuring parallel image pulls. It updates the Nix configuration to deploy this file, adjusts the activate.sh script to copy it to the target directory, and adds a test case to verify the script's behavior. A critical issue was identified in the activation script where running diff without $SUDO_CMD on root-owned files will fail with permission errors, leading to redundant file copies and unnecessary k3s service restarts on every activation. Using $SUDO_CMD diff is recommended to resolve this.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread config/k3s/activate.sh Outdated
Comment on lines +58 to +66
if ! diff -q "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml >/dev/null 2>&1; then
$SUDO_CMD cp "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml
K3S_CONFIG_CHANGED=1
fi

if ! diff -q "$KUBELET_CONFIG_SOURCE" "$KUBELET_CONFIG_TARGET" >/dev/null 2>&1; then
$SUDO_CMD cp "$KUBELET_CONFIG_SOURCE" "$KUBELET_CONFIG_TARGET"
K3S_CONFIG_CHANGED=1
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The activation script runs as a non-root user during Home Manager activation. Because the target files and directories (especially under /var/lib/rancher/k3s/) are typically owned by root and restricted (e.g., permissions 700 or 750), running diff without $SUDO_CMD will fail with a 'Permission denied' error (exit status 2).

Since the condition uses ! diff, any non-zero exit status (including permission errors) evaluates to true. This causes the script to copy the files and set K3S_CONFIG_CHANGED=1 on every single activation, leading to unnecessary and disruptive k3s service restarts.

Using $SUDO_CMD diff ensures that diff has the necessary privileges to read the target files and correctly determine if they have changed.

Suggested change
if ! diff -q "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml >/dev/null 2>&1; then
$SUDO_CMD cp "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml
K3S_CONFIG_CHANGED=1
fi
if ! diff -q "$KUBELET_CONFIG_SOURCE" "$KUBELET_CONFIG_TARGET" >/dev/null 2>&1; then
$SUDO_CMD cp "$KUBELET_CONFIG_SOURCE" "$KUBELET_CONFIG_TARGET"
K3S_CONFIG_CHANGED=1
fi
if ! $SUDO_CMD diff -q "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml >/dev/null 2>&1; then
$SUDO_CMD cp "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml
K3S_CONFIG_CHANGED=1
fi
if ! $SUDO_CMD diff -q "$KUBELET_CONFIG_SOURCE" "$KUBELET_CONFIG_TARGET" >/dev/null 2>&1; then
$SUDO_CMD cp "$KUBELET_CONFIG_SOURCE" "$KUBELET_CONFIG_TARGET"
K3S_CONFIG_CHANGED=1
fi

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 17524cc: both installed-file comparisons now run through the detected sudo command, and ShellCheck plus the focused 16-example ShellSpec suite pass.

Comment thread config/k3s/kubelet.conf

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/k3s/activate.sh`:
- Around line 58-61: Update both file-copy operations in config/k3s/activate.sh:
the copy guarded by the K3S_CONFIG_SOURCE comparison at lines 58-61 and the
KUBELET_CONFIG_SOURCE copy at lines 63-66 must use the force flag with $SUDO_CMD
cp, preserving their existing destinations and change-tracking behavior.
- Line 37: Separate the K3S_CONFIG_SOURCE and KUBELET_CONFIG_SOURCE existence
checks in the activation script so missing Kyber-specific kubelet configuration
does not prevent k3s setup on non-Kyber nodes. Keep k3s validation and
configuration unconditional, and wrap the downstream KUBELET_CONFIG_SOURCE
diff/copy logic in a file-existence conditional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e599a977-e352-4532-8405-42d09ca6d754

📥 Commits

Reviewing files that changed from the base of the PR and between 518063b and 6c93462.

📒 Files selected for processing (4)
  • config/k3s/activate.sh
  • config/k3s/default.nix
  • config/k3s/kubelet.conf
  • spec/activate_k3s_spec.sh

Comment thread config/k3s/activate.sh
KUBELET_CONFIG_SOURCE="$HOME/.config/k3s/kubelet.conf.d/10-kyber.conf"
KUBELET_CONFIG_TARGET="/var/lib/rancher/k3s/agent/etc/kubelet.conf.d/10-kyber.conf"

if [ ! -f "$K3S_CONFIG_SOURCE" ] || [ ! -f "$KUBELET_CONFIG_SOURCE" ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Separate the existence checks to prevent breaking non-Kyber nodes.

Because KUBELET_CONFIG_SOURCE is deployed conditionally for Kyber nodes (lib.mkIf isKyber in default.nix), combining its check with K3S_CONFIG_SOURCE means this script could inadvertently skip or abort k3s activation entirely on non-Kyber nodes where the drop-in file is naturally absent.

Please consider separating the validation so the script can still configure k3s even if the Kyber-specific drop-in is not present.

🛠️ Proposed fix
-if [ ! -f "$K3S_CONFIG_SOURCE" ] || [ ! -f "$KUBELET_CONFIG_SOURCE" ]; then
+if [ ! -f "$K3S_CONFIG_SOURCE" ]; then

(You will also need to wrap the downstream diff/cp logic for KUBELET_CONFIG_SOURCE in a conditional if [ -f "$KUBELET_CONFIG_SOURCE" ]; then ... fi block.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config/k3s/activate.sh` at line 37, Separate the K3S_CONFIG_SOURCE and
KUBELET_CONFIG_SOURCE existence checks in the activation script so missing
Kyber-specific kubelet configuration does not prevent k3s setup on non-Kyber
nodes. Keep k3s validation and configuration unconditional, and wrap the
downstream KUBELET_CONFIG_SOURCE diff/copy logic in a file-existence
conditional.

Comment thread config/k3s/activate.sh Outdated
Comment on lines +58 to +61
if ! diff -q "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml >/dev/null 2>&1; then
$SUDO_CMD cp "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml
K3S_CONFIG_CHANGED=1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Use non-interactive flags for file operations.

As per coding guidelines, always use non-interactive flags with file operations (cp -f) to avoid hanging on confirmation prompts during automated runs.

  • config/k3s/activate.sh#L58-L61: Update to use $SUDO_CMD cp -f "$K3S_CONFIG_SOURCE" /etc/rancher/k3s/config.yaml.
  • config/k3s/activate.sh#L63-L66: Update to use $SUDO_CMD cp -f "$KUBELET_CONFIG_SOURCE" "$KUBELET_CONFIG_TARGET".
📍 Affects 1 file
  • config/k3s/activate.sh#L58-L61 (this comment)
  • config/k3s/activate.sh#L63-L66
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config/k3s/activate.sh` around lines 58 - 61, Update both file-copy
operations in config/k3s/activate.sh: the copy guarded by the K3S_CONFIG_SOURCE
comparison at lines 58-61 and the KUBELET_CONFIG_SOURCE copy at lines 63-66 must
use the force flag with $SUDO_CMD cp, preserving their existing destinations and
change-tracking behavior.

Source: Coding guidelines

@mesa-dot-dev

mesa-dot-dev Bot commented Jul 19, 2026

Copy link
Copy Markdown

Mesa Description

TL;DR

Caps K3s parallel image pulls at four concurrent downloads via a new Kubelet configuration drop-in to prevent node disk saturation and CRI failures during cold starts.

What changed?

  • config/k3s/activate.sh: Updated activation script to manage both K3s and Kubelet configurations, creating required directories and restarting the k3s service if either configuration changes.
  • config/k3s/default.nix: Configured Home Manager to provision the Kubelet configuration drop-in (10-kyber.conf) when isKyber is enabled.
  • config/k3s/kubelet.conf: Added Kubelet configuration file enabling parallel image pulling capped at four concurrent images.
  • spec/activate_k3s_spec.sh: Added a test to verify the script correctly references the Kubelet config target path.

Root cause addressed

K3s 1.35 generated serializeImagePulls: false without maxParallelImagePulls. During cold runtime recovery, more than 40 image downloads ran concurrently, saturated the single disk, and caused CRI deadlines, name reservations, and pull-QPS failures. Kubernetes documents maxParallelImagePulls specifically to bound network and disk consumption while retaining parallel pulls.

Validation

  • shellspec spec/activate_k3s_spec.sh (16 examples)
  • shellcheck config/k3s/activate.sh
  • make nix-format-check
  • Kyber Home Manager evaluation confirms the drop-in is present

Description generated by Mesa. Update settings

@shunkakinoki
shunkakinoki merged commit 98a6d28 into main Jul 19, 2026
28 of 31 checks passed
@shunkakinoki
shunkakinoki deleted the codex/cap-k3s-parallel-image-pulls branch July 19, 2026 01:30
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