Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions docs/en/solutions/Configure_Kubelet_Log_Level_Verbosity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
kind:
- Troubleshooting
products:
- Alauda Container Platform
ProductsVersion:
- 4.1.0,4.2.x
---
## Issue

When troubleshooting node-level problems, increasing the kubelet log verbosity helps identify the root cause. The default log level (`2`) may not provide enough detail for complex issues such as pod scheduling failures, volume mount errors, or container runtime communication problems.

## Root Cause

The kubelet supports configurable log verbosity levels ranging from `0` (least verbose) to `10` (most verbose). The default level is `2`, which provides basic operational information. Higher levels expose progressively more diagnostic data, but consume additional CPU, disk I/O, and memory on the node.

## Resolution

### Log Level Reference

| Level Range | Purpose |
|---|---|
| 0 | Critical errors only |
| 1–2 | Default operational output |
| 3–4 | Debug-level information, suitable for most troubleshooting |
| 5–8 | Trace-level output, verbose internal state dumps |
| 9–10 | Maximum verbosity, rarely needed |

### Persistent Configuration — KubeletConfiguration (preferred on kubeadm clusters)

On kubeadm-provisioned clusters the kubelet is configured through `/var/lib/kubelet/config.yaml`. Set the `verbosity` field there and restart kubelet:

```bash
# On the target node
sudo sed -i 's/^\s*verbosity:.*/verbosity: 4/; t; $a\verbosity: 4' /var/lib/kubelet/config.yaml
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify actual key path used by kubelet config on a node
sudo grep -nE '^\s*logging:|^\s*verbosity:' /var/lib/kubelet/config.yaml

Repository: alauda/knowledge

Length of output: 104


🏁 Script executed:

# Search for kubelet config examples or references in the repository
rg -i 'kubelet.*config|logging\.verbosity|verbosity' docs/ --type md -A 2 -B 2

Repository: alauda/knowledge

Length of output: 11838


🏁 Script executed:

# Check if there are any existing kubelet configuration examples in the repo
fd -e yaml -e json -e md | xargs rg -l 'kubelet' | head -20

Repository: alauda/knowledge

Length of output: 400


🌐 Web query:

Kubernetes kubelet configuration verbosity field path schema logging.verbosity

💡 Result:

The Kubernetes kubelet configuration includes a logging.verbosity field in the KubeletConfiguration struct (v1beta1 API). This field is nested under the top-level logging object and specifies the log verbosity threshold as an integer (VerbosityLevel, alias of uint32). Default is 0 (only most important messages); higher values (e.g., 2-4 for debug, 5-8 for trace) enable more detailed logging. Error messages are always logged. Full path in YAML/JSON config: logging.verbosity Example from official docs: logging: format: "text" flushFrequency: "5s" verbosity: 3 options: json: infoBufferSize: "0" Schema details: - Type: VerbosityLevel (uint32) - Description: Threshold for log messages; higher values log more details. - Appears in: LoggingConfiguration within KubeletConfiguration. Configure via --config flag pointing to a YAML/JSON file matching this schema. Command-line --v flag is deprecated in favor of config file for this parameter.

Citations:


verbosity field path does not match KubeletConfiguration schema.

Per the official Kubernetes kubelet config schema (v1beta1), verbosity is nested under logging.verbosity, not a top-level verbosity field. The sed command will create a top-level field that kubelet ignores, causing the configuration to silently fail.

Suggested fix
- sudo sed -i 's/^\s*verbosity:.*/verbosity: 4/; t; $a\verbosity: 4' /var/lib/kubelet/config.yaml
+ sudo yq -i '.logging.verbosity = 4' /var/lib/kubelet/config.yaml
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sudo sed -i 's/^\s*verbosity:.*/verbosity: 4/; t; $a\verbosity: 4' /var/lib/kubelet/config.yaml
sudo yq -i '.logging.verbosity = 4' /var/lib/kubelet/config.yaml
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/en/solutions/Configure_Kubelet_Log_Level_Verbosity.md` at line 35, The
sed command in the diff (the expression matching 's/^\s*verbosity:.*/verbosity:
4/') writes a top-level verbosity key, but kubelet expects logging.verbosity;
update the fix to set logging.verbosity instead of a top-level field. Replace
the current sed with logic that updates or creates the nested logging: block and
its verbosity: entry (for example, use yq to set '.logging.verbosity = 4' or
with sed/awk ensure a logging: block exists and then set/append '  verbosity:
4'); ensure you update the exact command that currently matches '^\s*verbosity:'
so it targets 'logging.verbosity' (or manipulates the logging: section) rather
than adding a top-level verbosity key.

sudo systemctl restart kubelet
```

This works regardless of how the systemd unit passes arguments to kubelet, and kubeadm-based automation will preserve it across upgrades.

### Persistent Configuration — systemd drop-in (fallback)

If you cannot edit `config.yaml` (some operator-managed setups lock the file), override the kubelet `ExecStart` via a drop-in that **inlines the `--v` flag directly**. Setting a bare environment variable like `KUBELET_LOG_LEVEL=4` does **not** raise verbosity — the stock kubeadm systemd unit only expands the three specific variables `$KUBELET_KUBECONFIG_ARGS`, `$KUBELET_CONFIG_ARGS`, and `$KUBELET_KUBEADM_ARGS`; any other name (including `KUBELET_LOG_LEVEL` or `KUBELET_EXTRA_ARGS`) is silently ignored.
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect real kubelet unit/env expansion on target nodes
systemctl cat kubelet | sed -n '/^\[Service\]/,/^\[/p'
systemctl show kubelet -p FragmentPath -p DropInPaths -p EnvironmentFiles

Repository: alauda/knowledge

Length of output: 157


🏁 Script executed:

# Search the repository for references to kubelet systemd units, KUBELET_EXTRA_ARGS, or ExecStart
rg -i "kubelet.*extra.*args|KUBELET_EXTRA_ARGS|ExecStart.*kubelet" --type md --type txt --type yaml --type json

Repository: alauda/knowledge

Length of output: 1200


🏁 Script executed:

# Look for any kubeadm or kubelet configuration files in the repository
fd -i "kubelet|kubeadm" -type f | head -20

Repository: alauda/knowledge

Length of output: 230


🌐 Web query:

kubeadm systemd unit template kubelet environment variables KUBELET_EXTRA_ARGS official documentation

💡 Result:

The official Kubernetes documentation describes the kubeadm systemd unit template for the kubelet in the page "Configuring each kubelet in your cluster using kubeadm" at https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/kubelet-integration/. The key drop-in file is /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf, which augments the base kubelet.service. It sources environment variables including KUBELET_EXTRA_ARGS from /etc/default/kubelet (DEB packages) or /etc/sysconfig/kubelet (RPM packages). Here is the template content from the official docs: [Service] Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" # This is a file that "kubeadm init" and "kubeadm join" generate at runtime, populating # the KUBELET_KUBEADM_ARGS variable dynamically EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, # the user should use the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. # KUBELET_EXTRA_ARGS should be sourced from this file. EnvironmentFile=-/etc/default/kubelet ExecStart= ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS To use KUBELET_EXTRA_ARGS, add it to /etc/default/kubelet (or /etc/sysconfig/kubelet on RPM), e.g.: KUBELET_EXTRA_ARGS="--fail-swap-on=false --max-pods=110" Then run systemctl daemon-reload && systemctl restart kubelet. This has highest priority among the arg sources. Prefer NodeRegistration.KubeletExtraArgs in kubeadm config files instead.

Citations:


Correct the claim about expanded variables in kubeadm systemd units.

The official Kubernetes documentation shows that the stock kubeadm systemd unit template DOES expand $KUBELET_EXTRA_ARGS in the ExecStart line. The template sources this from /etc/default/kubelet (DEB) or /etc/sysconfig/kubelet (RPM). Saying that KUBELET_EXTRA_ARGS is "silently ignored" is factually incorrect — it is an officially supported mechanism with the highest priority among argument sources. The documentation should specify all four expanded variables ($KUBELET_KUBECONFIG_ARGS, $KUBELET_CONFIG_ARGS, $KUBELET_KUBEADM_ARGS, and $KUBELET_EXTRA_ARGS) rather than claiming only three are supported.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/en/solutions/Configure_Kubelet_Log_Level_Verbosity.md` at line 43,
Update the paragraph to correct the factual error: state that the kubeadm
systemd unit template expands four variables in ExecStart —
$KUBELET_KUBECONFIG_ARGS, $KUBELET_CONFIG_ARGS, $KUBELET_KUBEADM_ARGS, and
$KUBELET_EXTRA_ARGS — and remove the claim that KUBELET_EXTRA_ARGS is "silently
ignored"; instead note that KUBELET_EXTRA_ARGS (sourced from
/etc/default/kubelet on DEB or /etc/sysconfig/kubelet on RPM) is an officially
supported override and has the highest priority among argument sources, while
still clarifying that setting an unrelated env var like KUBELET_LOG_LEVEL will
not affect kubelet args unless placed into one of those expanded variables.


```bash
sudo mkdir -p /etc/systemd/system/kubelet.service.d/
sudo tee /etc/systemd/system/kubelet.service.d/10-log-level.conf <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS --v=4
EOF
sudo systemctl daemon-reload
sudo systemctl restart kubelet
```

The `ExecStart=` (empty) line clears the parent unit's ExecStart; the second line rebuilds it with `--v=4` appended.

### Persistent Configuration (Immutable OS Nodes)

On immutable-OS nodes — MicroOS, or any setup where `/etc` is backed by a read-mostly overlay that is reset on node upgrades or rollbacks — direct file edits under `/etc/systemd/system/kubelet.service.d/` **will not survive the next node update**. You may see the desired verbosity right after the change, then lose it silently when the node image is replaced.

Persist the change through ACP's Immutable Infrastructure mechanism instead:

- Define the drop-in file as part of the node configuration managed by ACP (under `configure/clusters/nodes`). The platform renders and re-applies it every time a node boots, so the override survives OS upgrades and rollbacks.
- Trigger a rolling apply on the target node pool. ACP will cordon/drain, restart the kubelet with the new verbosity, and resume scheduling.
- Revert the same way — update the node configuration to remove the override; do not `rm` the file directly on the node, because the mutation will be lost at the next reconcile.

If the cluster spans both mutable and immutable nodes, scope the change to a node group / pool so that only the intended nodes carry the higher verbosity.

### One-Time Change (Single Node)

For temporary debugging on a single mutable-OS node, use the same drop-in pattern shown above via `systemctl edit`:

```bash
sudo systemctl edit kubelet
```

Enter the override block — again, the flag must be inlined into `ExecStart`, not placed into a bare environment variable:

```ini
[Service]
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS --v=4
```

Then reload and restart:

```bash
sudo systemctl daemon-reload
sudo systemctl restart kubelet
```

On immutable-OS nodes, prefer the Immutable Infrastructure flow above even for short investigations: running `systemctl edit` on a single node works until that node is re-imaged, at which point the change is gone without warning.

> **Important:** Revert the log level back to the default (`2`) after collecting the necessary logs. Extended operation at high verbosity places significant load on node resources.

## Diagnostic Steps

Verify the current kubelet log level. On kubeadm-provisioned clusters kubelet typically does **not** carry `--v` on its command line — verbosity comes from `config.yaml` — so `ps` on its own reports nothing even when verbosity is set. Check both locations:

```bash
# The KubeletConfiguration path (primary on kubeadm clusters)
sudo grep -E '^\s*verbosity:' /var/lib/kubelet/config.yaml || echo "verbosity: (default 2)"

# The command-line path (only populated if you added --v via a drop-in)
ps aux | grep kubelet | grep -oE '\-\-v=[0-9]+' || echo "(no --v on cmdline)"
```

Gather kubelet logs from a specific node:

```bash
kubectl get nodes
kubectl debug node/<node-name> --image=busybox -- cat /host/var/log/kubelet.log
```

Alternatively, SSH into the node and use journalctl:

```bash
ssh <node-address>
sudo journalctl -b -f -u kubelet.service
```

To collect logs from all nodes at once:

```bash
for n in $(kubectl get nodes --no-headers | awk '{print $1}'); do
ssh "$n" "sudo journalctl -u kubelet.service --since '1 hour ago'" > "${n}.kubelet.log"
done
Comment on lines +126 to +128
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.

⚠️ Potential issue | 🟡 Minor

Using Kubernetes node name as SSH host is brittle.

ssh "$n" often fails because node name is not guaranteed to be DNS-resolvable/reachable from the runner host.

Portable alternative using InternalIP
-for n in $(kubectl get nodes --no-headers | awk '{print $1}'); do
-  ssh "$n" "sudo journalctl -u kubelet.service --since '1 hour ago'" > "${n}.kubelet.log"
+kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{" "}{range .status.addresses[*]}{.type}={.address}{" "}{end}{"\n"}{end}' \
+| while read -r name addrs; do
+  ip=$(printf '%s\n' "$addrs" | tr ' ' '\n' | awk -F= '$1=="InternalIP"{print $2; exit}')
+  [ -n "$ip" ] && ssh "$ip" "sudo journalctl -u kubelet.service --since '1 hour ago'" > "${name}.kubelet.log"
 done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for n in $(kubectl get nodes --no-headers | awk '{print $1}'); do
ssh "$n" "sudo journalctl -u kubelet.service --since '1 hour ago'" > "${n}.kubelet.log"
done
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{" "}{range .status.addresses[*]}{.type}={.address}{" "}{end}{"\n"}{end}' \
| while read -r name addrs; do
ip=$(printf '%s\n' "$addrs" | tr ' ' '\n' | awk -F= '$1=="InternalIP"{print $2; exit}')
[ -n "$ip" ] && ssh "$ip" "sudo journalctl -u kubelet.service --since '1 hour ago'" > "${name}.kubelet.log"
done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/en/solutions/Configure_Kubelet_Log_Level_Verbosity.md` around lines 126
- 128, The loop uses node names (variable n and ssh "$n") which may not be
DNS-resolvable; change the selection to use each node's InternalIP and keep the
node name for filenames: iterate over pairs (node name and InternalIP) by
replacing the kubectl invocation with a jsonpath or custom-columns query (e.g.,
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}
{.status.addresses[?(@.type=="InternalIP")].address}{"\n"}{end}' or equivalent),
then in the loop use ssh "$IP" to run sudo journalctl -u kubelet.service --since
'1 hour ago' and redirect to "${NODE}.kubelet.log" so SSH targets the reachable
IP while filenames still use the node name.

```
Loading