문제의 시작: 모든 게 정상인데 GPU가 놀고 있다
Kubernetes 환경에서 분산 학습 작업을 운영하다 보면 "모든 Pod가 Running 상태인데 학습이 시작되지 않는" 상황을 마주할 수 있다. 이 사례에서도 마찬가지였다. 대시보드에는 에러가 없고, OOMKill도 없고, 애플리케이션 로그도 깨끗하다. 그런데 GPU 사용률은 60%에 머문 채 학습은 시작조차 하지 않는다.
처음엔 모니터링 수치 자체를 의심했다. 하지만 문제는 실재했다. 수십 개의 GPU 워커 Pod는 모두 실행 중이었고, 비용은 그대로 청구되고 있었지만 실제 연산은 이루어지지 않고 있었다. 분산 학습은 모든 워커가 코디네이터와 통신하며 그래디언트를 동기화해야 비로소 작동한다. 즉, 단 하나의 통신 경로가 막혀도 전체 작업은 멈춘다.
원인: 각자 옳았지만, 함께하면 틀린 두 시스템
이 문제의 핵심은 Kubernetes 스케줄러와 Cilium 네트워크 정책이 각각은 완전히 올바른 동작을 했지만, 조합 결과가 틀렸다는 데 있다.
- Kubernetes 스케줄러(및 Kubeflow): 토폴로지를 고려하지 않는다. CPU, 메모리, GPU 수량 같은 리소스 가용성만 보고 Pod를 배치한다. 어떤 가용 영역(Availability Zone)에 Pod가 떨어지는지는 기본적으로 신경 쓰지 않는다.
- Cilium: 반대로 토폴로지를 인식한다. 운영자는
CiliumNetworkPolicy를 이용해 존(zone) 경계를 정의하고, 고가의 GPU 풀 보호나 규정 준수, 비용 제어 등의 목적으로 영역 간 통신을 제한한다.
결국 Kubeflow가 학습 코디네이터 Pod를 특정 Zone에 배치했는데, Cilium의 네트워크 정책이 해당 Zone과 GPU 워커 Pod가 속한 Zone 간의 통신을 차단하고 있었다. 코디네이터는 워커에게 닿을 수 없었고, 워커들은 신호를 기다리며 idle 상태로 유지됐다. 마치 연주 준비를 마친 오케스트라 단원들이 지휘자가 다른 문으로 갇혀버린 채 침묵하는 것과 같다.
디버깅 관점의 전환: 로그가 아닌 네트워크 레이어를 봐라
이 사례가 백엔드 개발자에게 주는 가장 중요한 교훈은 관측 범위의 확장이다. Pod 상태, 애플리케이션 로그, 리소스 메트릭만 보는 전통적인 디버깅 방식으로는 이 문제를 발견할 수 없다.
네트워크 계층 병목을 추적하려면 다음과 같은 도구와 접근이 필요하다.
# Cilium 정책 적중 여부 확인
cilium monitor --type drop
# 특정 Pod 간 연결성 테스트
cilium connectivity test
# 네트워크 정책 시뮬레이션
kubectl exec -n cilium cilium-xxxxx -- cilium policy get
분산 시스템에서는 애플리케이션 레이어가 완전히 정상이더라도 인프라 네트워킹 레이어에서의 암묵적 차단이 시스템 전체를 멈출 수 있다. 특히 CNI 플러그인 수준의 정책은 애플리케이션이 인지조차 못한 채 패킷을 drop하기 때문에, 타임아웃 외에는 아무런 에러도 발생하지 않는 경우가 많다.
실무적으로는 분산 작업 배포 전 스케줄러의 토폴로지 인식 여부와 CNI 정책이 그 토폴로지를 고려하는지를 함께 검증하는 파이프라인이 필요하다. TopologySpreadConstraints나 nodeAffinity를 활용해 스케줄러가 네트워크 정책과 호환되는 위치에 Pod를 배치하도록 유도하는 것이 현실적인 해결 방향이다.
정리
- Pod가 모두 Running이어도 네트워크 정책 레이어의 차단으로 분산 작업 전체가 멈출 수 있으며, 애플리케이션 로그에는 아무 흔적도 남지 않는다.
- Kubernetes 스케줄러(토폴로지 무관)와 Cilium(토폴로지 인식)은 각각 올바르게 동작하지만, 조합 결과가 의도치 않은 통신 차단을 만들 수 있다.
- 분산 시스템 디버깅 시 관측 범위를 애플리케이션 레이어에서 CNI/네트워크 정책 레이어까지 확장해야 한다.