시스템의 안정성을 확보하기 위한 노력과 기법은 수없이 많지만, 그중에서도 이번 글에서는 요기요 서비스를 대상으로 수행한 카오스 엔지니어링 실험 경험을 공유하고자 합니다.
이번 실험을 통해 카오스 엔지니어링을 어떻게 활용해 시스템을 진단할 수 있는지, 그리고 예상치 못했던 결과로부터 어떤 인사이트를 얻을 수 있었는지를 배우는 뜻깊은 시간이었습니다.
(1)편에서는 카오스 엔지니어링을 시작하기 위한 여정과 실행 과정, 그리고 그 과정에서 발견한 이슈들에 대해 다루고자 합니다.
(2)편에서는 카오스 엔지니어링을 실제로 실행한 결과를 공유할 예정입니다.
카오스 엔지니어링이란?
이름에서 알 수 있듯이 의도적으로 운영 중인 시스템에 ‘chaos(장애)’를 주입하여 그 결과를 통해 통찰력을 얻고, 시스템의 미래 안정성을 더욱 견고하게 만드는 것을 말합니다.
수년 전부터 Netflix, Google, Amazon 등 글로벌 기업들이 카오스 엔지니어링을 도입하여 서비스의 내구성과 탄력성을 검증하기 위한 실험적 접근 방식을 시도해 왔습니다.
그중에서도 Netflix는 2010년부터 AWS로 서비스를 이전하면서 폭증하는 트래픽 속에서도 안정적인 서비스를 제공하기 위해 Chaos Monkey라는 도구를 만들어냈습니다. 이 도구는 이름처럼 장난꾸러기 원숭이처럼 무작위로 인스턴스를 종료시키는 역할을 했습니다.
갑자기 서버가 종료되더라도 시스템이 정상적으로 작동하도록 보장하기 위해 고가용성(HA), 자동 복구 기능, 그리고 트래픽을 유연하게 처리할 수 있는 아키텍처를 설계해야 했습니다.
Press enter or click to view image in full size
Chaos Monkey가 성공적으로 작동하자, Netflix는 이를 더 큰 규모로 확장했습니다. 그 결과 Simian Army라는 일련의 도구 세트를 개발했는데, 여기에 Chaos Gorilla, Latency Monkey, Doctor Monkey 등 더 복잡한 시나리오를 시뮬레이션하는 도구들이 포함되었습니다.
예를 들어 Chaos Gorilla는 AWS의 특정 가용 영역(Availability Zone) 장애를, Chaos Kong은 전체 리전(Region) 장애를 시뮬레이션했습니다. 이러한 테스트를 통해 Netflix는 특정 리전이 완전히 중단되더라도 서비스가 지속될 수 있도록 복원 계획을 수립할 수 있었습니다.
무엇부터 시작해야 할까?
개념은 알아봤지만 어떻게 시작해야 할지 막막한 것이 카오스 엔지니어링의 특징입니다. 접근하려면 몇 가지 정의를 하고 범위를 좁혀가는 과정이 필요했습니다. 그래서 크게 다음 세 가지 주제로 고민했습니다.
1.대상 서비스와 테스트 환경 정의
대부분의 현대 시스템은 정도의 차이는 있지만 여러 개의 작은 마이크로서비스로 구성되어 있습니다. 요기요 서비스도 마찬가지로 수많은 작은 서비스들이 모여 하나의 애플리케이션을 구성하고 있습니다.
카오스 실험을 진행하려면 정확한 대상 서비스를 선별할 필요가 있습니다. 내부 논의 끝에 사용자의 멤버십을 관리하는 membership-yo를 실험 대상으로 정했습니다. (요기요는 대부분의 마이크로서비스 이름에 -yo를 붙이는 관례가 있습니다.)
이 서비스를 선택한 이유는 장애 발생 시 영향도가 비교적 낮고, 비교적 최근에 개발되어 여러 실험을 통해 아직 알려지지 않은 이슈를 발견하기에 적합하다고 판단했기 때문입니다.
또한 실험 환경도 결정해야 했습니다. 요기요는 24시간 주문이 계속 이어지는 서비스이므로, 멤버십 서비스라도 장애가 발생하면 음식 주문에 직접적인 영향이 생길 수 있습니다. 따라서 stage-integration 환경에서 실험을 수행하기로 했습니다. 해당 환경은 외부 연동 테스트와 부하 테스트를 주로 진행하는, 프로덕션 이전 단계의 환경입니다.
2. 카오스 실험 시나리오 작성과 예상 결과
카오스 엔지니어링을 실행하기로 했으니 시나리오를 두 개는 만들어보자고 결심했습니다.
Press enter or click to view image in full size
A. pod 네트워크 지연
Press enter or click to view image in full size
카오스 실험에서 가장 어렵고 고민스러운 과정은 시나리오를 도출하는 일입니다. 고민 끝에 ‘서비스에 네트워크 지연(network latency)이 점차 증가하면 어떤 일이 발생할까?’라는 질문을 세웠습니다.
운영 중 실제로 발생할 수 있는 상황이며, 단계적으로 네트워크 지연을 높여가면서 서비스 상태를 확인해보는 방식으로 실험을 설계했습니다.
B. 외부 API 통신 장애
Press enter or click to view image in full size
서비스에서 외부 제공 API에 문제가 생겼을 때 시스템이 의도대로 동작하는지 검증하려 했습니다. 요기요 membership 서비스는 앱 외에도 다른 외부 서비스와 연동해 멤버십 혜택을 제공합니다.
특히 회원 탈퇴 시 외부 API를 호출해 탈퇴 절차를 진행하는 흐름이 있는데, 이 호출을 의도적으로 차단하여 데이터베이스 정합성이 유지되는지, 그리고 서비스의 다른 기능들이 정상 동작하는지를 확인하고자 했습니다.
3. 장애 주입 도구 선정과 모니터링 지표 정의
장애를 주입하기 위한 여러 도구들이 있으며 우리 시나리오에 맞는 도구를 선정하기 위해 고민했습니다. 대표적인 도구로는 LitmusChaos, Chaos Mesh, AWS FIS 등이 있습니다. 시나리오에 적합하다면 그 밖의 방법을 사용해도 무방하다고 판단했습니다.
첫 번째 시나리오의 경우 AWS FIS(Fault Injection Service)를 선택했습니다. 이유는 Membership 서비스가 AWS EKS에서 운영되고 있었고, AWS FIS가 네트워크 지연(네트워크 레이턴시) 장애 주입을 명확히 지원하며 AWS 콘솔에서 간단히 실행할 수 있어 편리했기 때문입니다.
두 번째 시나리오에서는 외부 API 접근을 차단하는 실험이 필요했는데, 다행히 해당 외부 API를 단독으로 사용하고 있어 외부로 나가는 아웃바운드(outbound) IP만 차단하면 간단히 실험할 수 있었습니다.
그리고 실험 중 모니터링해야 하는 주요 지표들을 정의해야 했는데, 서비스의 latency나 pod 개수의 변화 5xx HTTP 상태코드 등을 포함했습니다. 이 부분은 2부에서 좀더 자세하게 소개하겠습니다.
AWS FIS 사용 및 이슈 해결
AWS FIS를 통해 pod network latency를 membership 서비스에 주입하는 실험을 진행하던 중 발견한 이슈와 이를 해결한 과정을 공유합니다.
이슈 1 — FIS pod에 Istio 적용 시 API server와 통신 불가
A. 원인 분석
AWS FIS를 사용하면 EKS 클러스터 내에 FIS pod가 설치됩니다. 그런데 Istio service mesh가 적용된 환경에서 FIS pod가 실행 후 곧바로 종료되는 현상을 발견했습니다.
이슈를 찾던 중, stack overflow에서 비슷한 경우가 많다는 것을 확인했습니다. (https://stackoverflow.com/questions/77833974/aws-fis-does-not-work-for-istio-enabled-applications)
FIS Pod는 기동 직후 즉시 Kubernetes API server에 접근을 시도합니다. 하지만 Istio를 사용하는 환경에서는 **Istio Proxy(Envoy)**가 트래픽을 정상적으로 전달할 수 있는 상태가 된 이후에야 통신이 허용됩니다. 즉, Proxy가 준비되기 전까지는 트래픽이 차단되며, 이 시간 차이 때문에 FIS Pod가 API Server와 통신하지 못하고 실패하여 종료되는 것이 원인이었습니다.
B. 해결 방법
FIS Pod는 서비스 트래픽을 주고받는 역할을 하지 않기 때문에 Istio Proxy를 사용할 필요가 없다고 판단했습니다. 따라서 Istio 사이드카 주입을 비활성화하는 Annotation을 pod에 추가하는 방법으로 문제를 해결했습니다. sidecar.istio.io/inject : false
AWS FIS는 template 작성 시 FIS pod의 annotation 설정을 지원하므로, 해당 설정을 통해 Istio Proxy가 주입되지 않도록 했습니다. 이 설정 이후 FIS Pod가 비정상 종료되는 현상이 해결되었습니다.
Press enter or click to view image in full size
참고로, Istio의 holdApplicationUntilProxyStarts 값을 사용해서 proxy가 준비될 때까지 application을 멈추는 기능을 사용해서 해결할 수도 있지만, 이것은 차후 운영 환경에 잠재적인 위험을 줄 수 있다고 판단하여 선택하지 않았습니다.
이슈 2 — FIS pod가 생성하는 임시 Container의 권한 부족
A. 원인 분석
EKS에서 AWS FIS가 pod에 장애를 주입하는 과정은 다음과 같습니다.
- FIS pod (controller) 생성
- 서비스 pod에 임시 컨테이너 생성
- 임시 컨테이너가 장애 주입
여기서 3번 과정의 임시 컨테이너는 장애를 주입하기 위해 root 권한이 필요합니다. AWS 공식 문서의 “Limitations” 섹션에서도 다음과 같은 조건이 명시되어 있습니다.
Press enter or click to view image in full size
즉, Pod-Network-Latency 액션을 사용하려면 임시 컨테이너가 root 권한을 가져야 합니다.
B. 해결 방법
요기요의 멤버십 서비스는 보안 강화를 위해 pod 및 컨테이너를 root 권한이 아닌 특정 UID로 실행하고 있었습니다.
현재 설정된 예제는 다음과 같이 설정되어 있었습니다. 이 설정을 통해서container는 xxxx UID/GID로 Container가 실행됩니다.
api:
security:
podSecurityContext:
fsGroup: xyz
runAsNonRoot: true
runAsUser: xxxx
runAsGroup: xxxx
containerSecurityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
이 문제를 해결하기 위해 실험 환경에 한정하여 임시로 root 권한을 허용하는 securityContext 설정을 적용했습니다. 운영 환경에는 영향을 주지 않도록 Stage 환경 전용 PodTemplate에만 아래 설정을 추가했습니다.
api:security:
podSecurityContext:
runAsNonRoot: false
runAsUser: 0
containerSecurityContext:
privileged: true
allowPrivilegeEscalation: true
readOnlyRootFilesystem: false
runAsNonRoot: false
runAsUser: xxxx
runAsGroup: xxxx
이 설정 이후, FIS 임시 컨테이너가 정상적으로 생성되고 network latency 주입이 성공적으로 수행되었습니다.