마지막 큰 단계는 정직함에 관한 것이었다. controller decision이 실제로 Kubernetes를 건드리고 있는가. 그리고 dashboard는 자신이 아는 것보다 더 많이 아는 척하지 않고 결과를 설명하고 있는가.
dual-cluster comparison은 탄탄한 로컬 framework를 제공했지만, 동시에 미묘한 gap도 드러냈다. Agent A/B/C decision은 orchestrator path를 통해 평가되고 있었고, dashboard는 실제 Kubernetes metric을 읽고 있었다. UI는 실제 cluster state를 보여줄 수 있었지만, recommendation과 controlled resource를 실제로 mutate한 action은 여전히 구분해야 했다.
controller는 말만 하고 있는데 dashboard만 살아 움직이는 것처럼 보이게 만들고 싶지는 않았다. 그래서 좁은 범위의 live Kubernetes action executor를 추가했다. 여기서 좁다는 말이 핵심이다. controlled namespace, label이 붙은 exercise deployment, bounded mutation, 그리고 모든 kubectl operation 기록이 필요했다.
Bounded action execution
executor는 모든 operation에 대해 command, return code, stdout, stderr를 기록한다. silent mutation은 위험하기 때문에 이렇게 하고 싶었다. controller가 무언가를 scale했거나 cap했다고 주장한다면, decision payload 안에 action trail이 있어야 한다.
def _record_operation(
kubeconfig: str | Path,
operations: list[dict[str, Any]],
description: str,
args: list[str],
) -> None:
completed = _run_kubectl(kubeconfig, args)
operations.append({
"description": description,
"command": "kubectl " + " ".join(args),
"returncode": completed.returncode,
"stdout": completed.stdout.strip(),
"stderr": completed.stderr.strip(),
})
Scaling과 resource changes도 마찬가지로 bounded되어 있다. executor는 orchestrator exerciser label로만 deployments를 발견한 다음, 허용된 작은 변경 집합만 적용한다. exercise deployments를 scale하거나, comparison load generator를 cap하거나, controlled work를 restart할 수 있다. 이건 general-purpose cluster automation tool이 아니고, 나는 그 편이 더 좋다.
def execute_live_kubernetes_action(
action: AgentAction,
kubeconfig: str | Path,
*,
namespace: str = DEFAULT_EXERCISE_NAMESPACE,
workload_namespace: str = DEFAULT_WORKLOAD_NAMESPACE,
) -> dict[str, Any]:
names, discovery_error = _deployment_names(kubeconfig, namespace)
operations: list[dict[str, Any]] = []
result: dict[str, Any] = {
"status": "observed",
"namespace": namespace,
"agent": action.agent_name,
"kind": action.kind.value,
"target": action.target,
"payload": dict(action.payload),
"matched_deployments": names,
"workload_namespace": workload_namespace,
"operations": operations,
}
if discovery_error:
result["status"] = "error"
result["error"] = discovery_error
return result
if not names or action.kind == ActionKind.NOOP:
result["status"] = "no_targets" if not names else "noop"
return result
dashboard state에 execution 연결하기
live loop는 execution result를 decision payload에 붙인다. 이 변화로 dashboard의 의미가 달라졌다. 이제 decision은 AgentA:replicate만 보여주는 것이 아니라, bounded Kubernetes action이 시도됐는지, kubectl이 무엇을 반환했는지도 보여줄 수 있었다.
if exercise_cluster:
decision_payload["kubernetes_execution"] = execute_live_kubernetes_action(
action,
kubeconfig_path,
namespace=exercise_namespace,
)
state.decision(decision_payload)
덕분에 dashboard는 덜 깔끔해졌지만, 더 정직해졌다. failed operations, no target matches, no-op states도 실험의 일부다. 무엇이 실제로 일어났는지를 숨기는 controller story를 내놓느니, 차라리 그 지저분함을 보여주는 편이 낫다.
Energy limits
Energy는 과장하기 가장 쉬운 metric이어서, 경계를 명시적으로 유지했다. 이 프로젝트는 physical wattmeter가 아니라 utilization-derived dynamic power estimate를 사용한다. 같은 local model 아래에서 controlled workload pressure를 비교하는 데는 유용하다. 하지만 정확한 machine power consumption에 대한 claim은 아니다.
@dataclass(frozen=True, slots=True)
class PowerCalibration:
idle_watts: float = 80.0
cpu_full_scale_watts: float = 120.0
mem_full_scale_watts: float = 60.0
source: str = "default_utilization_model"
def estimate_node_power_watts(
cpu_util: float,
mem_util: float,
calibration: PowerCalibration | None = None,
) -> float:
calibrated = calibration or DEFAULT_POWER_CALIBRATION
return (
calibrated.idle_watts
+ (calibrated.cpu_full_scale_watts * _bounded_ratio(cpu_util))
+ (calibrated.mem_full_scale_watts * _bounded_ratio(mem_util))
)
comparison dashboard는 controlled dynamic power와 whole-cluster background noise도 분리한다. 그렇지 않으면 unrelated observability나 control-plane activity 때문에 experimental 쪽이 엉뚱하게 더 나빠 보이거나 더 좋아 보일 수 있었기 때문에 중요했다.
최종적으로 남은 것
최종 로컬 시스템에는 Borg-derived features, XGBoost risk and demand models, six-layer orchestrator, Agent A/B/C proposals, deterministic referee, optional Optuna and Ray/RLlib adaptation, live Kubernetes exercise loops, dual-cluster comparison setup, 그리고 moving parts를 보여주는 dashboards가 들어 있다. production autoscaler는 아니다. HPA events만 바라보는 것보다 더 정밀한 질문을 던지게 해주는 개인용 cloud-systems research rig에 가깝다.
이 프로젝트에서 얻은 가장 유용한 것은 단일 metric이 아니었다. workflow였다. pressure를 만들고, live state를 수집하고, controller가 propose하게 하고, conflict를 보여주고, baseline과 비교하고, interpretation boundary를 계속 보이게 두는 workflow. 그래서 dashboards가 프로젝트의 중심이 되었다.
나중에 이 작업을 이어간다면 다음 버전은 더 오래 반복 실험을 돌리고, run마다 더 많은 dashboard snapshots를 보존하고, 결국 Kind에서 EKS와 실제 Karpenter로 comparison을 옮겨야 한다. 지금은 local version만으로도 Kubernetes frustration에서 live orchestration experiment까지의 전체 경로를 설명하기에 충분히 완성됐다.
이 마지막 단계는 프로젝트를 과장 없이 설명할 수 있을 만큼 완성된 형태로 만들었다. prediction이 어디서 끝나고, decision이 어디서 시작되며, Kubernetes가 실제로 어디에서 건드려지고, measurement가 어디에서 겸손해야 하는지 보여줬다. 그 boundary가 polished demo와 내가 책임질 수 있는 engineering artifact를 가른다.