# Ray head가 죽으면 무엇이 사라지는가 - GCS 장애 복원력
Ray 클러스터에서 head 파드는 특별합니다. 워커는 몇 개가 죽어도 클러스터가 살아남지만, head가 죽으면 워커가 전부 멀쩡해도 클러스터가 사라집니다. 이 비대칭의 원인은 head 안에서 도는 GCS(Global Control Store)에 있습니다.
그런데 head 파드에 restartPolicy를 걸어 두는 것만으로는 해결되지 않습니다. 재시작된 head는 기존 워커를 자기 것으로 인식하지 못합니다. 이 글에서는 GCS가 무엇을 들고 있는지, 외부 Redis를 붙였을 때 정확히 무엇이 복구되고 무엇이 복구되지 않는지, 그리고 두 개의 타임아웃이 왜 서로 다른 값이어야 하는지를 정리합니다.
# 1. GCS가 죽으면 벌어지는 일
GCS는 클러스터 수준 메타데이터를 관리하고, 기본 설정에서는 전부 메모리에만 들고 있습니다. 문서의 표현이 직설적입니다.
a failure can cause the entire Ray cluster to fail
연쇄는 이렇게 진행됩니다. GCS 프로세스가 죽으면 head 파드도 RAY_gcs_rpc_server_reconnect_timeout_s 초 뒤에 함께 죽습니다. 워커들은 재접속을 시도하지만 결국 종료되고, 클러스터 상태는 사라집니다. 새로 뜬 head는 살아 있던 워커들을 "unknown workers" 로 취급합니다.
여기서 짚어야 할 점이 있습니다. 워커 파드는 그 시점까지 정상이었습니다. GPU를 붙들고 모델을 로드해 둔 상태 그대로였는데, head 하나 때문에 전부 버려집니다. 모델 로딩에 수 분씩 걸리는 추론 클러스터라면 head 재시작 한 번의 비용이 그대로 서비스 중단 시간이 됩니다.
# head 파드가 실제로 무엇을 지고 있는지
kubectl get pod <head-pod> -o jsonpath='{.spec.containers[*].name}'
# ray-head, autoscaler … GCS 와 오토스케일러가 같은 파드에 있다
즉 head 파드는 컨트롤 플레인입니다. 스팟 인스턴스, 축출 대상 노드, 자원 경합이 심한 노드에 올려서는 안 됩니다.
# 2. 외부 Redis를 붙인다는 것
GCS 장애 복원력은 메타데이터를 외부 Redis에 넘겨 두는 방식으로 동작합니다. RayCluster에 gcsFaultToleranceOptions를 추가합니다.
apiVersion: ray.io/v1
kind: RayCluster
metadata:
name: inference
spec:
gcsFaultToleranceOptions:
redisAddress: "redis:6379"
redisPassword:
valueFrom:
secretKeyRef:
name: redis-password
key: password
GCS가 재시작하면 Redis에서 데이터를 다시 읽어 원래 기능을 이어갑니다. 그 결과 결정적인 차이가 생깁니다.
the worker Pod isn't terminated by the new head after reconnecting because GCS fault tolerance is enabled
워커가 살아남습니다. 1절의 연쇄가 끊기는 지점이 바로 여기입니다.
요구 조건은 명확합니다. Ray 2.0.0 이상, KubeRay 1.3.0 이상, 그리고 Redis는 단일 샤드 구성의 고가용 Redis여야 합니다. 여기서 흔한 실수가 나옵니다. 복원력을 위해 붙인 Redis가 단일 파드에 emptyDir로 떠 있으면, 단일 장애점을 head에서 Redis로 옮긴 것뿐입니다. Redis 자체의 가용성을 먼저 해결해야 의미가 있습니다.
# 3. 무엇이 복구되지 않는가
이 부분을 오해하면 설계가 어긋납니다. 문서는 선을 분명히 긋습니다.
the fault tolerance doesn't persist the actor's state
복구되는 것은 클러스터 메타데이터이지 애플리케이션 상태가 아닙니다. 액터가 메모리에 들고 있던 값은 돌아오지 않습니다. detached 액터가 살아남는 경우가 있는데, 상태가 복구돼서가 아니라 그 액터가 올라가 있던 워커 파드가 죽지 않았기 때문입니다. 구분이 중요합니다.
| 대상 | head 재시작 후 |
|---|---|
| 클러스터 메타데이터(노드 구성, 배치 정보 등) | Redis에서 복구 |
| 워커 파드 | 유지 (재접속) |
| 워커 위에 있던 detached 액터 | 파드가 살아 있으므로 유지 |
| 액터 내부 상태 | 복구 안 됨 |
| head 위에서 돌던 드라이버 | 복구 안 됨 |
그래서 설계 원칙이 따라옵니다. 상태는 액터 메모리가 아니라 외부에 두어야 합니다. 체크포인트를 공유 스토리지나 오브젝트 스토리지에 남기고, 액터는 언제든 다시 만들 수 있는 계산 단위로 취급하는 편이 안전합니다. GCS 장애 복원력은 "재기동 시간을 줄여 주는 장치"이지 "상태를 지켜 주는 장치"가 아닙니다.
# 4. 두 개의 타임아웃이 다른 이유
이 설정에서 가장 실수하기 쉬운 부분입니다. 재접속 타임아웃이 head와 워커에 각각 존재하고, 기본값이 다릅니다.
| 대상 | 기본값 | 의미 |
|---|---|---|
| head 파드 | 60초 | GCS 재접속을 기다리는 시간 |
| 워커 파드 (KubeRay가 주입) | 600초 | 워커가 head 재접속을 기다리는 시간 |
워커 쪽이 10배 긴 이유는 단순합니다. 워커가 head보다 먼저 포기하면 안 되기 때문입니다. head가 재시작해서 다시 뜨는 데 걸리는 시간 동안 워커가 기다려 줘야 3절의 "워커 유지"가 성립합니다. 워커 타임아웃이 head 복구 시간보다 짧으면, 복원력을 켜 놓고도 워커가 먼저 종료됩니다.
여기서 실무적으로 확인할 것은 실제 head 복구 시간입니다. 이미지 pull이 느리거나 노드를 새로 띄워야 하는 환경이라면 600초를 넘길 수 있습니다.
# head 파드 재생성부터 Ready 까지 실제로 얼마나 걸리는지 측정
kubectl delete pod <head-pod>
kubectl get pod -l ray.io/node-type=head -w
이 값이 600초에 가깝다면 타임아웃을 늘리기 전에 복구 시간 자체를 줄이는 편이 낫습니다. 이미지를 노드에 미리 캐시하거나, head 전용 노드를 상시 확보하는 방식입니다. 타임아웃만 늘리면 장애 감지도 그만큼 늦어집니다.
# 5. 도입 전 점검
| 항목 | 확인 |
|---|---|
| Redis 가용성 | 단일 파드·비영속 구성이 아닌지 |
| Redis 접근 제어 | 클러스터 메타데이터가 담기므로 인증·네트워크 정책 필요 |
| head 노드 안정성 | 스팟·축출 대상 노드 배제 |
| head 복구 시간 | 워커 재접속 타임아웃보다 충분히 짧은지 |
| 애플리케이션 상태 | 액터 메모리에만 있는 상태가 없는지 |
| Redis 정리 | 클러스터 삭제 시 남는 데이터 처리 정책 |
마지막 항목은 놓치기 쉽습니다. KubeRay는 Redis 정리에 실패하면 finalizer를 제거하고 넘어가므로, 삭제된 클러스터의 데이터가 Redis에 남을 수 있습니다. 클러스터를 자주 만들고 지우는 환경이라면 키 네임스페이스와 정리 절차를 미리 정해 두어야 합니다.
# 6. 트러블슈팅
| 증상 | 원인 | 조치 |
|---|---|---|
| head 재시작 후 워커가 전부 재생성됨 | 복원력 미적용 | gcsFaultToleranceOptions 확인 |
| 복원력을 켰는데도 워커가 죽음 | 워커 재접속 타임아웃 < head 복구 시간 | 복구 시간 측정 후 값 조정 |
| head가 뜨자마자 다시 죽음 | Redis 접속 실패 | 주소·비밀번호·네트워크 정책 확인 |
| 복구 후 액터 응답이 이상함 | 액터 상태는 복구 대상이 아님 | 상태를 외부로 분리 |
| Redis 장애 시 클러스터 전체 정지 | 단일 장애점 이동 | Redis 고가용 구성 |
# 7. 마무리
- head가 죽으면 워커가 멀쩡해도 클러스터가 무너집니다. 원인은 GCS가 메타데이터를 메모리에만 두기 때문입니다.
- 외부 Redis를 붙이면 head 재시작 뒤에도 워커가 살아남습니다. 모델 로딩이 긴 추론 클러스터에서 효과가 가장 큽니다.
- 복구되는 것은 메타데이터뿐입니다. 액터 상태는 돌아오지 않습니다. 상태는 처음부터 외부에 두어야 합니다.
- 워커 재접속 타임아웃(기본 600초)은 head 복구 시간보다 길어야 합니다. 실제 복구 시간을 측정하지 않으면 켜 놓고도 동작하지 않습니다.