-
Notifications
You must be signed in to change notification settings - Fork 0
fix(k3s): preserve Kyber disk headroom #2126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||
| # Install the generated k3s service and sync kubeconfig for the current user. | ||||||||||||||
| # @diff@ and @systemctl@ are substituted by pkgs.replaceVars. | ||||||||||||||
| # Command placeholders are substituted by pkgs.replaceVars. | ||||||||||||||
| set -euo pipefail | ||||||||||||||
|
|
||||||||||||||
| SERVICE_FILE="$1" | ||||||||||||||
|
|
@@ -37,6 +37,42 @@ require_sudo() { | |||||||||||||
| fi | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| configure_root_ext4_reserve() { | ||||||||||||||
| local root_source root_fs_type block_count reserved_blocks target_reserved_blocks | ||||||||||||||
| local target_reserved_percent=1 | ||||||||||||||
|
|
||||||||||||||
| root_source="$(@findmnt@ --noheadings --output SOURCE --target /)" | ||||||||||||||
| root_fs_type="$(@findmnt@ --noheadings --output FSTYPE --target /)" | ||||||||||||||
|
|
||||||||||||||
| if [ "$root_fs_type" != "ext4" ]; then | ||||||||||||||
| return 0 | ||||||||||||||
| fi | ||||||||||||||
| if [ ! -b "$root_source" ]; then | ||||||||||||||
| echo "Warning: ext4 root source is not a block device: $root_source" >&2 | ||||||||||||||
| return 0 | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| require_sudo || return 0 | ||||||||||||||
| # 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 }')" | ||||||||||||||
|
Comment on lines
+57
to
+59
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since To prevent this and allow the subsequent
Suggested change
References
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit — duplicated 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. |
||||||||||||||
| if [ -z "$block_count" ] || [ -z "$reserved_blocks" ]; then | ||||||||||||||
| echo "Warning: unable to inspect ext4 reserve on $root_source" >&2 | ||||||||||||||
| return 0 | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| target_reserved_blocks=$((block_count * target_reserved_percent / 100)) | ||||||||||||||
| if [ "$reserved_blocks" -eq "$target_reserved_blocks" ]; then | ||||||||||||||
| return 0 | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| run_sudo @tune2fs@ -m "$target_reserved_percent" "$root_source" | ||||||||||||||
| echo "Configured $root_source ext4 reserved blocks to ${target_reserved_percent}%" | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| configure_root_ext4_reserve | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: If the 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 Prompt for AI agents |
||||||||||||||
|
|
||||||||||||||
| if [ -f "$SERVICE_FILE" ] && ! @diff@ -q "$SERVICE_FILE" "$SYSTEM_SERVICE" >/dev/null 2>&1; then | ||||||||||||||
| require_sudo || exit 0 | ||||||||||||||
| run_sudo cp "$SERVICE_FILE" "$SYSTEM_SERVICE" | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,42 @@ Once Tailscale is set up: | |
| kyber # Fish abbreviation that runs: ssh ubuntu@kyber | ||
| ``` | ||
|
|
||
| ## k3s Disk Headroom | ||
|
|
||
| Kyber runs k3s and its embedded containerd on the root ext4 filesystem. The | ||
| host activation keeps ext4 reserved blocks at 1% and limits kubelet to two | ||
| parallel image pulls. On this 916 GiB volume, Ubuntu's default 5% reserve hid | ||
| about 46 GiB from kubelet and left too little usable headroom during overlapping | ||
| application rollouts. | ||
|
|
||
| Kubelet owns image, container, and pod-sandbox garbage collection. Do not add a | ||
| separate `crictl` cleanup timer: deleting CRI objects behind kubelet can race | ||
| active pod lifecycle operations and leave container names or cgroups stuck. | ||
|
|
||
| The July 2026 incident was a disk-pressure feedback loop, not a slow Temporal | ||
| queue. Root usage crossed kubelet's 85% image-GC threshold during concurrent | ||
| image pulls. Kubelet attempted to reclaim tens of GiB from a much smaller | ||
| logical image cache while containerd and Kine were already I/O-bound. CRI calls | ||
| timed out, stale tasks accumulated, and Temporal workers could not start new | ||
| 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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Prompt for AI agents |
||
| and CRI health before restarting services: | ||
|
|
||
| ```bash | ||
| df -h / | ||
| sudo tune2fs -l "$(findmnt -n -o SOURCE /)" | grep -E 'Block count|Reserved block count' | ||
| sudo journalctl -u k3s --since '30 minutes ago' | grep -E 'image garbage collection|DiskPressure|deadline exceeded' | ||
| sudo k3s crictl info | ||
| ``` | ||
|
|
||
| An ordinary `systemctl restart k3s` intentionally preserves running containers | ||
| because the upstream unit uses `KillMode=process`. If containerd itself is | ||
| wedged, use the installed `k3s-killall.sh` once during an attended recovery, | ||
| then start k3s again. The helper preserves cluster data but terminates every | ||
| running workload, so it is not a timer or routine cleanup mechanism. | ||
|
|
||
| ## SSH Key Management | ||
|
|
||
| ### Automated Setup | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -75,5 +75,15 @@ It 'preserves dry-run command handling' | |||||
| When run bash -c "grep 'DRY_RUN_CMD' '$SCRIPT'" | ||||||
| The output should include 'DRY_RUN_CMD' | ||||||
| End | ||||||
|
|
||||||
| It 'keeps one percent of the ext4 root volume reserved' | ||||||
| When run bash -c "grep 'target_reserved_percent=1' '$SCRIPT'" | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The grep pattern Prompt for AI agents
Suggested change
|
||||||
| The output should include 'target_reserved_percent=1' | ||||||
| End | ||||||
|
|
||||||
| It 'resolves the mounted root block device instead of hard-coding it' | ||||||
| When run bash -c "grep '@findmnt@ --noheadings --output SOURCE --target /' '$SCRIPT'" | ||||||
| The output should include '@findmnt@ --noheadings --output SOURCE --target /' | ||||||
| End | ||||||
| End | ||||||
| End | ||||||
There was a problem hiding this comment.
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
KubeletConfigurationreference, 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.