fix: 披露 NC01 留存策略安装漂移
This commit is contained in:
@@ -151,6 +151,8 @@ bun scripts/cli.ts gc remote <providerId> status --job-id <id>
|
||||
- 活跃、过新、未知分组、时间不可解析、扫描截断和 Table 形状漂移全部保护;
|
||||
- 只删除 YAML 指定的根对象,通过 ownerRef 级联,不直接删除下游 TaskRun、Pod、Secret 或 Event;
|
||||
- 自动回收由 `gc remote <providerId> policy plan|install --confirm|status` 管理,周期和单批上限由 YAML 控制。
|
||||
- `policy status` 必须一次披露期望与安装态的配置/runner 指纹、关键留存字段和 `inSync`;
|
||||
- owning YAML 变化后只通过 `policy install --confirm` 收敛安装态,禁止直接修改 host JSON、runner 或 systemd unit。
|
||||
|
||||
## WebProbe 低内存人工处置
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ gc:
|
||||
keepLatestPerGroup: 3
|
||||
manualMaxDeletePerRun: 500
|
||||
automaticMaxDeletePerRun: 100
|
||||
previewLimit: 10
|
||||
previewLimit: 5
|
||||
scanLimit: 5000
|
||||
requestTimeoutSeconds: 45
|
||||
deleteBatchSize: 50
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
#### R1.2.4 [completed]
|
||||
|
||||
完成 [UniDesk #1710](https://github.com/pikasTech/unidesk/issues/1710) 的 Kafka 周边内存调优:保持纯 Kafka 实时/回放 SSE 架构不变,把 broker、Strimzi Cluster Operator、Topic/User Entity Operator 的 JVM 与 Kubernetes 资源预算,以及临时 consumer group 生命周期收敛到 `config/platform-infra/kafka.yaml`;经受控渲染、正常 PR 自动交付后验证 Kafka topic/client/producer/consumer 正常并对比物理内存,完成任务后将详细报告写入[任务报告](./details/web-observe-runtime-reliability/R1.2.4_Task_Report.md)。
|
||||
#### R1.2.5 [in_progress]
|
||||
|
||||
完成 [UniDesk #1710](https://github.com/pikasTech/unidesk/issues/1710) 的 NC01 CI 历史对象自动留存对齐与 k3s 内存降载:修复 YAML 期望与 host timer 安装态不可见、不同步问题,使 `gc remote <node> policy status` 一次披露配置/runner 指纹、关键留存字段和漂移结论;通过 YAML-first 自动留存回收终态 PipelineRun owner 树,验证对象数量、k3s 内存与真实 `MemAvailable`,明确不触碰 Webterm,完成任务后将详细报告写入[任务报告](./details/web-observe-runtime-reliability/R1.2.5_Task_Report.md)。
|
||||
### R1.3
|
||||
|
||||
测试与验收:https://github.com/pikasTech/unidesk/issues/1842;覆盖生命周期、stale/protected 分类和失败路径,提交独立 PR;合并后只等待自动 CI/CD 发布链,使用正式入口验证 observer/Chrome 退出、内存压力缓解且健康运行面不受扰动,完成任务后将详细报告写入[任务报告](./details/web-observe-runtime-reliability/R1.3_Task_Report.md)。
|
||||
|
||||
@@ -151,6 +151,11 @@ PK01 pikanode temp retention 只允许清理 `/home/ubuntu/pikanode/html/temp`
|
||||
- 不直接删除 TaskRun、Pod、Secret 或 Event;
|
||||
- 手动入口提交异步 job 后必须用 `status --job-id` 查询终态;
|
||||
- 自动 systemd timer 复用同一筛选器,并把每轮结果写入 YAML 指定的状态文件。
|
||||
- 策略安装态核对固定使用 `gc remote NC01 policy status`:
|
||||
- `policySync` 必须同时披露期望与安装态的配置/runner SHA-256、字节数和关键留存字段;
|
||||
- `inSync=false` 时只通过 `gc remote NC01 policy install --confirm` 从 owning YAML 重装,禁止直接修改 host JSON 或 Python runner;
|
||||
- owning YAML 的周期、保留窗口或阶段配置发生变化后,必须重新安装策略,并以 `policySync.inSync=true` 作为配置收敛证据。
|
||||
- `retention plan` 的默认分组与候选预览数量由同一 YAML 的 `previewLimit` 控制,默认输出必须保留摘要且不触发 stdout 截断;完整明细只在显式 `--full` 时披露。
|
||||
|
||||
## JD01 远端策略
|
||||
|
||||
|
||||
@@ -476,9 +476,9 @@ def run_kubernetes_object_retention_policy(config):
|
||||
"id": "kubernetes-object-retention",
|
||||
"ok": result.get("ok") is True,
|
||||
"skipped": result.get("mutation") is not True,
|
||||
"configId": result.get("configId"),
|
||||
"namespace": result.get("namespace"),
|
||||
"resource": result.get("resource"),
|
||||
"configId": result.get("configId") or retention.get("configId"),
|
||||
"namespace": result.get("namespace") or retention.get("namespace"),
|
||||
"resource": result.get("resource") or retention.get("resource"),
|
||||
"candidateCount": ((result.get("plan") or {}).get("summary") or {}).get("eligibleCount"),
|
||||
"requestedDeleteCount": result.get("requestedDeleteCount"),
|
||||
"deletedCount": result.get("deletedCount"),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import base64
|
||||
import calendar
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -1964,6 +1965,7 @@ def remote_policy_status_payload(observed_at):
|
||||
last = None
|
||||
if last is not None and not bool(OPTIONS.get("full")):
|
||||
last = compact_policy_last_run(last)
|
||||
policy_sync = remote_policy_sync_payload(rendered)
|
||||
return {
|
||||
"ok": True,
|
||||
"action": "gc remote policy status",
|
||||
@@ -1976,6 +1978,7 @@ def remote_policy_status_payload(observed_at):
|
||||
"timer": bounded(timer),
|
||||
"service": bounded(service),
|
||||
},
|
||||
"policySync": policy_sync,
|
||||
"lastRun": last,
|
||||
"next": {
|
||||
"plan": "bun scripts/cli.ts gc remote %s policy plan" % PROVIDER_ID,
|
||||
@@ -1984,6 +1987,78 @@ def remote_policy_status_payload(observed_at):
|
||||
},
|
||||
}
|
||||
|
||||
def sha256_summary(data):
|
||||
return {
|
||||
"bytes": len(data),
|
||||
"sha256": hashlib.sha256(data).hexdigest(),
|
||||
}
|
||||
|
||||
def file_sha256_summary(path):
|
||||
try:
|
||||
with open(path, "rb") as handle:
|
||||
data = handle.read()
|
||||
return {"present": True, **sha256_summary(data)}
|
||||
except FileNotFoundError:
|
||||
return {"present": False, "bytes": None, "sha256": None, "error": "not-found"}
|
||||
except Exception as exc:
|
||||
return {"present": False, "bytes": None, "sha256": None, "error": str(exc)}
|
||||
|
||||
def retention_policy_summary(config):
|
||||
retention = config.get("kubernetesObjectRetention") if isinstance(config, dict) else None
|
||||
if not isinstance(retention, dict):
|
||||
return None
|
||||
return {
|
||||
key: retention.get(key)
|
||||
for key in [
|
||||
"enabled",
|
||||
"configId",
|
||||
"namespace",
|
||||
"resource",
|
||||
"minAgeHours",
|
||||
"keepLatestPerGroup",
|
||||
"automaticMaxDeletePerRun",
|
||||
]
|
||||
}
|
||||
|
||||
def remote_policy_sync_payload(rendered):
|
||||
desired_config_data = (json.dumps(rendered["policyConfig"], sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
|
||||
desired_script_data = rendered["script"].encode("utf-8")
|
||||
desired_config = sha256_summary(desired_config_data)
|
||||
desired_script = sha256_summary(desired_script_data)
|
||||
installed_config = file_sha256_summary(rendered["configPath"])
|
||||
installed_script = file_sha256_summary(rendered["scriptPath"])
|
||||
installed_policy = None
|
||||
installed_config_error = None
|
||||
try:
|
||||
with open(rendered["configPath"], "r", encoding="utf-8") as handle:
|
||||
installed_policy = json.load(handle)
|
||||
except Exception as exc:
|
||||
installed_config_error = str(exc)
|
||||
config_in_sync = installed_config.get("sha256") == desired_config.get("sha256")
|
||||
runner_in_sync = installed_script.get("sha256") == desired_script.get("sha256")
|
||||
drift = []
|
||||
if not config_in_sync:
|
||||
drift.append("policy-config-drift")
|
||||
if not runner_in_sync:
|
||||
drift.append("policy-runner-drift")
|
||||
if installed_config_error is not None:
|
||||
drift.append("installed-config-unreadable")
|
||||
return {
|
||||
"inSync": config_in_sync and runner_in_sync and installed_config_error is None,
|
||||
"drift": drift,
|
||||
"desired": {
|
||||
"config": desired_config,
|
||||
"runner": desired_script,
|
||||
"retention": retention_policy_summary(rendered["policyConfig"]),
|
||||
},
|
||||
"installed": {
|
||||
"config": installed_config,
|
||||
"runner": installed_script,
|
||||
"retention": retention_policy_summary(installed_policy),
|
||||
"configError": installed_config_error,
|
||||
},
|
||||
}
|
||||
|
||||
def compact_policy_last_run(last):
|
||||
results = []
|
||||
for item in last.get("results") or []:
|
||||
|
||||
Reference in New Issue
Block a user