대규모 클러스터(128대 이상) 환경에서 kubectl describe를 쓰면 속도가 매우 느리므로, kubectl의 JSON 출력을 파싱하거나 Kubernetes API를 직접 호출해 노드 Allocatable 대비 Pod CPU Requests 합산치, 할당률(%), 잔여 Core를 산출하는 Python 스크립트와 경량 Bash 스크립트입니다.
Python 기반 노드별 잔여 CPU Core 산출 스크립트 (calc_node_cpu.py)
별도의 무거운 패키지 없이 로컬 kubectl 권한(~/.kube/config)을 활용하여 실행 가능하며, 단위(m, 정수) 변환 및 종료된 Pod(Succeeded/Failed) 자동 제외 로직이 포함되어 있습니다.
#!/usr/bin/env python3
import json
import subprocess
import sys
def parse_cpu_to_millicores(val: str) -> int:
"""CPU 문자열('64', '500m' 등)을 millicores 정수로 변환"""
if not val:
return 0
val = str(val).strip()
if val.endswith('m'):
return int(val[:-1])
return int(float(val) * 1000)
def main():
# 1. Node 정보 수집 (Allocatable CPU)
print("[*] Fetching node specifications...", file=sys.stderr)
try:
nodes_raw = subprocess.check_output(
["kubectl", "get", "nodes", "-o", "json"], stderr=subprocess.PIPE
)
nodes_data = json.loads(nodes_raw)
except subprocess.CalledProcessError as e:
print(f"Error fetching nodes: {e.stderr.decode()}", file=sys.stderr)
sys.exit(1)
node_stats = {}
for item in nodes_data.get("items", []):
node_name = item["metadata"]["name"]
allocatable_cpu = item["status"]["allocatable"].get("cpu", "0")
alloc_m = parse_cpu_to_millicores(allocatable_cpu)
node_stats[node_name] = {
"allocatable_m": alloc_m,
"requested_m": 0,
}
# 2. 모든 Namespace의 Pod 정보 수집
print("[*] Fetching active pods and CPU requests...", file=sys.stderr)
try:
pods_raw = subprocess.check_output(
["kubectl", "get", "pods", "-A", "-o", "json"], stderr=subprocess.PIPE
)
pods_data = json.loads(pods_raw)
except subprocess.CalledProcessError as e:
print(f"Error fetching pods: {e.stderr.decode()}", file=sys.stderr)
sys.exit(1)
for item in pods_data.get("items", []):
status = item.get("status", {})
phase = status.get("phase", "")
# 종료되었거나 실패한 Pod 제외
if phase in ["Succeeded", "Failed"]:
continue
spec = item.get("spec", {})
node_name = spec.get("nodeName")
if not node_name or node_name not in node_stats:
continue
# App 컨테이너 requests 합산
pod_req_m = 0
for container in spec.get("containers", []):
resources = container.get("resources", {})
requests = resources.get("requests", {})
cpu_req = requests.get("cpu", "0")
pod_req_m += parse_cpu_to_millicores(cpu_req)
# Init 컨테이너 고려 (Kubernetes 표준: max(initContainers, sum(appContainers)))
init_req_m = 0
for init_c in spec.get("initContainers", []):
req = init_c.get("resources", {}).get("requests", {}).get("cpu", "0")
init_req_m = max(init_req_m, parse_cpu_to_millicores(req))
effective_req_m = max(pod_req_m, init_req_m)
node_stats[node_name]["requested_m"] += effective_req_m
# 3. 산출 결과 계산 및 출력 (남은 Core 적은 순 정렬)
results = []
for node, data in node_stats.items():
alloc_m = data["allocatable_m"]
req_m = data["requested_m"]
remain_m = alloc_m - req_m
usage_ratio = (req_m / alloc_m * 100.0) if alloc_m > 0 else 0.0
results.append({
"node": node,
"alloc_cores": alloc_m / 1000.0,
"req_cores": req_m / 1000.0,
"ratio_pct": usage_ratio,
"remain_cores": remain_m / 1000.0
})
# 잔여 CPU가 가장 부족한 노드부터 오름차순 정렬
results.sort(key=lambda x: x["remain_cores"])
print(f"\n{'NODE NAME':<40} {'ALLOC(Core)':>12} {'REQUEST(Core)':>14} {'USAGE(%)':>10} {'REMAIN(Core)':>14}")
print("-" * 94)
for r in results:
print(f"{r['node']:<40} {r['alloc_cores']:>12.2f} {r['req_cores']:>14.2f} {r['ratio_pct']:>9.1f}% {r['remain_cores']:>14.2f}")
if __name__ == "__main__":
main()
Bash + jq 경량 1-Liner 스크립트
파이썬 없이 터미널에서 즉시 노드별 남은 Core와 할당률을 확인해야 할 때 유용합니다.
kubectl get nodes -o json | jq -r '
.items[] |
.metadata.name as $node |
(.status.allocatable.cpu | if endswith("m") then (rtrimstr("m") | tonumber) else (tonumber * 1000) end) as $alloc |
"\($node) \($alloc)"
' | while read node alloc_m; do
req_m=$(kubectl get pods -A --field-selector spec.nodeName=$node -o json | jq '
[ .items[] | select(.status.phase != "Succeeded" and .status.phase != "Failed") |
.spec.containers[].resources.requests.cpu // "0" |
if endswith("m") then (rtrimstr("m") | tonumber) else (tonumber * 1000) end
] | add // 0
')
remain_cores=$(awk "BEGIN {printf \"%.2f\", ($alloc_m - $req_m) / 1000}")
ratio=$(awk "BEGIN {printf \"%.1f\", ($req_m / $alloc_m) * 100}")
alloc_cores=$(awk "BEGIN {printf \"%.2f\", $alloc_m / 1000}")
req_cores=$(awk "BEGIN {printf \"%.2f\", $req_m / 1000}")
printf "%-40s | Alloc: %6s Core | Req: %6s Core (%5s%%) | Remain: %6s Core\n" "$node" "$alloc_cores" "$req_cores" "$ratio" "$remain_cores"
done
운영 팁
Allocatable 기준 적용: capacity가 아닌 allocatable을 기준으로 삼아 kubelet, OS 예약 영역(kube-reserved, system-reserved)을 제외한 실제 워크로드 스케줄링 가능 여유 Core만 계산합니다.max(sum(appContainers), max(initContainers)) 규칙을 반영하여 오차를 없앴습니다.