2026. 8. 16. 21:01ㆍPROJECT/Toy Project
[Cloud Ops Lab #4] 학습 목표, 요구사항, Harness 회고
1. 시작하며
앞선 세 글에서는 Cloud Ops Lab 프로젝트를 시작하기 전에 무엇을 배우고, 무엇을 만들고, 어떤 방식으로 개발할지를 먼저 정리했다.
프로젝트를 시작할 때 가장 중요하게 생각했던 원칙은 이전 프로젝트들과 크게 다르지 않았다.
Baseline
↓
문제 재현
↓
Metric 측정
↓
원인 분석
↓
후보 비교
↓
Human Gate
↓
구현
↓
동일 Scenario 재측정
그리고 이번에도:
측정하지 않은 개선을 개선이라고 쓰지 않는다.
사용하지 않은 기술을 효과가 없었다고 쓰지 않는다.
를 기준으로 프로젝트를 진행했다.
Cloud Ops Lab에서는 여기에 하나가 더 있었다.
Kubernetes를 사용했다
≠
운영을 이해했다
따라서:
kind Cluster가 있다.
HPA YAML이 있다.
Grafana Dashboard가 있다.
AWS에 배포했다.
같은 결과를 프로젝트 성공 기준으로 두지 않았다.
Phase 9까지 진행한 지금은 #1에서 정한 학습 목표와 #2의 요구사항, #3의 Harness 전략이 실제 Repository에서 어느 정도 유지됐는지 돌아볼 수 있게 됐다.
결론부터 말하면 이번 프로젝트도 처음 계획한 기술을 모두 사용한 프로젝트는 아니었다.
HPA
→ 미도입
AWS
→ 미진행
KEDA
→ 미사용
Argo CD
→ 미사용
Chaos Mesh
→ 미사용
대신 실제로 확인한 것은 조금 달랐다.
고정 Replica의 부하 특성
Resource Constraint
Readiness / Liveness
Pod Failure
Recovery
Rolling Update
Client 경로와 Kubernetes Ready의 차이
Application Metric과 Kubernetes 상태의 연결
이번 글에서는 이 차이를 숨기지 않고 그대로 정리해보려고 한다.
2. 최종 구조부터 보면
최종 구조는 처음 생각했던 것보다 단순하게 남았다.
k6 / curl
↓
kubectl port-forward
↓
Kubernetes Service
↓
Deployment
↓
Pod
↓
Spring Backend
Application과 Kubernetes 상태는:
Application / Kubernetes
↓
Metric
↓
Prometheus
↓
Grafana
구조로 확인했다.
Application은 Container Image로 만들고 kind Cluster의 Deployment에서 실행했다.
Spring Application
↓
cloud-ops-lab:v1
↓
Pod
↓
Deployment
최종적으로 Repository에 남은 핵심 구성은 대략 다음과 같다.
Backend Application
Docker Image
kind Kubernetes
Deployment
Service
Liveness / Readiness
Resource Request / Limit
Prometheus
Grafana
k6
Failure / Load Script
Experiment Scenario
ADR
반대로 최종 구조에 들어가지 않은 것도 분명하다.
HPA
metrics-server
AWS
KEDA
Service Mesh
Argo CD
Chaos Mesh
이 구조만 보면:
Cloud Ops Lab인데
HPA도 없고 AWS도 없다?
라고 볼 수 있다.
하지만 실제 Experiment 결과까지 같이 보면 왜 이렇게 남았는지는 조금 다르게 보인다.
3. 실제로 무엇을 실험했나
이번 프로젝트에서는 정상 상태의 Baseline만 만들어놓고 끝내지 않았다.
실제로 다음 Scenario를 반복했다.
001 Baseline Load
002 Increasing Load
003 Replica Comparison
004 Resource Constraint
005 Readiness Failure
006 Liveness Failure
007 Pod Failure
008 Increasing Cost
011 Rolling Update
각 Experiment에서는 가능한 한 다음 흐름을 유지했다.
Scenario 조건 고정
↓
Load / Failure 실행
↓
Metric 확인
↓
Kubernetes 상태 확인
↓
Application 영향 확인
↓
결과 기록
결과를 아주 짧게 줄이면 다음과 같다.
cost=1, 10~800 RPS
→ HTTP Error 0
→ CPU는 Limit 500m에 닿지 않음
Replica 1 / 2 / 3 비교
→ Replica 2·3에서도 Latency 이득 없음
→ Memory 사용만 증가
CPU 50m / Memory 128Mi
→ 새 Pod Ready 실패
→ 128Mi에서는 OOMKilled
Readiness Failure
→ Process 유지
→ Restart 0
Liveness Failure
→ Restart 1
Pod Failure
→ 새 Pod Ready 약 13초
→ 그 시각 Pod 직접 호출은 200
→ 기존 port-forward / k6 경로는 계속 실패
Rolling Update
→ 존재하지 않는 Image로 변경
→ 신규 Pod는 정상화되지 않음
→ 기존 Pod는 유지
처음 예상했던 흐름과 실제 결과가 꽤 달랐다.
4. 처음 예상했던 Cloud Ops와 실제 프로젝트는 같았나?
#1을 작성할 때는 꽤 자연스러운 운영 흐름을 생각했다.
Traffic 증가
↓
CPU 증가
↓
Latency 증가
↓
고정 Replica 한계
↓
HPA
↓
Scale Out
↓
Latency / Error 개선
그리고 최종적으로:
Local Kubernetes
↓
AWS
까지 가져가려고 했다.
하지만 실제 Repository에서는 이 흐름이 만들어지지 않았다.
가장 큰 이유는 첫 번째 조건이었다.
고정 Replica Capacity 문제
가 실제로 나타나지 않았다.
실제로:
cost=1
10 RPS
↓
...
↓
800 RPS
까지 올렸지만 HTTP Error는 없었고 CPU도 Limit 500m에 도달하지 않았다.
Work Endpoint의 cost도 1에서 16까지 올려봤지만 CPU는 예상처럼 선형적으로 증가하지 않았다.
따라서:
현재 Replica가 부족하다.
라고 말할 근거가 나오지 않았다.
이 상태에서 HPA Threshold를 임의로 정하면:
HPA를 학습하고 싶어서
Scaling 문제를 만들어낸 것
에 가까워질 수 있었다.
그래서 실제 Repository에서는 HPA를 적용하지 않았다.
5. Container와 Kubernetes의 관계는 실제로 이해할 수 있었나
이 부분은 비교적 처음 목표와 비슷하게 진행됐다.
Spring Application을 Container Image로 만들고 kind Deployment에서 실행했다.
Application
↓
Image
↓
Container
↓
Pod
↓
Deployment
여기서 중요한 것은 단순히:
Container가 실행됐다.
가 아니었다.
실험을 진행하면서:
Image가 존재한다
≠
Pod가 Ready다
라는 차이가 자연스럽게 나타났다.
Resource Constraint에서는 Image 자체는 정상이어도 Pod가 Ready가 되지 않았다.
Rolling Update에서는 존재하지 않는 Image를 지정했을 때 신규 Pod가 정상화되지 않았고 기존 Pod는 유지됐다.
즉:
Application 실행 가능
Container 실행 가능
Pod Ready
Deployment 정상
은 같은 상태가 아니라는 것을 실제 Failure에서 확인할 수 있었다.
6. Resource Request / Limit은 예상과 다른 방식으로 영향을 줬다
#1에서는 Resource가 부족하면 다음과 같은 흐름을 예상했다.
Traffic 증가
↓
CPU 부족
↓
처리 지연
↓
Latency 증가
실제 결과는 달랐다.
Baseline Resource는:
CPU Request
250m
CPU Limit
500m
Memory
512Mi
였다.
여기서 Resource를 크게 줄인 실험에서는:
CPU 50m
Memory 128Mi
조건을 사용했다.
결과는:
Latency가 조금 느려짐
정도가 아니었다.
새 Pod Ready 실패
Memory 128Mi
↓
OOMKilled
가 나타났다.
즉 이번 Experiment에서 Resource Limit은:
성능을 조금씩 조절하는 값
이라기보다:
Application이 정상적으로 실행 가능한가?
를 가르는 조건에 더 가까웠다.
이 결과 때문에 기본 Resource YAML을 바로 수정하지 않았다.
실험용 제약 값
≠
운영 기본값을 바꿔야 한다는 근거
로 구분했다.
7. Liveness와 Readiness는 실제로 다른 문제였다
이번 프로젝트에서 가장 깔끔하게 확인된 부분 중 하나다.
Failure Endpoint를 이용해 두 상태를 직접 만들었다.
Readiness Failure
Readiness 거절
↓
Process 유지
↓
Restart 0
Liveness Failure
Liveness 거절
↓
Container Restart
↓
Restart 1
즉:
Process가 살아 있는가?
와:
Traffic을 받을 준비가 되어 있는가?
는 실제로 다른 상태였다.
문서에서 읽으면 당연해 보이는 내용이지만 직접 Failure를 만들어보니 Kubernetes가 두 상태를 다루는 방식도 분명히 달랐다.
다만 이 실험에서도 한계가 하나 있었다.
Readiness Failure 시 실제 Client Traffic이 Service Endpoint에서 빠지는지를 완전히 확인했다고 말하기 어렵다.
이유는 실제 Load Client가 대부분:
kubectl port-forward
를 사용했기 때문이다.
이 부분은 이후 Experiment에서 계속 다시 나타났다.
8. Pod가 다시 생긴 것과 서비스가 복구된 것은 같지 않았다
이번 프로젝트에서 가장 의미 있었던 Experiment 중 하나는 Pod Failure였다.
kubectl delete pod를 이용해 직접 재현했다.
Replica 1
↓
Pod 삭제
↓
Ready Pod = 0
↓
새 Pod 생성
↓
새 Pod Ready ≈ 13s
여기까지만 보면:
Kubernetes가 약 13초 만에 복구했다.
라고 쓸 수 있을 것 같다.
하지만 실제 Client 결과는 조금 달랐다.
새 Pod Ready
↓
Pod 직접 호출 = 200
하지만
기존 port-forward / k6
→ 계속 실패
즉:
Kubernetes Ready
≠
기존 Client 경로 정상화
였다.
처음 #1에서 적었던 문장:
Infrastructure가 복구된 것
≠
서비스가 정상 상태를 유지한 것
을 실제로 확인한 셈이다.
다만 이 결과를 그대로:
Service Routing이 복구되지 않았다.
라고 말할 수도 없다.
실제 Client 경로가:
k6
↓
port-forward
↓
Service
였기 때문이다.
Pod가 바뀌면서 port-forward 자체가 끊긴 영향이 섞여 있다.
이 부분은 이번 프로젝트에서 가장 반복적으로 남은 한계다.
9. Replica를 늘리면 실제로 빨라졌나?
초기에는 자연스럽게 이런 가설을 가질 수 있다.
Replica 1
<
Replica 2
<
Replica 3
Replica가 많을수록 처리 Capacity가 높아지고 Latency도 낮아질 것이라는 가설이다.
그래서 같은 800 RPS 조건에서 Replica를 비교했다.
결과는 예상과 달랐다.
Replica 1
vs
Replica 2
vs
Replica 3
에서 Replica 2와 3이 명확한 Latency 이득을 만들지 못했다.
대신:
Replica 증가
↓
Memory 증가
가 더 명확했다.
즉 현재 Scenario에서는:
Pod 수 증가
=
성능 개선
이라고 말할 근거가 없었다.
이 결과도 그대로 남겼다.
10. Capacity 한계를 찾으려고 했지만 찾지 못했다
#1에서 중요하게 생각했던 질문 중 하나는 이것이었다.
어느 수준의 Traffic부터 Latency가 증가하는가?
이 질문에는 이번 프로젝트에서 명확한 숫자로 답하지 못했다.
10 RPS
...
800 RPS
까지 Increasing Load를 실행했지만 현재 Application과 Resource 조건에서는 명확한 Saturation이 나타나지 않았다.
Work Endpoint의 cost도 높였지만 CPU는 Limit에 닿지 않았다.
따라서 현재 결과는:
현재 테스트 범위에서
Capacity 한계를 확인하지 못했다.
가 정확하다.
여기서 Scenario를 낮춰서:
더 작은 Limit을 주고
억지로 HPA가 동작하게 만든다.
같은 방식으로 가지 않았다.
다만 회고를 하면서 하나는 남는다.
왜 cost를 올렸는데 CPU가 예상만큼 증가하지 않았는가?
이 부분을 Application 계층에서 더 깊게 분석하지 않았다.
Profiler나 더 세밀한 CPU Metric을 먼저 확인했다면 Capacity Experiment의 설계 자체를 더 검증할 수 있었을 것 같다.
11. 그래서 HPA는 사용하지 않았다
이번 프로젝트에서 가장 설명이 필요한 부분이다.
#1에서는 HPA를 중요한 학습 대상으로 적었다.
HPA는 언제 Pod를 늘리고 언제 줄일까?
HPA가 실제 사용자 관점의
Latency와 Error를 줄였는가?
ROADMAP에도 HPA Phase가 있었다.
그런데 실제 Repository에는:
HPA Manifest 없음
metrics-server 없음
이다.
이유는 단순하다.
고정 Replica 문제 재현
↓
HPA 필요성 판단
순서를 지키려고 했는데 첫 번째 조건이 충족되지 않았다.
따라서 Human Gate에서 HPA를 적용하지 않는 쪽을 선택했고:
docs/adr/001-fixed-replica-without-autoscaling.md
에 남겼다.
여기서 표현은 조심해야 한다.
HPA를 테스트했지만 효과가 없었다.
가 아니다.
현재 Load와 Failure Case에서는 HPA 도입을 정당화할 Capacity 문제를 재현하지 못했기 때문에 실험하지 않았다.
가 정확하다.
즉 #3의 Harness 원칙은 지켰지만,
#1에서 약속했던 HPA 학습 범위는 완료하지 못했다.
12. AWS도 같은 이유로 내려놓았다
AWS도 비슷하다.
처음 계획은:
Local Kubernetes
↓
반복 가능한 Experiment
↓
AWS Validation
이었다.
로컬 Experiment 구조는 만들었다.
하지만 AWS 단계는 진행하지 않았다.
결과적으로:
FR-27
AWS 환경 실행
FR-28
AWS 환경 관측
은 미충족 상태다.
이 역시:
AWS가 필요 없었다.
라고 말할 수는 없다.
비교하지 않았기 때문이다.
실제로 남은 중요한 질문 중 하나는:
port-forward가 아닌
실제 Load Balancer 경로에서는
Kubernetes Ready
=
Client Recovery
인지 여부다.
AWS를 사용하지 않으면서 이 질문도 끝까지 닫지 못했다.
그래서:
docs/adr/002-skip-aws-validation.md
에 Trade-off를 남겼다.
13. Observability는 Grafana보다 문제를 설명하는 과정에 가까웠다
Prometheus와 Grafana는 Baseline부터 있었다.
하지만 실제 Experiment 문서에서 가장 자주 사용한 것은:
k6 Summary / Timeline
kubectl get
kubectl describe
Endpoints
lastTerminated
Prometheus Histogram
CPU Metric
Throttle Metric
등이었다.
즉:
Grafana Dashboard를 만들었다.
가 이번 프로젝트의 Observability 결과는 아니다.
실제로 중요했던 것은:
왜 Ready가 늦었지?
왜 Client는 계속 실패하지?
왜 Restart가 발생했지?
왜 Replica를 늘렸는데 Latency가 안 줄지?
같은 질문을 여러 신호로 좁혀간 과정이었다.
이 관점은 #1과 #3에서 생각했던 방향과 잘 맞았다.
다만 부족한 부분도 있다.
처음에는 다음 신호를 같은 시간축에 붙이는 것을 생각했다.
Load Event
Kubernetes Event
Application Metric
Resource Metric
Log
실제로 Pod Failure와 Rolling Update에서는 Ready 시각과 Client 실패 시각을 어느 정도 연결했다.
하지만 모든 Experiment에서 Grafana / Prometheus의 같은 시간창을 증거로 남기지는 않았다.
특히 Capacity Experiment는 결과가 평평했기 때문에:
CPU가 별로 안 올라갔다.
라는 결과를 그래프로 남겼다면 회고 시점에 더 명확한 증거가 됐을 것 같다.
14. Rolling Update도 set image 성공과 무중단 배포를 구분했다
Rolling Update에서는 존재하지 않는 Image를 지정하는 Failure를 만들었다.
기존 Version
↓
존재하지 않는 Image로 변경
↓
신규 Pod 정상화 실패
↓
기존 Pod 유지
여기서:
kubectl set image 성공
=
배포 성공
이라고 보지 않았다.
Deployment Controller가 신규 Pod와 기존 Pod를 어떻게 관리하는지,
Ready 상태가 어떻게 유지되는지를 봤다.
다만 이 실험 역시 Client가 port-forward를 사용했기 때문에:
배포 중 사용자 Traffic이 실제 Service 경로에서
완전히 무중단이었는가?
까지는 설명하지 못한다.
15. 요구사항 #2는 얼마나 지켰나
#2에서는 기능 요구사항과 비기능 요구사항을 꽤 구체적으로 작성했다.
전체적으로 보면 대부분의 Kubernetes 기본 요구사항과 Failure Experiment는 실제로 구현했다.
충족에 가까웠던 기능은 다음과 같다.
Backend 실행
Container Image
Deployment
Replica 변경
Desired State 확인
Liveness / Readiness
Resource Constraint
Application Metric
Kubernetes Metric
Prometheus
반복 Load
Replica 비교
Pod Failure
Recovery Time
Rolling Update
배포 실패
Experiment 기록
반대로 부분적으로만 확인된 것도 있다.
Service 접근
→ Service는 존재
→ Client는 port-forward 사용
Grafana 시각화
→ Dashboard 존재
→ 실험 분석의 주증거는 아님
고정 Replica Capacity
→ 비교는 했지만 한계 미재현
그리고 명확하게 완료하지 않은 것도 있다.
HPA
Scale Out
Scale In
Spike
AWS 실행
AWS 관측
중요한 점은 이 항목들을 REQUIREMENTS에서 지우지 않았다는 것이다.
프로젝트 결과에 맞춰:
FR 삭제
성공 기준 완화
를 하지 않았다.
현재 Repository만 봐도 무엇이 미충족인지 확인할 수 있다.
16. Experiment Scenario를 Dataset처럼 보호한 것은 잘 됐다
Cloud Ops Lab에서는 Evaluation Dataset 대신 Experiment Scenario를 Harness의 핵심으로 봤다.
실제로 다음 Scenario들이 남았다.
Baseline Load
Increasing Load
Replica Comparison
Resource Constraint
Readiness Failure
Liveness Failure
Pod Failure
Increasing Cost
Rolling Update
그리고 결과가 기대와 다르더라도 Scenario를 낮추지 않았다.
예를 들어:
800 RPS에서도
Capacity 한계가 안 나옴
이라는 이유로:
부하를 줄이고
Result 기준을 바꾸거나
Metric을 다른 값으로 바꿔
문제가 있었던 것처럼 만들지 않았다.
Replica Comparison에서도 Latency가 좋아지지 않은 결과를 그대로 남겼다.
Resource Constraint에서도 예상했던:
Latency 증가
가 아니라:
Ready 실패 / OOMKilled
가 나왔지만 그대로 기록했다.
이 부분은 이전 AI 프로젝트에서 Dataset을 보호했던 방식과 꽤 비슷했다.
17. Baseline과 README는 이전 프로젝트보다 나아졌다
LLM Router 회고에서 반복해서 남았던 문제 중 하나는 README 작성 시점이었다.
이번 #3에서는:
Baseline 완료
=
README 실행 가능
상태를 만들려고 했다.
실제로 README에는 초반부터 다음 내용이 들어갔다.
프로젝트 목적
로컬 실행 방법
Image Build 방법
kind 배포 방법
Request 방법
Load Test 방법
Prometheus / Grafana 확인 방법
Experiment 위치
그리고 현재 한계도 남겼다.
Replica 1
HPA 없음
AWS 없음
Capacity 한계 미재현
Ready와 Client Recovery가 다름
README를:
마지막에 작성하는 홍보 문서
보다:
Repository 진입점
으로 보겠다는 방향은 이전보다 잘 적용됐다.
18. Harness #3는 실제로 얼마나 작동했나
Cloud Ops Lab #3에서 가장 먼저 반영하려고 했던 것은 LLM Router 회고의 문제였다.
Blog Goal
=
REQUIREMENTS
=
ROADMAP
시작 시점에는 이 정합성을 맞췄다.
그리고 개발 과정에서도 대부분의 원칙은 실제 Repository에 남았다.
Repository를 Source of Truth로 사용
DESIGN에는 현재 구조만 기록
ROADMAP은 문제 확인 순서
Baseline First
Test ≠ Experiment
Scenario 보호
Failure 직접 재현
Recovery 분리
측정 없는 개선 주장 금지
Human Gate + ADR
README를 Baseline부터 유지
Agent / Multi-Agent를 기본값으로 두지 않음
특히 HPA와 AWS에서 이 원칙이 강하게 작동했다.
AI가:
HPA Threshold
Min Replica
Max Replica
AWS Architecture
를 임의로 정해서 먼저 구현하지 않았다.
실제 Metric과 학습 필요성을 본 뒤 Human Gate에서 적용하지 않는 쪽을 선택했다.
19. Human Gate 뒤 ADR을 바로 남긴 것은 이전보다 나아졌다
LLM Router에서는 일부 ADR을 프로젝트 후반에 정리했다.
이번에는 운영 정책을 바꿀 수 있는 지점에서:
현재 문제
↓
Metric
↓
후보
↓
Human Gate
↓
Decision
↓
ADR
순서를 가져갔다.
대표적인 것이:
ADR 001
fixed replica without autoscaling
ADR 002
skip AWS validation
이다.
결정이:
아무것도 안 함
이더라도 ADR로 남겼다는 점이 중요했다.
기술을 도입한 이유뿐 아니라:
왜 도입하지 않았는가?
도 Repository에서 확인할 수 있게 됐다.
20. 대신 Harness Loop의 후반은 약했다
#3에서는 전체 Loop를 이렇게 생각했다.
LOAD / FAILURE
↓
OBSERVE
↓
ANALYZE
↓
후보 비교
↓
HUMAN GATE
↓
ADR
↓
IMPLEMENT
↓
RE-EXPERIMENT
앞부분은 많이 반복했다.
하지만:
운영 정책 변경
↓
동일 Scenario 재실행
은 거의 없었다.
이유는 실제 운영 기본값을 많이 바꾸지 않았기 때문이다.
기본 Replica 유지
Resource 기본값 유지
Probe 정책 튜닝 안 함
HPA 미도입
따라서 Before / After는 주로:
Replica 1 vs 2 vs 3
Resource 기본 vs Constraint
Readiness vs Liveness
정상 Image vs 없는 Image
같은 실험 조건 비교에 가까웠다.
즉:
관찰과 Failure 분석은 충분했지만, 실제 운영 정책을 바꾼 뒤 같은 Failure를 다시 돌리는 개선 Loop는 얇았다.
이 부분은 다음 프로젝트에서 보완할 수 있을 것 같다.
21. Client 경로는 이번 프로젝트에서 가장 큰 Harness 구멍이었다
회고하면서 가장 반복적으로 보이는 문제는 이것이다.
실제 사용자가 타야 할 경로
Client
↓
Service
↓
Pod
와 실제 Experiment 경로가 달랐다.
k6 / curl
↓
kubectl port-forward
↓
Service
↓
Pod
port-forward는 Pod 생명주기와 완전히 독립된 실제 Load Balancer 경로가 아니다.
그래서 Pod Failure에서:
새 Pod Ready
↓
Pod 직접 호출 200
하지만
기존 port-forward 실패
가 나타났다.
이 결과 자체는 의미 있다.
Kubernetes Ready와 Client Recovery가 같은 값이 아닐 수 있다.
하지만:
Service Routing이 복구되지 않았다.
라고 결론 내릴 수는 없다.
Client 관측 경로 자체가 실험 변수였기 때문이다.
다음에 같은 Experiment를 한다면:
kind 내부 k6 Job
또는
Ingress
처럼 Service를 실제 Client 경로로 사용하는 구조가 더 적절할 것 같다.
22. Replica 1은 Capacity에는 좋았지만 HA 질문에는 부족했다
Baseline을 단순하게 유지하기 위해 Replica 1로 시작했다.
이 선택은 Capacity Experiment에는 자연스러웠다.
Replica 1
↓
현재 Pod 하나의 Capacity 확인
하지만 Pod Failure에서는 문제가 생겼다.
Replica 1
↓
Pod 삭제
↓
남은 Pod = 0
따라서 #1에서 궁금했던:
Pod 하나가 죽으면
남은 Pod의 CPU는 어떻게 변하는가?
는 볼 수 없었다.
이 질문에는 Replica 2 이상이 필요했다.
다음에는 Scenario 목적에 따라 Replica를 나누는 편이 더 좋을 것 같다.
Capacity Scenario
→ Replica 1
Failure / HA Scenario
→ Replica 2 또는 3
기본 운영 Replica를 변경하는 것과,
실험에서만 Replica를 바꾸는 것은 다르다.
이번에는 두 개를 충분히 구분하지 못했다.
23. Spike가 HPA와 같이 사라진 것도 아쉽다
#2에서는 Spike Scenario를 HPA와 연결해서 생각했다.
HPA가 존재한다
≠
순간 Traffic을 막을 수 있다
라는 질문이었다.
그런데 HPA를 내려놓으면서 Spike도 같이 진행하지 않았다.
하지만 지금 보면:
고정 Replica
+
Spike
만으로도 의미 있는 Experiment가 될 수 있었다.
짧은 고RPS Traffic
↓
Replica 1
↓
Latency / Error / CPU
Increasing Load에서는 800 RPS까지 Capacity 문제가 없었다.
짧은 순간에 더 큰 Traffic을 넣었을 때도 같은 결과가 나왔을지는 이번 프로젝트로는 알 수 없다.
즉:
HPA 미도입
과:
Spike 미실시
는 같은 결정으로 묶을 필요가 없었다.
24. Skill은 또 만들지 않았다
#3에서는 반복 Experiment가 세 번째 정도가 되면:
이 Workflow를 Skill로 빼면 이득인가?
를 확인해보려고 했다.
실제로 Script는 많이 생겼다.
run-baseline.sh
run-increasing-load.sh
run-replica-comparison.sh
run-resource-constraint.sh
run-readiness-failure.sh
run-liveness-failure.sh
run-pod-failure.sh
run-increasing-cost.sh
run-rolling-update.sh
collect-metrics.sh
dump-health-state.sh
즉 반복 자동화 자체는 잘 됐다.
하지만:
Skill로 추출할지 평가
라는 명시적인 Task는 없었다.
결과적으로 Skill은 만들지 않았다.
이번에는 Script로 충분했을 가능성이 높다.
하지만:
검토하고 안 만들었다.
와:
검토 자체를 잊었다.
는 다르다.
다음 프로젝트에서는 반복 Workflow가 일정 횟수를 넘으면
한 줄이라도 판단을 남기는 편이 좋을 것 같다.
25. 학습 대상과 도입 조건을 더 명확히 나눴어야 했다
이번 프로젝트에서 문서적으로 가장 아쉬운 부분이다.
#1에서는:
HPA를 직접 학습한다.
AWS로 확장한다.
라고 꽤 강하게 적었다.
반면 #3에서는:
Phase 도달
≠
기술 반드시 도입
이라고 적었다.
둘 다 내가 쓴 원칙이다.
실제 Repository에서는 후자를 선택했다.
Capacity 문제 없음
↓
HPA 미도입
Cloud 검증 필요성보다
현재 실험 질문 우선
↓
AWS 미진행
이 선택 자체는 현재 Harness와 일관된다.
다만 처음부터 이렇게 분리했으면 더 명확했을 것 같다.
학습 대상
HPA Scale Out / Scale In
도입 조건
고정 Replica Capacity 한계가
Metric으로 재현된 뒤 적용
조건이 만들어지지 않으면
HPA를 사용하지 않은 채
그 결과를 기록하고 Phase 종료 가능
AWS도 같은 방식이다.
즉:
학습하고 싶은 기술
과:
현재 프로젝트에 실제로 도입할 조건
을 같은 문장에 넣지 않는 편이 좋다.
26. 요구사항을 조용히 고치지 않은 것은 잘했다
HPA와 AWS가 빠진 상태에서 가장 쉬운 방법은:
REQUIREMENTS에서 관련 FR 삭제
였을 수 있다.
하지만 그렇게 하지 않았다.
FR-19 HPA
FR-20 Scale Out
FR-21 Scale In
FR-27 AWS 실행
FR-28 AWS 관측
은 그대로 남았다.
따라서 프로젝트 결과는:
요구사항 100% 달성
이 아니다.
일부는:
미충족
이고 일부는:
Human Gate 이후 내려놓음
이다.
이 표현이 더 정확하다.
다만 문서 사용성 측면에서는 하나 개선할 수 있다.
REQUIREMENTS 상단에:
Deferred
HPA
AWS
처럼 Phase 9의 최종 상태를 한눈에 볼 수 있는 섹션이 있었다면
FR 목록만 읽는 사람이 덜 헷갈렸을 것 같다.
27. 그래서 이번 프로젝트는 실패였나?
그렇게 보지는 않는다.
다만 무엇을 성공으로 보는지 범위를 나눠야 한다.
Kubernetes 기본 운영 관점
의미 있는 결과가 있었다.
Container / Pod / Deployment 관계
Resource Constraint
Liveness / Readiness
Desired State
Pod Failure
Recovery
Rolling Update
Metric 관찰
을 실제 Failure와 Load로 확인했다.
Scaling 관점
부족했다.
Capacity 한계 미재현
HPA 미실시
Scale Out 미실시
Scale In 미실시
Spike 미실시
이기 때문이다.
따라서:
Kubernetes Auto Scaling을 검증했다.
라고는 말할 수 없다.
Cloud 관점
부족했다.
AWS 미진행
이기 때문이다.
따라서:
AWS 환경에서도 같은 운영 특성을 검증했다.
라고 말할 수 없다.
Observability 관점
부분적으로 의미가 있었다.
Grafana가 있다는 사실보다는:
k6
kubectl
Endpoints
Prometheus
Ready 상태
Restart / OOM
를 연결해 Failure를 분석한 과정이 더 의미 있었다.
하지만 Log / Event / Grafana를 모든 Experiment에서 같은 시간축으로 연결하는 수준까지는 가지 않았다.
Harness 관점
이번 프로젝트에서 가장 잘 작동한 축이다.
Blog Goal / REQUIREMENTS / ROADMAP 시작 정합성
Baseline First
문제 재현 전 해결책 금지
Test ≠ Experiment
Scenario 보호
Failure 직접 재현
측정 없는 개선 주장 금지
Human Gate + ADR
DESIGN = 현재 구조
README Baseline부터 유지
사용하지 않은 기술을 효과 없음으로 표현하지 않음
이 원칙은 실제 개발 과정에 대부분 남았다.
28. 다음에 같은 프로젝트를 한다면
새로운 기술부터 추가하기보다 이번 Repository에서 닫지 못한 질문부터 다시 보는 편이 맞을 것 같다.
우선순위를 줄이면 다음 정도다.
1. Cluster 내부 Client로
Pod Failure / Rolling Update 재실행
port-forward Failure와
Service Failure 분리
2. Replica 2 상태에서
Pod Failure 재실행
남은 Pod CPU / Error / Ready 확인
3. Work Endpoint가 실제 CPU Bound인지 확인
CPU Saturation이 재현되면
HPA Experiment 다시 Open
4. HPA와 별개로
Fixed Replica Spike 실행
5. Experiment마다
같은 시간창의
Prometheus / Grafana Metric을 증거로 남김
그리고 문서에서는:
학습 대상
실제 도입 조건
내려놓은 항목
을 처음부터 분리해두는 편이 좋겠다.
29. 이번 프로젝트에서 가장 크게 남은 것
처음 Cloud Ops Lab을 계획할 때는 자연스럽게 다음 그림을 생각했다.
Kubernetes
↓
Prometheus / Grafana
↓
HPA
↓
AWS
하지만 실제 프로젝트에서는:
HPA
AWS
가 빠졌다.
처음 계획만 보면 학습 범위가 줄었다.
그 대신 실제로 확인한 것은 조금 더 운영 문제에 가까웠다.
Replica를 늘렸지만
Latency 이득이 없었다.
Resource를 줄였더니
Latency보다 Ready / OOM 문제가 먼저 나타났다.
Readiness 실패와
Liveness 실패의 동작이 달랐다.
Pod가 Ready가 됐지만
기존 Client 경로는 복구되지 않았다.
Capacity 문제가 없어서
HPA Threshold를 임의로 만들지 않았다.
결국 이번 Repository에서 설명 가능한 문장은 이것에 가깝다.
현재 Load에서 Replica 1의 Traffic Capacity 한계는 나타나지 않았고, 오히려 Failure와 배포 과정에서 Ready 상태와 Client 경로가 분리되는 문제가 더 명확하게 나타났다.
그리고 이 결과 때문에:
HPA를 넣지 않았고
AWS까지 확장하지 않았고
Replica 기본값도 바꾸지 않았다.
30. 정리
Cloud Ops Lab을 시작할 때는 다음 질문에 답하고 싶었다.
Traffic이 증가하면
Kubernetes는 어떻게 대응할까?
Pod가 죽으면
서비스는 어떻게 복구될까?
HPA는 실제 Latency를 줄일까?
AWS에서도 같은 운영 특성이 나타날까?
프로젝트가 끝난 뒤 모든 질문에 답할 수 있게 된 것은 아니다.
답할 수 있게 된 것은:
Resource 제약이 Pod Ready에 어떤 영향을 주는가?
Readiness와 Liveness는 실제로 어떻게 다른가?
Pod Failure 이후 Kubernetes Ready와
Client Recovery는 같은 시점인가?
Replica 증가가 현재 Load에서
실제 Latency 개선으로 이어지는가?
Rolling Update 실패 시
기존 Pod는 어떻게 유지되는가?
에 가깝다.
반대로 답하지 못한 것은:
실제 Capacity 한계는 어디인가?
HPA Scale Out은 얼마나 빠른가?
Scale In은 안정적인가?
Spike에서 Auto Scaling은 충분한가?
AWS Load Balancer 경로에서는
Ready와 Client Recovery가 같은가?
이다.
그리고 이번에도 결과보다 개발 방식에서 남은 원칙은 같다.
기술을 사용했다
≠
문제를 해결했다
Pod가 Ready다
≠
서비스가 정상이다
Replica를 늘렸다
≠
성능이 좋아졌다
Dashboard가 있다
≠
Observability가 확보됐다
Phase에 도달했다
≠
기술을 반드시 사용해야 한다
처음 학습 대상으로 적었다
≠
조건 없이 반드시 구현해야 한다
Cloud Ops Lab에서 가장 중요하게 남은 것은:
운영 기술을 많이 적용하는 것이 아니라, 실제 Load와 Failure에서 무엇이 발생했고 어떤 Metric으로 확인했으며, 어떤 기술은 왜 적용했고 어떤 기술은 왜 적용하지 않았는지를 설명할 수 있어야 한다.
라는 점이었다.
그리고 이번 프로젝트에서는 LLM Router에서 남았던:
Blog Goal
≠
Repository Goal
문제를 시작 시점에는 반복하지 않았다.
대신 한 단계 더 다른 문제가 보였다.
학습 대상으로 적은 기술
≠
실제로 도입해야 하는 기술
이었다.
다음 프로젝트에서는 이 둘도 처음부터 분리해서 적어보려고 한다.
결국 Harness의 역할은 AI가 코드를 잘 쓰게 만드는 것만은 아니다.
무엇을 배우려는지, 무엇을 성공이라고 판단할지, 어떤 조건에서 기술을 도입할지, 그리고 어떤 목표를 끝까지 닫지 못했는지까지 개발자가 통제하고 기록하게 만드는 것
이 이번 Cloud Ops Lab에서 다시 확인한 Harness의 역할이었다.
'PROJECT > Toy Project' 카테고리의 다른 글
| ① AdFlow - Mini OTT Ad Platform 프로젝트 설계 (2) | 2026.08.27 |
|---|---|
| 5개 프로젝트를 마치고 — AI와 함께 개발하는 방식에 대한 회고 (0) | 2026.08.16 |
| [Cloud Ops Lab #3] 바이브 코딩과 Harness 전략 (1) | 2026.08.16 |
| [Cloud Ops Lab #2] 요구사항 정의 (0) | 2026.08.16 |
| [Cloud Ops Lab #1] 프로젝트 소개와 학습 목표 (0) | 2026.08.16 |