fix: 收敛 NC01 物理内存回收边界
This commit is contained in:
+8
-12
@@ -224,7 +224,7 @@ gc:
|
||||
processSwapLimit: 4
|
||||
commandTimeoutSeconds: 20
|
||||
kubeconfigPath: /etc/rancher/k3s/k3s.yaml
|
||||
targetMemAvailableBytes: 4294967296
|
||||
targetMemAvailableBytes: 5368709120
|
||||
objectCountResources:
|
||||
- pods
|
||||
- pipelineruns.tekton.dev
|
||||
@@ -262,19 +262,15 @@ gc:
|
||||
- /headless_shell
|
||||
cgroupReclaim:
|
||||
enabled: true
|
||||
targetMemAvailableBytes: 4294967296
|
||||
minimumSwapFreeBytesAfterReclaim: 1073741824
|
||||
targetMemAvailableBytes: 5368709120
|
||||
minimumSwapFreeBytesAfterReclaim: 34359738368
|
||||
targets:
|
||||
- id: k3s-file-pages
|
||||
path: /sys/fs/cgroup/system.slice/k3s.service
|
||||
reclaimBytes: 402653184
|
||||
swappiness: 0
|
||||
priority: 10
|
||||
- id: kubepods-file-pages
|
||||
- id: kubepods-inactive-anon
|
||||
path: /sys/fs/cgroup/kubepods.slice
|
||||
reclaimBytes: 536870912
|
||||
swappiness: 0
|
||||
priority: 20
|
||||
reclaimBytes: 3221225472
|
||||
swappiness: 200
|
||||
minimumInactiveAnonBytes: 3221225472
|
||||
priority: 10
|
||||
kubernetesObjectRetention:
|
||||
enabled: true
|
||||
aggregatePreviewLimit: 1
|
||||
|
||||
@@ -73,13 +73,16 @@
|
||||
- 数量、超时、资源和 namespace 范围全部由 `config/unidesk-cli.yaml` 的
|
||||
`memoryPressure.attribution` 拥有。
|
||||
|
||||
NC01 的内存压力文件页回收规则:
|
||||
NC01 的内存压力 cgroup 回收规则:
|
||||
|
||||
- 只从 YAML 精确允许的 k3s cgroup 请求回收;
|
||||
- 固定使用 `swappiness=0`;
|
||||
- 不把 swap 当作可用物理内存;
|
||||
- 不终止 workload,也不重启 k3s;
|
||||
- 执行前由 overview 证明目标 cgroup 存在足够的非匿名页;
|
||||
- `swappiness=0` 的文件页回收必须由 overview 证明目标 cgroup 存在足够的非匿名页;
|
||||
- `swappiness>0` 的匿名页回收必须同时满足:
|
||||
- owning YAML 声明目标 cgroup、回收量、`minimumInactiveAnonBytes` 和 swap 保留线;
|
||||
- plan 与执行前复核 `inactive_anon` 不低于 YAML 下限;
|
||||
- 最坏情况下的换出量不会突破 YAML 声明的 `minimumSwapFreeBytesAfterReclaim`;
|
||||
- 按 `plan`、`run --confirm`、`status`、`memory` 的顺序复核实际结果。
|
||||
|
||||
默认 `/tmp` GC 只包含 allowlisted 诊断目录和已知低风险路径。非 allowlist 的 stale `/tmp` 一级子项必须显式 `--include-stale-tmp` 才能进入候选;扫描按 `--limit` 有界枚举候选,执行时仍只允许删除 `/tmp` 直接子项,并避开 X11/ICE/font socket、systemd private、tmux、ssh、vscode 等系统/session 前缀。该入口不能递归扩大成通用 `/tmp` 清空器,也不能为了估算全量临时目录而长时间阻塞。
|
||||
|
||||
@@ -137,7 +137,7 @@ MEMORY_CONFIG = {
|
||||
"minimumSwapFreeBytesAfterReclaim": 402653184,
|
||||
"targets": [
|
||||
{"id": "file-pages", "path": "/sys/fs/cgroup/system.slice/k3s.service", "reclaimBytes": 402653184, "swappiness": 0, "priority": 10},
|
||||
{"id": "inactive-anon", "path": "/sys/fs/cgroup/system.slice/k3s.service", "reclaimBytes": 1342177280, "swappiness": 100, "priority": 20},
|
||||
{"id": "inactive-anon", "path": "/sys/fs/cgroup/system.slice/k3s.service", "reclaimBytes": 1342177280, "swappiness": 100, "minimumInactiveAnonBytes": 1342177280, "priority": 20},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -361,6 +361,7 @@ def configured_cgroup_reclaim_targets():
|
||||
path = item.get("path")
|
||||
reclaim_bytes = item.get("reclaimBytes")
|
||||
swappiness = item.get("swappiness")
|
||||
minimum_inactive_anon = item.get("minimumInactiveAnonBytes", 0)
|
||||
if not isinstance(target_id, str) or not re.match(r"^[A-Za-z0-9._-]{1,80}$", target_id) or target_id in seen_ids:
|
||||
_MEMORY_RECLAIM_CONFIG_ERROR = "yaml-cgroup-reclaim-target-id-invalid:%s" % index
|
||||
return []
|
||||
@@ -373,6 +374,12 @@ def configured_cgroup_reclaim_targets():
|
||||
if isinstance(swappiness, bool) or not isinstance(swappiness, int) or swappiness < 0 or swappiness > 200:
|
||||
_MEMORY_RECLAIM_CONFIG_ERROR = "yaml-cgroup-reclaim-swappiness-invalid:%s" % target_id
|
||||
return []
|
||||
if isinstance(minimum_inactive_anon, bool) or not isinstance(minimum_inactive_anon, int) or minimum_inactive_anon < 0:
|
||||
_MEMORY_RECLAIM_CONFIG_ERROR = "yaml-cgroup-reclaim-minimum-inactive-anon-invalid:%s" % target_id
|
||||
return []
|
||||
if swappiness > 0 and minimum_inactive_anon < reclaim_bytes:
|
||||
_MEMORY_RECLAIM_CONFIG_ERROR = "yaml-cgroup-reclaim-minimum-inactive-anon-too-small:%s" % target_id
|
||||
return []
|
||||
priority = item.get("priority", index + 1)
|
||||
if isinstance(priority, bool) or not isinstance(priority, int) or priority < 1 or priority > 1000:
|
||||
_MEMORY_RECLAIM_CONFIG_ERROR = "yaml-cgroup-reclaim-priority-invalid:%s" % target_id
|
||||
@@ -383,6 +390,7 @@ def configured_cgroup_reclaim_targets():
|
||||
"path": path,
|
||||
"reclaimBytes": reclaim_bytes,
|
||||
"swappiness": swappiness,
|
||||
"minimumInactiveAnonBytes": minimum_inactive_anon,
|
||||
"priority": priority,
|
||||
"targetMemAvailableBytes": target_available,
|
||||
"minimumSwapFreeBytesAfterReclaim": swap_reserve,
|
||||
@@ -416,15 +424,19 @@ def collect_cgroup_reclaim_candidates():
|
||||
elif safe_int(item.get("swappiness")) > 0 and swap_free - safe_int(item.get("reclaimBytes")) < safe_int(item.get("minimumSwapFreeBytesAfterReclaim")):
|
||||
protected_reason = "minimum-swap-free-reserve"
|
||||
current = read_cgroup_integer(path, "memory.current")
|
||||
memory_stat = read_cgroup_memory_stat(path)
|
||||
inactive_anon = safe_int(memory_stat.get("inactive_anon"))
|
||||
if protected_reason is None and safe_int(item.get("swappiness")) > 0 and inactive_anon < safe_int(item.get("minimumInactiveAnonBytes")):
|
||||
protected_reason = "minimum-inactive-anon-not-met"
|
||||
if protected_reason is not None:
|
||||
diagnostics["protected"].append({
|
||||
"id": item.get("id"),
|
||||
"path": path,
|
||||
"reason": protected_reason,
|
||||
"memoryCurrentBytes": current,
|
||||
"inactiveAnonBytes": inactive_anon,
|
||||
})
|
||||
continue
|
||||
memory_stat = read_cgroup_memory_stat(path)
|
||||
reclaim_bytes = safe_int(item.get("reclaimBytes"))
|
||||
candidates.append({
|
||||
"id": "cgroup-memory-reclaim:%s" % item.get("id"),
|
||||
@@ -437,6 +449,7 @@ def collect_cgroup_reclaim_candidates():
|
||||
"priority": safe_int(item.get("priority")),
|
||||
"reclaimBytes": reclaim_bytes,
|
||||
"swappiness": safe_int(item.get("swappiness")),
|
||||
"minimumInactiveAnonBytes": safe_int(item.get("minimumInactiveAnonBytes")),
|
||||
"targetMemAvailableBytes": safe_int(item.get("targetMemAvailableBytes")),
|
||||
"minimumSwapFreeBytesAfterReclaim": safe_int(item.get("minimumSwapFreeBytesAfterReclaim")),
|
||||
"memoryCurrentBytes": current,
|
||||
@@ -667,7 +680,7 @@ def execute_cgroup_reclaim_candidate(candidate):
|
||||
configured = next((item for item in configured_cgroup_reclaim_targets() if item.get("id") == config_id), None)
|
||||
if configured is None:
|
||||
raise RuntimeError("cgroup reclaim candidate is no longer present in YAML allowlist")
|
||||
for key in ["path", "reclaimBytes", "swappiness", "targetMemAvailableBytes", "minimumSwapFreeBytesAfterReclaim"]:
|
||||
for key in ["path", "reclaimBytes", "swappiness", "minimumInactiveAnonBytes", "targetMemAvailableBytes", "minimumSwapFreeBytesAfterReclaim"]:
|
||||
if configured.get(key) != candidate.get(key):
|
||||
raise RuntimeError("cgroup reclaim candidate no longer matches YAML: %s" % key)
|
||||
path = configured.get("path")
|
||||
@@ -698,6 +711,9 @@ def execute_cgroup_reclaim_candidate(candidate):
|
||||
required = safe_int(configured.get("reclaimBytes")) + safe_int(configured.get("minimumSwapFreeBytesAfterReclaim"))
|
||||
if swap_free < required:
|
||||
raise RuntimeError("cgroup reclaim would violate YAML minimum SwapFree reserve")
|
||||
inactive_anon = safe_int(read_cgroup_memory_stat(path).get("inactive_anon"))
|
||||
if inactive_anon < safe_int(configured.get("minimumInactiveAnonBytes")):
|
||||
raise RuntimeError("minimum inactive anonymous memory is no longer available")
|
||||
cgroup_before = read_cgroup_integer(path, "memory.current")
|
||||
write_error = None
|
||||
try:
|
||||
|
||||
@@ -411,6 +411,7 @@ function compactMemoryPressureCandidate(value: unknown): Record<string, unknown>
|
||||
path: candidate.path,
|
||||
reclaimBytes: candidate.reclaimBytes,
|
||||
swappiness: candidate.swappiness,
|
||||
minimumInactiveAnonBytes: candidate.minimumInactiveAnonBytes,
|
||||
targetMemAvailableBytes: candidate.targetMemAvailableBytes,
|
||||
minimumSwapFreeBytesAfterReclaim: candidate.minimumSwapFreeBytesAfterReclaim,
|
||||
memoryCurrentBytes: candidate.memoryCurrentBytes,
|
||||
@@ -446,9 +447,14 @@ function memoryPressureCandidateAllowed(value: unknown): boolean {
|
||||
const action = recordOrEmpty(candidate.action);
|
||||
if (typeof candidate.id !== "string" || candidate.id.length === 0 || positiveInteger(candidate.expectedMemoryRssBytes) === null) return false;
|
||||
if (candidate.kind === "cgroup-memory-reclaim") {
|
||||
const swappiness = typeof candidate.swappiness === "number" && Number.isInteger(candidate.swappiness)
|
||||
? candidate.swappiness
|
||||
: null;
|
||||
return typeof candidate.configId === "string" && candidate.configId.length > 0
|
||||
&& typeof candidate.path === "string" && candidate.path.startsWith("/")
|
||||
&& positiveInteger(candidate.reclaimBytes) !== null
|
||||
&& swappiness !== null && swappiness >= 0 && swappiness <= 200
|
||||
&& (swappiness === 0 || positiveInteger(candidate.minimumInactiveAnonBytes) !== null)
|
||||
&& action.op === "cgroup-v2-memory-reclaim"
|
||||
&& action.allowlist === "yaml-exact-cgroup-path";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user