Skip to content

fix(k3s): preserve Kyber disk headroom - #2126

Merged
shunkakinoki merged 1 commit into
mainfrom
codex/kyber-k3s-disk-headroom
Jul 19, 2026
Merged

fix(k3s): preserve Kyber disk headroom#2126
shunkakinoki merged 1 commit into
mainfrom
codex/kyber-k3s-disk-headroom

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • declaratively keep Kyber's ext4 root reserve at 1% using the mounted root device
  • cap kubelet image pulls at two and make the 85%/80% image-GC thresholds explicit
  • document the disk-pressure feedback loop and attended recovery in the Kyber host README
  • preserve kubelet ownership of CRI garbage collection; no external cleanup timer

Root cause

During overlapping image rollouts, root usage crossed kubelet's 85% image-GC threshold. Ubuntu's default 5% ext4 reserve hid roughly 46 GiB on the 916 GiB root filesystem. Kubelet then tried to reclaim tens of GiB from a much smaller logical image cache while containerd and Kine were I/O-bound, producing CRI timeouts and stale container-name/cgroup state.

Validation

  • shellcheck home-manager/services/k3s/activate.sh config/k3s/activate.sh
  • shellspec spec/k3s_service_activate_spec.sh spec/activate_k3s_spec.sh (31 examples, 0 failures)
  • git diff --check
  • make nix-format-check
  • native x86_64-linux Home Manager build will be completed on Kyber after the current unrelated application rollout settles

Summary by cubic

Preserves Kyber’s root disk headroom to prevent DiskPressure and CRI timeouts during overlapping image rollouts. Sets a 1% ext4 reserve on the mounted root device, caps kubelet parallel pulls, and makes image GC thresholds explicit while keeping kubelet as the sole GC owner.

  • Bug Fixes
    • Activation script sets ext4 reserved blocks to 1% on the actual root block device via findmnt/tune2fs; no-op if non-ext4.
    • kubelet.conf: maxParallelImagePulls 4 → 2; imageGCHighThresholdPercent: 85, imageGCLowThresholdPercent: 80.
    • No external crictl cleanup timer; GC stays under kubelet.
    • README documents the disk-pressure loop, diagnosis commands, and attended recovery for k3s/containerd/Kine.
    • Tests assert ext4 reserve config and root device detection; default.nix wires awk, findmnt, tune2fs, diff.

Written for commit 8496917. 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

Preserves disk headroom for k3s on the Kyber host after a July 2026 disk-pressure feedback loop, by reclaiming the Ubuntu default ext4 reserve, lowering image-pull concurrency, and documenting the incident + kubelet-owned GC contract. The changes are Kyber-scoped and idempotent.

  • Adds configure_root_ext4_reserve to home-manager/services/k3s/activate.sh that resolves the root block device via findmnt, and if it is ext4 with a non-1% reserve, runs tune2fs -m 1 (skipped otherwise; safe under DRY_RUN_CMD).
  • Wires new awk, findmnt, and tune2fs placeholders through pkgs.replaceVars in home-manager/services/k3s/default.nix.
  • Tightens config/k3s/kubelet.conf: drops maxParallelImagePulls from 4 to 2 and pins imageGCHighThresholdPercent: 85 / imageGCLowThresholdPercent: 80 (kubelet defaults; explicit for documentation).
  • Documents the incident, diagnostic playbook, and the "don't add a crictl cleanup timer" rule in named-hosts/kyber/README.md.
  • Adds two grep-based spec cases in spec/k3s_service_activate_spec.sh covering the 1% target and the findmnt invocation.

Issues

2 potential issues found:

  • Redundant tune2fs -l call: configure_root_ext4_reserve invokes sudo tune2fs -l "$root_source" | awk … twice on every activation to extract two fields from the same output; combine them into one call to halve the sudo/IO overhead. → Autofix
  • imageGCHighThresholdPercent: 85 and imageGCLowThresholdPercent: 80 in config/k3s/kubelet.conf match kubelet's upstream defaults, so the added lines are documentational only and do not change GC behavior — worth noting so reviewers don't misread the diff as tightening the thresholds. → Autofix

CI Checks

Waiting for CI checks...


⚡ Autofix All Issues

@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

K3s kubelet image-pull and garbage-collection settings were updated, and activation now configures ext4 root reserved space to 1% when applicable. Required command paths, activation tests, and Kyber disk-headroom documentation were added.

Changes

K3s disk headroom

Layer / File(s) Summary
Kubelet disk policy
config/k3s/kubelet.conf, named-hosts/kyber/README.md
Kubelet image pulls are capped at two concurrent pulls, image-GC thresholds are set to 85%/80%, and Kyber documentation describes diagnostics and recovery procedures.
Root ext4 reservation activation
home-manager/services/k3s/activate.sh, home-manager/services/k3s/default.nix, spec/k3s_service_activate_spec.sh
Activation resolves the root block device dynamically, sets ext4 reserved space to 1% when required, receives awk, findmnt, and tune2fs paths, and tests the generated setup logic.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ActivationScript
  participant findmnt
  participant tune2fs
  participant K3sSetup
  ActivationScript->>findmnt: Resolve the block device mounted at /
  ActivationScript->>tune2fs: Inspect ext4 reserved-block values
  ActivationScript->>tune2fs: Set reserved blocks to 1% when needed
  ActivationScript->>K3sSetup: Continue k3s service and kubeconfig setup
Loading

Possibly related PRs

Poem

I tuned the root where the disk blocks lie,
Two pulls now hop beneath the sky.
GC keeps space in tidy flight,
K3s starts clean through day and night.
— A grateful rabbit 🐇

🚥 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 is concise and accurately summarizes the main Kyber K3s disk-headroom fix.
Description check ✅ Passed The description is clearly aligned with the changeset and describes the ext4 reserve, kubelet tuning, docs, and validation.
✨ 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/kyber-k3s-disk-headroom

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 optimizes disk headroom and prevents disk-pressure feedback loops on the k3s host. It reduces maxParallelImagePulls to 2, sets explicit image garbage collection thresholds, and adds a bash function to dynamically configure the root ext4 filesystem's reserved blocks to 1%. The Nix configuration, documentation, and tests are updated accordingly. Feedback was provided to append || true to the tune2fs command substitutions in the activation script to prevent premature abortion under set -e and pipefail if the commands fail.

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 on lines +57 to +59
block_count="$(run_sudo @tune2fs@ -l "$root_source" 2>/dev/null | @awk@ -F: '/^Block count:/ { gsub(/[[:space:]]/, "", $2); print $2 }')"
# shellcheck disable=SC2016
reserved_blocks="$(run_sudo @tune2fs@ -l "$root_source" 2>/dev/null | @awk@ -F: '/^Reserved block count:/ { gsub(/[[:space:]]/, "", $2); print $2 }')"

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

Since set -e and pipefail are enabled, if run_sudo @tune2fs@ fails (for example, if sudo requires a password in a non-interactive shell, or if the block device cannot be opened), the command substitution will return a non-zero exit status and prematurely abort the entire activation script.

To prevent this and allow the subsequent if [ -z "$block_count" ] check to handle the failure gracefully, append || true to the pipelines.

Suggested change
block_count="$(run_sudo @tune2fs@ -l "$root_source" 2>/dev/null | @awk@ -F: '/^Block count:/ { gsub(/[[:space:]]/, "", $2); print $2 }')"
# shellcheck disable=SC2016
reserved_blocks="$(run_sudo @tune2fs@ -l "$root_source" 2>/dev/null | @awk@ -F: '/^Reserved block count:/ { gsub(/[[:space:]]/, "", $2); print $2 }')"
block_count="$(run_sudo @tune2fs@ -l "$root_source" 2>/dev/null | @awk@ -F: '/^Block count:/ { gsub(/[[:space:]]/, "", $2); print $2 }' || true)"
# shellcheck disable=SC2016
reserved_blocks="$(run_sudo @tune2fs@ -l "$root_source" 2>/dev/null | @awk@ -F: '/^Reserved block count:/ { gsub(/[[:space:]]/, "", $2); print $2 }' || true)"
References
  1. When 'set -e' is enabled in Bash scripts, ensure that individual command failures within a loop do not prematurely abort the entire script. Handle potential failures gracefully, for example by appending '|| true'.

# shellcheck disable=SC2016
block_count="$(run_sudo @tune2fs@ -l "$root_source" 2>/dev/null | @awk@ -F: '/^Block count:/ { gsub(/[[:space:]]/, "", $2); print $2 }')"
# shellcheck disable=SC2016
reserved_blocks="$(run_sudo @tune2fs@ -l "$root_source" 2>/dev/null | @awk@ -F: '/^Reserved block count:/ { gsub(/[[:space:]]/, "", $2); print $2 }')"

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.

Nit — duplicated tune2fs -l: Both block_count and reserved_blocks are extracted with independent sudo tune2fs -l "$root_source" invocations, each piped to awk. On every home-manager activation this runs tune2fs -l twice. Consider a single call whose output is parsed once, e.g.:

read -r block_count reserved_blocks < <(
  run_sudo @tune2fs@ -l "$root_source" 2>/dev/null \
    | @awk@ -F: '
        /^Block count:/          { gsub(/[[:space:]]/, "", $2); bc=$2 }
        /^Reserved block count:/ { gsub(/[[:space:]]/, "", $2); rc=$2 }
        END                       { print bc, rc }'
)

Purely cosmetic — behavior is identical.

Comment thread config/k3s/kubelet.conf
# by the Kyber activation script, keeping ordinary usage below the low watermark
# while kubelet remains the sole owner of image and container garbage collection.
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80

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.

Nit — explicit defaults: 85 / 80 are already the kubelet defaults per the KubeletConfiguration reference, so these two lines don't change runtime GC behavior; they only pin the values in code. The comment above ("Make the single-node disk contract explicit") makes the intent clear, but it may be worth adding # (kubelet defaults, restated for clarity) so future readers don't infer this was the fix for the July 2026 incident.

@shunkakinoki
shunkakinoki force-pushed the codex/kyber-k3s-disk-headroom branch from 658debe to 8496917 Compare July 19, 2026 07:26
@mesa-dot-dev

mesa-dot-dev Bot commented Jul 19, 2026

Copy link
Copy Markdown

Mesa Description

TL;DR

Prevents K3s disk-pressure feedback loops on Kyber by reducing the ext4 root filesystem reserve to 1% and configuring explicit kubelet image garbage collection thresholds and parallel pull limits.

What changed?

  • config/k3s/kubelet.conf: Reduced maxParallelImagePulls to 2 and set explicit high (85%) and low (80%) image-GC thresholds.
  • home-manager/services/k3s/activate.sh: Added configure_root_ext4_reserve function to dynamically resolve the mounted root block device and tune its reserve block percentage to 1%.
  • home-manager/services/k3s/default.nix: Provided executable paths for awk, findmnt, and tune2fs dependencies in the setup script.
  • named-hosts/kyber/README.md: Added documentation for host disk management, the July 2026 disk-pressure incident, diagnostic commands, and recovery procedures.
  • spec/k3s_service_activate_spec.sh: Added unit tests to verify ext4 reserve adjustments and dynamic block device resolution.

Root cause

During overlapping image rollouts, root usage crossed kubelet's 85% image-GC threshold. Ubuntu's default 5% ext4 reserve hid roughly 46 GiB on the 916 GiB root filesystem. Kubelet then tried to reclaim tens of GiB from a much smaller logical image cache while containerd and Kine were I/O-bound, producing CRI timeouts and stale container-name/cgroup state.

Validation

  • shellcheck home-manager/services/k3s/activate.sh config/k3s/activate.sh
  • shellspec spec/k3s_service_activate_spec.sh spec/activate_k3s_spec.sh (31 examples, 0 failures)
  • git diff --check
  • make nix-format-check
  • native x86_64-linux Home Manager build will be completed on Kyber after the current unrelated application rollout settles

Description generated by Mesa. Update settings

@cubic-dev-ai cubic-dev-ai 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.

3 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="spec/k3s_service_activate_spec.sh">

<violation number="1" location="spec/k3s_service_activate_spec.sh:80">
P2: The grep pattern `target_reserved_percent=1` is a substring match — it also matches `target_reserved_percent=10`, `target_reserved_percent=12`, etc. If the reserved percent is ever changed to a multi-digit value, this test will silently pass even after the script loses the intended value. Adding `-w` isolates the value boundary correctly.</violation>
</file>

<file name="named-hosts/kyber/README.md">

<violation number="1" location="named-hosts/kyber/README.md:72">
P3: The diagnosis checklist says to check I/O pressure, but the commands below do not inspect it, so an operator following this recovery procedure can miss the I/O-bound condition described above. Adding an explicit PSI or `iostat` check would make the checklist complete.</violation>
</file>

<file name="home-manager/services/k3s/activate.sh">

<violation number="1" location="home-manager/services/k3s/activate.sh:74">
P2: If the `tune2fs -l` pipeline fails (e.g., sudo permission issue or transient device error), `set -euo pipefail` exits the subshell, `block_count`/`reserved_blocks` stay empty, and the script exits at line ~65 before the `[ -z "$block_count" ]` guard on line ~74 is reached. The `2>/dev/null` on the tune2fs call hides the original error, making the failure hard to diagnose. The same risk applies to the final `run_sudo @tune2fs@ -m` command on the last changed line — if that fails, the function returns non-zero and `set -e` aborts the entire activation script, skipping k3s service installation.

The ext4 reserve adjustment is a best-effort optimization; it shouldn't block the service file installation and k3s enable/start that follow. Consider guarding the function call with `|| true` so a disk-configuration failure doesn't prevent k3s from being installed. As a secondary improvement, removing `2>/dev/null` (or keeping it but adding `|| true` after each subshell) lets the existing `[ -z "$block_count" ]` warning actually fire when tune2fs fails.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

End

It 'keeps one percent of the ext4 root volume reserved'
When run bash -c "grep 'target_reserved_percent=1' '$SCRIPT'"

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.

P2: The grep pattern target_reserved_percent=1 is a substring match — it also matches target_reserved_percent=10, target_reserved_percent=12, etc. If the reserved percent is ever changed to a multi-digit value, this test will silently pass even after the script loses the intended value. Adding -w isolates the value boundary correctly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/k3s_service_activate_spec.sh, line 80:

<comment>The grep pattern `target_reserved_percent=1` is a substring match — it also matches `target_reserved_percent=10`, `target_reserved_percent=12`, etc. If the reserved percent is ever changed to a multi-digit value, this test will silently pass even after the script loses the intended value. Adding `-w` isolates the value boundary correctly.</comment>

<file context>
@@ -75,5 +75,15 @@ It 'preserves dry-run command handling'
 End
+
+It 'keeps one percent of the ext4 root volume reserved'
+When run bash -c "grep 'target_reserved_percent=1' '$SCRIPT'"
+The output should include 'target_reserved_percent=1'
+End
</file context>
Suggested change
When run bash -c "grep 'target_reserved_percent=1' '$SCRIPT'"
When run bash -c "grep -w 'target_reserved_percent=1' '$SCRIPT'"

echo "Configured $root_source ext4 reserved blocks to ${target_reserved_percent}%"
}

configure_root_ext4_reserve

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.

P2: If the tune2fs -l pipeline fails (e.g., sudo permission issue or transient device error), set -euo pipefail exits the subshell, block_count/reserved_blocks stay empty, and the script exits at line ~65 before the [ -z "$block_count" ] guard on line ~74 is reached. The 2>/dev/null on the tune2fs call hides the original error, making the failure hard to diagnose. The same risk applies to the final run_sudo @tune2fs@ -m command on the last changed line — if that fails, the function returns non-zero and set -e aborts the entire activation script, skipping k3s service installation.

The ext4 reserve adjustment is a best-effort optimization; it shouldn't block the service file installation and k3s enable/start that follow. Consider guarding the function call with || true so a disk-configuration failure doesn't prevent k3s from being installed. As a secondary improvement, removing 2>/dev/null (or keeping it but adding || true after each subshell) lets the existing [ -z "$block_count" ] warning actually fire when tune2fs fails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/services/k3s/activate.sh, line 74:

<comment>If the `tune2fs -l` pipeline fails (e.g., sudo permission issue or transient device error), `set -euo pipefail` exits the subshell, `block_count`/`reserved_blocks` stay empty, and the script exits at line ~65 before the `[ -z "$block_count" ]` guard on line ~74 is reached. The `2>/dev/null` on the tune2fs call hides the original error, making the failure hard to diagnose. The same risk applies to the final `run_sudo @tune2fs@ -m` command on the last changed line — if that fails, the function returns non-zero and `set -e` aborts the entire activation script, skipping k3s service installation.

The ext4 reserve adjustment is a best-effort optimization; it shouldn't block the service file installation and k3s enable/start that follow. Consider guarding the function call with `|| true` so a disk-configuration failure doesn't prevent k3s from being installed. As a secondary improvement, removing `2>/dev/null` (or keeping it but adding `|| true` after each subshell) lets the existing `[ -z "$block_count" ]` warning actually fire when tune2fs fails.</comment>

<file context>
@@ -37,6 +37,42 @@ require_sudo() {
+  echo "Configured $root_source ext4 reserved blocks to ${target_reserved_percent}%"
+}
+
+configure_root_ext4_reserve
+
 if [ -f "$SERVICE_FILE" ] && ! @diff@ -q "$SERVICE_FILE" "$SYSTEM_SERVICE" >/dev/null 2>&1; then
</file context>

chat turns. Reducing the ext4 reserve, limiting pull parallelism, and preserving
free space prevent that loop.

For diagnosis, check filesystem headroom, I/O pressure, kubelet GC messages,

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.

P3: The diagnosis checklist says to check I/O pressure, but the commands below do not inspect it, so an operator following this recovery procedure can miss the I/O-bound condition described above. Adding an explicit PSI or iostat check would make the checklist complete.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At named-hosts/kyber/README.md, line 72:

<comment>The diagnosis checklist says to check I/O pressure, but the commands below do not inspect it, so an operator following this recovery procedure can miss the I/O-bound condition described above. Adding an explicit PSI or `iostat` check would make the checklist complete.</comment>

<file context>
@@ -49,6 +49,42 @@ Once Tailscale is set up:
+chat turns. Reducing the ext4 reserve, limiting pull parallelism, and preserving
+free space prevent that loop.
+
+For diagnosis, check filesystem headroom, I/O pressure, kubelet GC messages,
+and CRI health before restarting services:
+
</file context>

@shunkakinoki
shunkakinoki merged commit 411b671 into main Jul 19, 2026
29 of 32 checks passed
@shunkakinoki
shunkakinoki deleted the codex/kyber-k3s-disk-headroom branch July 19, 2026 07: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