Kubernetes Kubernetes StatefulSet Storage GitLab NKS

Kubernetes StatefulSet 노드 이동 시 다운타임, 어떻게 줄일까?

관리형 Kubernetes에서 Block Storage + StatefulSet 조합의 구조적 한계와 해결 방안

들어가며

“GitLab 업그레이드하는데 20분이나 걸렸어요.”

고객사에서 운영 중인 GitLab이 NKS(Naver Kubernetes Service)에 Helm Chart로 설치되어 있었다. 스토리지는 NCP Block Storage를 사용하고 있었는데, Helm Upgrade 과정에서 StatefulSet Pod가 다른 노드로 재스케줄링되면서 Block Storage attach 지연이 발생했다. 결과적으로 약 20분의 다운타임이 발생한 것이다.

이 글에서는 문제의 원인을 분석하고, 홈클러스터에서 PoC를 진행하며 발견한 점, 그리고 관리형 Kubernetes에서의 실제 해결 방안을 공유한다.


문제 상황

환경

  • 클라우드: NCP (Naver Cloud Platform)
  • Kubernetes: NKS (관리형)
  • 스토리지: NCP Block Storage (CSI Driver)
  • 워크로드: GitLab Helm Chart (Redis, PostgreSQL, Gitaly StatefulSet)

발생한 오류

Warning FailedAttachVolume multi-attach error for volume "pvc-xxx"
Volume is already attached to node "prod-k8s-node-pool-w-2j10"
and can't be attached to another node "prod-k8s-node-pool-w-2j11"

Helm Upgrade 과정에서 Pod가 다른 노드로 스케줄링되면서 Multi-Attach 오류가 발생했다. CSI Driver가 retry하면서 결국 해결되었지만, 약 20분의 다운타임이 발생했다.


원인 분석

Block Storage의 특성

특성설명
Access ModeReadWriteOnce - 한 번에 하나의 노드에만 attach 가능
연결 방식네트워크 기반이지만 논리적으로 물리 디스크처럼 동작
노드 이동기존 노드에서 detach → 새 노드에 attach 과정 필수

이 특성은 NCP뿐 아니라 AWS EBS, GCP Persistent Disk 모두 동일하다.

노드 이동 시 attach 지연이 발생하는 이유

[기존 상태]
Node A ─── PV (attached)
[업그레이드 시작]
1. Pod 삭제 요청
2. Node A에서 PV detach 시작 (시간 소요)
3. 새 Pod 스케줄링 → Node B 선택
4. Node B에서 PV attach 시도
5. 아직 Node A에서 detach 완료 안 됨 → Multi-Attach 오류
6. Node A detach 완료 → Node B attach 성공 (수 분~20분 소요)

핵심: 클라우드 Block Storage는 detach/attach에 클라우드 API 호출이 필요하며, 이 과정에서 수 분에서 수십 분의 지연이 발생한다.

해결 가설

“Pod가 같은 노드에 스케줄링되면 attach/detach 자체가 불필요하지 않을까?”

논리적으로 맞는 말이다. 같은 노드라면 Volume이 이미 attach되어 있으므로 지연이 발생하지 않는다. 이를 위해 Soft Affinity를 사용할 수 있다.


PoC: Soft Affinity 동작 검증

Soft Affinity 설정이 실제로 동작하는지, 그리고 장애 시 복구성은 유지되는지 홈클러스터에서 검증했다.

테스트 환경

  • 스토리지: Longhorn (분산 스토리지)
  • 노드: 3개 (node1, node2, node3-gpu)
  • 워크로드: Redis, PostgreSQL StatefulSet

Soft Affinity 설정

affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2 # 현재 노드

검증 결과

검증 항목결과
Soft Affinity 설정 시 같은 노드 스케줄링O
선호 노드 cordon 시 다른 노드로 복구O
Hard Affinity 대비 복구성 유지O

Soft Affinity 동작 확인. 설정하면 같은 노드에 스케줄링되고, 해당 노드가 불가능하면 다른 노드로 복구된다.

예상과 다른 점: 환경 차이 발견

테스트 중 노드 이동 시나리오에서 예상과 다른 결과가 나왔다.

예상: Multi-Attach error 후 수 분~20분 대기
실제: Multi-Attach error 후 10-20초 만에 복구

60배 차이. 원인을 분석해보니 홈클러스터의 Longhorn과 NCP Block Storage는 근본적으로 아키텍처가 달랐다.


Longhorn vs NCP Block Storage: 아키텍처 차이

NCP Block Storage (중앙 집중식)

flowchart LR
subgraph Node["Node A"]
Pod[Pod]
end
subgraph Backend["NCP Storage Backend"]
Volume[(Block Volume<br/>물리 디스크)]
end
Pod <-->|"iSCSI/NVMe<br/>(네트워크)"| Volume
style Backend fill:#f5f5f5,stroke:#333

노드 이동 순서: detach API 호출 → 완료 대기 → attach API 호출 → 마운트 소요 시간: 수 분 ~ 20분

Longhorn (분산 스토리지)

flowchart TB
subgraph NodeA["Node A"]
ReplicaA[Replica]
PodBefore[Pod - 이동 전]
end
subgraph NodeB["Node B"]
ReplicaB[Replica]
PodAfter[Pod - 이동 후]
end
subgraph NodeC["Node C"]
ReplicaC[Replica]
end
ReplicaA <-->|동기화| ReplicaB
ReplicaB <-->|동기화| ReplicaC
ReplicaA --> PodBefore
ReplicaB -->|"이미 로컬에<br/>데이터 있음"| PodAfter
style PodBefore fill:#ccc,stroke:#999
style PodAfter fill:#90EE90,stroke:#333

노드 이동 순서: 로컬 replica 활성화 → 마운트 소요 시간: 수 초

비교

항목NCP Block StorageLonghorn
데이터 위치중앙 스토리지 (1곳)각 노드에 replica (3곳)
Detach클라우드 API 호출 필요내부 상태 변경
AttachAPI 호출 + 네트워크 연결로컬 replica 활성화
병목하이퍼바이저 + API + 네트워크없음

결론: 홈클러스터 환경이 NCP 환경을 재현하지 못했다. 하지만 “같은 노드면 attach 불필요”라는 논리는 여전히 유효하다.


그렇다면 NKS에서는 어떻게 해야 할까?

”NKS에 별도 StorageClass를 설치하면 되지 않나요?”

Longhorn이나 Rook-Ceph 같은 분산 스토리지를 설치하면 attach 지연 문제가 해결될 것 같지만, 관리형 Kubernetes에서 별도 스토리지를 띄우는 것은 일반적이지 않다.

이유설명
중복 비용클라우드 스토리지 + 노드 디스크 이중 비용
복잡성분산 스토리지 운영 부담 (Ceph는 15 CPU + 24GB RAM 필요)
지원 문제클라우드 벤더 지원 범위 밖

Kubernetes Storage Comparison에서도 이렇게 권장한다:

“If you’re all-in on a single cloud provider, use the managed block/file CSI drivers and invest instead in replication + backup strategies.”

NKS에서의 실제 권장 패턴

방안효과복잡도권장 상황
Soft Affinity다운타임 감소낮음단기 완화
HA 구성 (Primary-Replica)다운타임 제거높음프로덕션 필수
관리형 서비스다운타임 제거낮음비용 허용 시

GitLab Helm Chart의 프로덕션 권장사항

GitLab 공식 문서에서는 기본 Helm Chart 설정에 대해 다음과 같이 명시하고 있다:

The default Helm chart configuration is not intended for production. The default chart creates a proof of concept (PoC) implementation where all GitLab services are deployed in the cluster. For production deployments, you must follow the Cloud Native Hybrid reference architecture.

기본 Helm Chart의 한계:

  • 모든 서비스가 클러스터 내 StatefulSet으로 배포
  • PostgreSQL, Redis, Gitaly가 단일 인스턴스
  • 노드 이동 시 다운타임 발생이 구조적으로 불가피

Cloud Native Hybrid 아키텍처

GitLab이 프로덕션에서 권장하는 구조:

컴포넌트기본 HelmCloud Native Hybrid
PostgreSQL클러스터 내 StatefulSet관리형 DB (Cloud SQL, RDS)
Redis클러스터 내 StatefulSet관리형 캐시 (ElastiCache)
Gitaly클러스터 내 StatefulSet외부 HA 구성
Object StorageMinIO StatefulSet관리형 스토리지 (S3, GCS)

결국 Block Storage + StatefulSet 조합의 구조적 한계를 아키텍처 수준에서 해결하는 것이다.


결론: 단계별 접근

단기 (즉시 적용 가능)

Soft Affinity 설정으로 다운타임 최소화:

# GitLab values.yaml
redis:
master:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- <현재_redis_노드>
  • 같은 노드 스케줄링 → attach 자체가 불필요
  • 노드 장애 시 다른 노드로 복구 가능 (복구성 유지)
  • 완화책일 뿐 근본 해결은 아님 - 노드 장애/유지보수 시에는 여전히 다운타임 발생

중기 (아키텍처 개선)

HA 구성으로 다운타임 제거:

  • Redis Sentinel / Redis Cluster
  • PostgreSQL Streaming Replication
  • Gitaly Praefect (HA)

장기 (근본 해결)

Cloud Native Hybrid 아키텍처 전환:

  • 관리형 DB 서비스 사용 (NCP Cloud DB)
  • 관리형 캐시 서비스 사용
  • StatefulSet 다운타임 문제 자체가 사라짐

교훈

  1. Block Storage + StatefulSet은 구조적 한계가 있다

    • 노드 이동 시 다운타임은 피할 수 없음
    • Soft Affinity는 완화책
  2. 프로덕션 수준 아키텍처는 고가용성, 관리성, 확장성을 빼놓을 수 없다.

    • GitLab 기본 Helm Chart 구성은 프로덕션용으로는 부족
    • 다운타임 제로가 필요하면 HA 구성 또는 관리형 서비스 필수

참고 자료

관련 콘텐츠

댓글