Case Study - LinkedIn의 Stateful Workload Operator

· Case Study

들어가며

Stateful workload operator: stateful systems on Kubernetes at LinkedIn

아티클을 읽고 정리한 글입니다.

서문

LinkedIn에서는 자체 개발한 스케줄러를 이용해 Kafka, Zookeeper 등 Stateful한 서비스들을 운영하는 팀을 위해 베어메탈 서버를 Private Pool에 할당했습니다. 하지만 Stateful 시스템의 규모가 커짐에 따라 OS 업그레이드, 하드웨어 유지 보수, 시스템 관리 등의 부하가 계속 커졌습니다.

LinkedIn은 자체 개발했던 스케줄러에서 Kubernetes로 전환하면서, Stateful Application의 LifeCycle을 관리할 수 있는 Stateful Workload Operator를 개발했습니다.

기존 Operator를 기준으로 생각해보면, 각 Stateful 어플리케이션(Kafka, Zookeeper, MySQL 등) 별로 전용 오퍼레이터를 구성해야 했습니다. 하지만 LinkedIn은 반대로 어떤 Stateful 어플리케이션이든 공통으로 사용할 수 있는 하나의 오퍼레이터를 만들었습니다.

이 오퍼레이터가 스토리지 관리, 롤링 업데이트, 스케일링 등 Stateful Workload에 공통적으로 필요한 기능들을 제공하고, 각 어플리케이션에 특화된 기능(Cluster의 리더 선출 등)은 외부 정책 엔진(External Policy Engine)을 플러그인으로 처리하는 구조입니다.

StatefulSet이 적합하지 않다고 판단한 이유

LinkedIn이 StatefulSet을 그대로 쓰지 않고 오퍼레이터를 만든 이유입니다.

첫째, Sharding을 처리하기 힘든 구조입니다. StatefulSet은 각 Stateful Application의 샤딩 정책을 인식하지 못해서, StatefulSet 위에 별도 구현이 추가로 필요합니다.

둘째, StatefulSet은 파드 스케일 인/아웃, 배포만 지원하고, 특정 노드의 모든 파드를 중지하거나 다른 노드로 이전하는 등 유지보수 처리가 어렵습니다.

셋째, 카나리 배포가 어렵습니다. 같은 파드 세트 내에서 여러 카나리 버전을 동시에 운영할 수 없습니다.

당연히 StatefulSet 위에 추가 구현을 하거나, 웹훅, 커스텀 로직 등을 이용해 해결할 수는 있었으나 근본적인 해결이 되지 못해 커스텀 리소스를 만드는게 요구사항에 더 부합한다고 판단했습니다.

쿠버네티스가 샤드를 인식하도록 만들기

Stateful Application들은 각기 다른 아키텍쳐를 가지고, 각 아키텍쳐 별로 고유한 라이프 싸이클을 가집니다. 예를 들어 데이터베이스 시스템은 파티션의 Active 인스턴스를 하나만 갖는데, 이 인스턴스에 문제가 생기면 다운타임이나 데이터 손실이 발생합니다. 하지만 쿠버네티스는 파티션 인식 기능이 없으므로, 어플리케이션이 장애를 겪기 전에 Pod 손실에 대비할 수 있는 시점을 알려주는 매커니즘이 필요합니다.

이런 문제들을 해결하기 위해 LinkedIn팀은 Application Cluster Manager(ACM)을 개발했습니다. ACM은 “Cooperative Scheduling” 방식으로 Operator와 연계해 작동합니다. ACM은 Operator가 배포, 메인터넌스 작업을 진행해도 안전한지 평가해 어플리케이션 클러스터를 유지합니다. 각 ACM 구현은 각 Stateful Application 맞춤 로직들로 작성되어 안전성을 보장합니다. 예를 들어 Pod를 내리기 전에 충분히 샤드가 복제될 수 있도록 보장한다거나, 어플리케이션 클러스터에 걸쳐 배포 순서를 정하거나, 업데이트 과정에서 클러스터가 리더 선출을 자동으로 수행하는 것을 방지하는 등 여러 로직을 담을 수 있습니다.

Operator는 ACM에 배포, 중단, 스케일링, 스왑 4가지 유형의 요청을 보냅니다.

ACM은 작업들을 어플리케이션 클러스터에 적용해도 안전한지 판단하고, ACM이 승인 응답을 보내면 Operator가 작업을 시작합니다. 승인 응답이 없으면 Operator는 아무 것도 하지 않습니다. 이 프로세스를 통해 Stateful Application Cluster의 안정성과 보안을 보장합니다.

ACM은 Stateful Application의 라이프 싸이클 관리, 메인터넌스 작업을 간소화할 수 있습니다. 각 팀이 자체적인 Operator를 개발할 때, 각 어플리케이션은 배포를 위한 상태 머신을 개발하고, IaaS 레이어 및 쿠버네티스와 통합하도록 만들어야 합니다. 이 작업은 내부 작동 방식에 대한 지식이 필요하고, 각 어플리케이션 팀의 운영 부담이 증가합니다. ACM은 모든 오케스트레이션과 통합 작업을 중앙에서 관리하므로 이러한 부담을 줄여줄 수 있습니다. 각 어플리케이션 팀은 운영 안정성을 위한 로직을 개발하는데 집중할 수 있습니다.

Stateful 오퍼레이터 아키텍처

쿠버네티스의 오퍼레이터 패턴은 복잡한 어플리케이션 관리를 쿠버네티스-네이티브 컨트롤러로 캡슐화하는 방식입니다. 배포, 업그레이드, 스케일링, 복구 등과 같은 작업을 자동화고, CRD(Custom Resource Definition)으로 새로운 리소스 타입을 정의하고 해당 리소스의 상태 변화를 감지하여 Desired State에 맞게 조정합니다.

LinkedIn팀의 Stateful Workload Operator는 LiStatefulSet, Revision, PodIndex, Operation, StatefulPod라는 5가지 CRD를 중심으로 한 쿠버네티스 오퍼레이터입니다. 각 CRD는 오퍼레이터의 전체 라이프싸이클에서 특정 목적을 가지고 서로 다른 계층 간의 관심사를 분리합니다.

High-Level Flow

사용자가 LiStatefulSet을 적용하면 다음 순서로 동작합니다.

  1. 사용자가 LiStatefulSet YAML을 Apply
  2. Scale-out PodTemplate 버전을 추적하기 위해 Revision 생성
  3. 배포할 Pod 상세 정보를 담은 PodIndex 생성
  4. PodIndex 기반으로 어떤 Pod를 배포할지 지정하는 Operation 생성
  5. gRPC 인터페이스를 통해 ACM에 Operation 전송 → Pod 실행 준비 완료 여부를 폴링으로 모니터링
  6. ACM 정상 응답을 보내면 Pod 생성 지시
  7. Kubernetes가 적절한 노드에 Pod 스케줄링 및 배포

Auto Remediation

시스템이 클러스터의 실제 Pod 상태를 계속 모니터링하면서 LiStatefulSet에 정의된 Desired State와 비교해 차이가 발생하면 자동으로 Reconciliation Operation을 생성합니다. Operation은 scale-out, scale-in, swap, disruption, deployment이라는 5가지 유형으로 분류됩니다.

중복 Operation 방지를 위한 Deduplication 매커니즘이 있는데, 동일한 Pod + 동일한 Operation Type의 기존 Operation이 이미 존재하면 새로 생성하지 않습니다. 그래서 Reconciliation Loop가 여러 번 돌더라도 일관성을 유지할 수 있습니다.

Coordination Maintenance

오퍼레이터 설계의 목표 중 하나는 어플리케이션 소유자가 노드 관련 유지보수를 신경쓰지 않도록 하는 것입니다. 데이터센터는 OS, 펌웨어, 하드웨어 업그레이드가 빈번하게 발생합니다. 워크로드 이관, 노드 헬스 모니터링, 대체 하드웨어 프로비저닝 같은 부담을 덜어줍니다.

ACM은 Maintenance Stack으로부터 Disruption 이벤트(일시적 노드 손실 or 영구적 노드 손실)를 전달 받고, ACM이 Pod의 재배치 준비가 되면 Operator에게 알려주는 구조입니다.

Temporary Node Disruption

노드 Re-image, 업그레이드 등 데이터 손실이 없는 경우입니다. IaaS가 Operator에게, Operator가 ACM에게 요청을 전달하고, ACM 승인 후 Pod는 삭제하고 PVC는 유지해서 데이터를 보존합니다. IaaS가 노드 작업을 완료하면 노드가 클러스터에 재합류하고, Operator

Permanent Node Loss

노드 폐기 혹은 심각한 하드웨어 장애가 발생한 경우입니다. IaaS에서 영구 퇴출 요청을 받으면 오퍼레이터는 Swap 요청을 생성합니다. 기존 노드의 Pod를 내리고, 동일한 Identity로 다른 노드에 Pod를 생성합니다.

ACM이 새 Pod 추가를 먼저 승인하고, 이전 Pod 제거 승인은 지연시킵니다. 새 Pod가 완전히 올라와서 트래픽 서빙이 가능해지면, 이전 Pod 제거를 승인합니다. 확인 후 이전 Pod와 관련 리소스를 삭제하고, IaaS에 알려 노드를 클러스터에서 영구 제거합니다.

교훈

사족

왜 링크드인팀은 오퍼레이터를 선택했을까요? 다른 선택지는 없었을지 생각해보았습니다.

단순히 StatefulSet을 쓰는 것도 생각해볼 수 있었습니다. 하지만 StatefulSet은 각 스테이트풀 어플리케이션의 Shard Policy를 알 수 없고, Canary Version 대응도 불가능했습니다. 그래서 각 스테이트풀 어플리케이션 별로 별도의 추가 작업이 필요했습니다.

링크드인 팀이 해결하고자 했던 문제는 다음과 같았던 것 같습니다.

당연히 오퍼레이터의 존재 이유와 부합합니다. 단순 자동화를 넘어서는 도메인 레벨의 자동화를 담기 위해 존재하는게 오퍼레이터이기 때문입니다. 하지만 다른 방법은 없을지 궁금했습니다.

핵심 컴포넌트 중 하나인 ACM은 어떻게 관리하는건지 궁금했습니다. 본문에는 ACM을 실제로 어떻게 운영하는지에 대한 정보는 나와 있지 않았습니다. 아마도 ACM도 Service와 Pod 형태로 관리할 것 같습니다. ACM이 응답을 주지 않으면 아무 일도 일어나지 않는다고 나와 있으니, Node Drain 등으로 인해 ACM의 모든 파드가 교체되는 상황에서는 ACM이 아무 응답도 줄 수 없을테니 아무 일도 일어나지 않을테니 쿠버네티스 클러스터 내 리소스로 관리해도 문제가 없을 것으로 보입니다.

ACM에 대한 구현 정보도 본문에는 나오지 않았는데, Stateful Application 별로 ACM을 관리했거나, 하나의 ACM을 두고 플러그인 형태로 로직만 추가하는 형태 중 하나로 예상이 됩니다.