시작하며
안녕하세요. 카카오페이증권 DevOps 팀의 벨입니다.
본 글은 다양한 Kubernetes 플랫폼에 서비스를 분산 배포하고, 클라우드 종속성을 줄이면서 장애 상황에도 유연하게 대응할 수 있는 구조를 통해 고가용성과 안정성을 동시에 확보한 클러스터 운영 경험을 공유합니다.
클라우드 환경에서 운영 중인 서비스의 안정성 향상에 고민하는 분들을 위한 글입니다.
도입 배경
서비스 가용률 99.999%.
많은 업계에서 이 수치는 단순히 “좋은 서비스의 상징”처럼 여겨지지만 증권업에서는 선택이 아니라 의무에 가깝습니다.
서비스가 몇 분 멈췄다는 사실보다 언제 장애가 발생했고, 얼마나 지속됐고, 그 여파가 어땠는지를 증명해야 하는 업이죠.
단 0.001%의 가용성 손실은 하루 24시간 기준으로 약 5분의 장애를 의미합니다.
2024년 카카오페이증권의 SLO였던 99.993%는 객관적으로는 높은 수치입니다.
하지만, 증권업의 특성상 0.007%의 손실은 10분 이상의 장애로 고객이 제대로 돈을 벌 수 있는 투자 문화를 만들어가기 위해서는 이 수치조차도 부족할 수 있습니다.
시스템이 잠시 멈춘 그 순간 고객의 중요한 투자 기회가 지나갈 수 있습니다.
즉, 기술적인 이슈를 넘어서 금융 서비스가 지켜야 할 고객과의 신뢰 문제로 이어집니다.
그래서 스스로 묻게 됐습니다. “진짜 99.999%에 가까워질 수 있을까?”
그리고 이 질문은 자연스럽게 더 유연하고 견고한 인프라 구조, 더 빠른 장애 대응 체계, 플랫폼에 얽매이지 않는 서비스 운영 방식을 고민하게 만들었습니다.
그 결과가 바로 이 글에서 소개할 멀티 클러스터 그리고 하이브리드 클러스터입니다.
멀티 클러스터: 하나의 서비스를 n개의 클러스터에
카카오페이증권의 서비스들은 총 3개(AWS EKS, IDC Kubernetes, KakaoCloud Kubernetes Engine)의 쿠버네티스 플랫폼 위에서 운영되고 있습니다.
(* 이후 서술에서는 ‘KakaoCloud Kubernetes Engine’를 KC KE로 축약하여 표기합니다.)
이렇게 다양한 클라우드 플랫폼을 사용하게 된 이유는 증권업이라는 업의 특성상, 장중 거래량의 급격한 변화에도 안정적으로 대응할 수 있는 유연한 인프라가 요구되기 때문입니다.
이러한 요구사항을 충족시키기 위해 다양한 클라우드 플랫폼을 활용하는 방향을 택하게 되었습니다.
즉각적인 스케일링이 요구되는 서비스나 S3, CF 등 클라우드에서 제공하는 매니지드 인프라 서비스를 사용하는 경우 네트워크 경로 최적화 및 서비스 연동 편의성을 고려해 해당 서비스와 같은 클라우드 환경 즉, AWS EKS나 KC KE 위에 배포하는 방식으로 구성했습니다.
반면, 단순히 내부 시스템이나 데이터센터 내 서비스들을 호출하는 서비스의 경우에는 IDC Kubernetes 클러스터에서도 충분히 안정적으로 운영이 가능했고 비용과 운영 효율 측면에서도 더 적합한 선택이었습니다.
물론 최근에는 IAM Role Anywhere 같은 기능을 활용해 KC KE나 IDC 클러스터에서도 AWS 리소스를 직접 사용할 수 있게 되면서 플랫폼 간 경계가 점점 더 유연해지고 있는 상황입니다.
(* IAM Role Anywhere는 AWS 외부 환경(예: 온프레미스, 다른 클라우드)에서도 IAM Role을 안전하게 사용할 수 있도록 해주는 인증 방식입니다.)
결과적으로 서비스의 성격과 호출 위치, 필요한 매니지드 리소스, 비용 구조 등을 종합적으로 고려하여 AWS EKS, IDC Kubernetes, KC KE라는 세 가지 클라우드 환경을 목적에 맞게 조합해서 사용하는 구조가 되었습니다.
이렇게 다양한 클라우드 플랫폼을 도입해 운영 중이지만, 실제 서비스는 하나의 클러스터로만 배포되고 운영되고 있었습니다.
이는 서비스 간 격리를 용이하게 하고 운영을 단순화한다는 장점이 있지만, 반대로 클러스터 단위의 장애 발생 시 서비스 전체가 영향을 받는다는 리스크도 함께 내포합니다.
특히, 클라우드 단위에서 장애가 발생할 경우 서비스 전체가 중단될 수밖에 없다는 점은 운영상 매우 치명적인 한계로 작용합니다.
이러한 고민 끝에 하나의 서비스를 여러 클러스터에 동시에 배포하고 서비스 트래픽 분산과 장애 대응이 가능하게 하는 고가용성 클러스터 운영 환경을 구축하게 되었습니다.
서비스의 고가용성을 확보하기 위해 먼저 배포 레이어에서 접근했습니다.
서비스를 여러 클러스터에 병렬로 배포하여 하나의 클러스터에 장애가 나더라도 트래픽을 다른 클러스터에 떠있는 서비스로 우회할 수 있도록 구성하였습니다.
이 방식은 저희 wallga 배포 파이프라인을 통해 배포되는 서비스들에 매우 쉽고 빠르게 적용할 수 있었습니다.
배포 전략
멀티 클러스터 환경을 적용하겠다고 해서 사용자 입장에서 배포 방식이 복잡해지는 것은 아닙니다.
기존에 사용하던 서비스명세서.yaml 파일을 그대로 활용하되, 단지 platform 항목의 값을 aws-app과 같은 단일 클러스터 정의에서 [aws-app, idc-app]처럼 여러 클러스터로 확장 정의해주기만 하면 됩니다.
즉, 서비스 개발자나 운영자는 별도의 복잡한 설정이나 추가 작업 없이 기존에 하던 방식 그대로 배포를 진행할 수 있으며, 단지 platform 항목만 조정하면 멀티 클러스터 배포가 자연스럽게 이뤄지도록 설계되어 있습니다.
예를 들어, AWS EKS인 aws-app, IDC Kubernetes인 idc-app, KC Kubernetes Engine인 kc-app 모두에 서비스를 멀티 배포하고 싶은 경우 설정 파일인 서비스명세서.yaml의 platform 항목을 다음과 같이 설정하여 배포하면 됩니다.
deploy:
platform: [aws-app, idc-app, kc-app] # 배포할 클러스터 정보
멀티 클러스터 서비스 통신
멀티 클러스터로 배포된 서비스의 통신 방식을 설계하면서, 저희는 아래 세 가지 포인트를 중심 방향으로 잡고 고민을 이어갔습니다.
- N개의 클러스터에 배포된 서비스의 트래픽 유량을 어떻게 조절할 것인가
- 멀티 클러스터로 배포됨에 따라, 불필요한 클러스터 크로스 통신으로 지연이 발생하지 않을까
- 특정 클러스터에 장애가 났거나 특정 클러스터로 트래픽을 몰고 싶을 때, 트래픽을 어떻게 빠르게 전환할 것인가
트래픽 분산: N개의 클러스터에 배포된 서비스의 트래픽 유량 조절
멀티 클러스터 환경에서는 하나의 서비스가 여러 클러스터에 동시에 배포되기 때문에 클러스터별로 얼마나 트래픽을 할당할지, 또는 어떤 기준으로 트래픽을 분산할지에 대한 전략이 필요했습니다.
기본적으로 하나의 서비스가 여러 클러스터에 분산 배포되어 있는 경우, 각 클러스터에 위치한 Load Balancer의 IP를 글로벌 단위의 단일 도메인으로 묶기 위해 GSLB(Global Server Load Balancing)를 사용하고 있습니다.
이 방식은 외부에서는 하나의 도메인으로 접근하되, 실제 트래픽은 GSLB를 통해 각 클러스터의 상태나 설정에 따라 적절히 분산되도록 구성되어 있습니다.
또한, 클러스터별로 유입되는 트래픽의 비율 역시 서비스 담당자 판단 아래 GSLB를 통해 정책적으로 조정할 수 있도록 설계해 각 클러스터별 리소스, 유관 서비스 현황에 따라 유동적으로 조절할 수 있도록 하였습니다.
GSLB를 사용하면, 다음과 같이 AWS EKS, IDC K8S, KC KE를 GSLB로 묶어서 운영 중 AWS EKS 플랫폼에 장애가 발생한 경우, AWS EKS ip가 헬스체크에 실패하면 GSLB는 해당 IP를 응답 대상에서 제외하고 남은 IDC K8S ip, KC KE ip에만 트래픽을 분산하게 됩니다.
이때 트래픽 비율도 기존 설정(40/30/30)이 아닌 남은 두 IP 간에 비율을 다시 50:50으로 재조정하여 분산하게 되며 사용자는 장애 상황을 인지하지 못한 채 정상적인 응답을 받을 수 있게 됩니다.
이처럼 GSLB를 통해 멀티 클러스터 서비스의 도메인을 서빙하면 플랫폼 단위의 장애가 발생하더라도 자동으로 대응할 수 있습니다.
불필요한 클러스터 크로스 통신 제어
GSLB로 여러 클러스터를 묶는 통신 설정을 하게 되면 불필요한 클러스터 크로스 통신이 발생할 수 있습니다.
가령, 서비스 A, B 모두 멀티 클러스터로 배포되어 있고 서비스 B가 서비스 A를 호출하고자 할 때, GSLB에 설정된 트래픽 유량 정책에 따라 다양한 클러스터에 존재하는 서비스 A로 흐르게 됩니다.
이는 동일한 클러스터에 서비스 A가 있음에도 다른 클러스터의 서비스 A를 호출하여 클러스터 간을 오가며 통신하는 경우도 발생할 수 있음을 의미합니다.
이러한 불필요한 네트워크 홉을 줄이고자 요청을 보내는 서비스와 대상 서비스가 동일한 클러스터 내에 위치하는 있는 경우 클러스터 내에서 통신하도록 하여 네트워크 홉 수를 줄이고 지연을 최소화하였습니다.
이를 구현하기 위해 사용한 기술은 바로 NodeLocal DNSCache의 rewrite 기능입니다.
📌 NodeLocal DNSCache 가 무엇인지 간략히 설명하자면,
NodeLocal DNSCache는 Kubernetes 환경에서 DNS 성능을 향상하고 안정성을 높이기 위해 사용되는 컴포넌트입니다.
기본적으로 각 노드마다 NodeLocal DNSCache라는 경량 DNS 캐시 서버가 배포되어 있으며, 클러스터 내의 DNS 요청을 CoreDNS로 직접 보내는 대신, 로컬 노드에서 먼저 응답을 처리함으로써 응답 속도를 개선하고 중앙 DNS에 대한 부하를 줄이는 역할을 합니다.
NodeLocal DNSCache의 rewrite 기능은 특정 도메인 요청을 다른 도메인으로 자동 변환해서 요청하게 해주는 것으로 우리가 의도하는 로컬 클러스터 우선 통신을 구현함에 있어 매우 적합했습니다.
멀티 클러스터로 서비스 배포 시, 동일 클러스터 우선 통신을 지향한다면 클러스터별 서브 도메인을 발급하고 있습니다.
가령, a-api.kakaopaysec.com이라는 도메인을 가진 A 서비스가 aws-app, kc-app에 동시 배포된다면 a-api-aws.kakaopaysec.com과 a-api-kc.kakaopaysec.com 이라는 도메인을 발급하는 것입니다.
예를 들어, b-api.kakaopaysec.com이라는 GSLB 기반의 글로벌 도메인을 사용하는 서비스가 있다고 가정해 봅니다.
다음과 같은 rewrite 규칙을 적용하여 해당 도메인 요청을 aws 클러스터 내부의 서비스로 우선 전달되도록 구성했습니다.
rewrite name exact b-api.kakaopaysec.com. b-api-aws.kakaopaysec.com.
이 규칙문이 무엇을 의미하는지 확인해 봅시다.
- rewrite
coreDNS 플러그인의 하나로 DNS 질의 내용을 수정하는 기능입니다.
A, AAAA, CNAME 같은 질의를 처리하기 전에 도메인 이름을 바꾸거나, 특정 패턴을 기반으로 질의를 재작성합니다.- name
어떤 부분을 재작성할 것인지를 명시하는 키워드로 name은 도메인 이름 자체를 바꾸겠다는 의미입니다.
이 외에도 class, type, answer 등 다양한 필드를 대상으로 재작성할 수 있지만, 저희가 바꾸고자 하는 것은 도메인 이름이기 때문에 name을 사용하였습니다.- exact
정확히 일치하는 도메인 이름에 대해서만 rewrite를 적용하겠다는 설정입니다.
정확히 b-api.kakaopaysec.com 도메인이 요청됐을 때만 b-api-aws.kakaopaysec.com으로 바꿔서 DNS 질의를 수행합니다.
NodeLocal DNSCache의 rewrite 기능을 자칫 HTTP 리다이렉트와 같은 개념으로 볼 수 있는데요.
NodeLocal DNSCache의 rewrite는 클라이언트를 다른 도메인으로 보내는 게 아니고, 내부적으로 DNS 이름 자체를 바꿔서 쿼리를 다시 보내는 것입니다.
해당 rewrite 규칙이 적용된 도메인을 호출하면 실제로 어떻게 동작하는 확인 해봅시다.
> sudo tcpdump —n -i any udp port 53
// 클라이언트(10.1.2.3)가 NodeLocal DNSCache(169.254.25.10)에 b-api.kakaopaysec.com 도메인에 대한 DNS 요청
15:41:05.953554 IP 10.1.2.3.60995 > 169.254.25.10.domain: 46264+ [lau] A? b-api.kakaopaysec.com. (64)
// NodeLocal DNSCache(10.123.45.67, 로컬 노드 IP)가 외부 DNS 서버(10.12.34.56)에 rewrite 규칙이 적용된 a-api-aws.kakaopaysec.com 도메인에 대한 실제 DNS 질의 요청
15:41:05.953863 IP 10.123.45.67.38664 > 10.12.34.56. domain: 20215+ [1au] A? a-api-aws.kakaopaysec.com. (71)
// 외부 DNS(10.12.34.56)가 NodeLocal DNSCache에게 a-api-aws.kakaopaysec.com의 IP 10.11.22.33를 응답
15:41:05.954349 IP 10.12.34.56.domain > 10.123.45.67.38664:20215* 1/0/1 A 10.11.22.33 (87)
// NodeLocal DNSCache가 원래 클라이언트(10.1.2.3)에게 클라이언트가 원래 요청했던 도메인(b-api.kakaopaysec.com)의 응답으로 rewrite 된 도메인(a-api-aws.kakaopaysec.com)의 IP 10.11.22.33를 응답
15:41:05.954519 IP 169.254.25.10.domain > 10.1.2.3.60995: 46264* 1/0/1 A 10.11.22.33 (115)
즉, 결과적으로는 사용자는 단일 서비스 도메인을 질의하지만 자동으로 클러스터 서비스 도메인의 IP를 바꿔서 응답하는 효과를 볼 수 있습니다.
이 방식은 클러스터마다 다르게 적용할 수 있으며, 각 클러스터에 맞는 클러스터 도메인을 설정하는 방식으로 확장성 있게 구성할 수 있습니다.
예를 들어, idc 클러스터에서는 b-api-idc.kakaopaysec.com으로 rewrite 하는 식으로 적용할 수 있습니다.
rewrite name exact b-api.kakaopaysec.com. b-api-idc.kakaopaysec.com.
저희는 NodeLocal DNSCache를 통해서 멀티 클러스터 환경에서의 도메인 기반 트래픽 라우팅을 구현했지만 NodeLocal DNSCache 외에도 Istio나 eBPF 기반의 네트워크 솔루션도 사용할 수도 있습니다.
내부적으로도 설계 단계에서 이러한 다른 옵션들도 검토했지만 몇 가지 이유로 NodeLocal DNSCache를 우선 선택하게 되었습니다.
우선 Istio의 경우, 클러스터 간 트래픽을 제어하는 데 강력한 기능들을 제공하지만 저희 환경은 Istio를 기본적으로 사용하지 않는 구조기에 먼저 모든 클러스터에 Istio를 신규로 설치해야만 제대로 된 라우팅 구성이 가능했습니다.
또한, 지금은 아니지만 당시 IDC 클러스터의 Kubernetes 버전이 Istio를 지원하지 않아 버전 업그레이드와 구성 변경 등 여러 선행 작업이 필요한 상황이었습니다.
뿐만 아니라 Istio 도입 시 필연적으로 sidecar proxy가 함께 배포되어 리소스를 점유하게 되고 Ingress 구성에도 변경이 필요하였습니다.
따라서, 멀티 클러스터 서비스를 빠르게 제공해야 하는 상황에서는 적용 비용 대비 효과가 크지 않다는 판단을 내리게 되었습니다.
Istio가 제공하는 다양한 기능은 장기적인 측면에서 분명 매력적이지만 멀티 클러스터 서비스 도입이라는 현재 목표에는 불필요한 구조 복잡성과 리스크를 동반할 수 있다는 점에서 제외하게 되었습니다.
또한 저희가 기존에 Cilium을 쓰고 있었기 때문에 Cilium eBPF도 생각해볼 수 있는 선택지였는데요.
eBPF는 리눅스 커널 수준에서 네트워크 트래픽을 정밀하게 제어할 수 있는 강력한 기능을 제공하며 특정 조건에 맞는 패킷을 선별하고 분기 처리하는 데에는 탁월한 성능을 보여줍니다.
하지만 우리가 원하는 것은 단순한 패킷 필터링이 아니라 같은 서비스가 여러 클러스터에 존재하는 환경에서 요청의 목적에 따라 어느 클러스터의 서비스를 바라볼지를 결정하는 고수준의 라우팅입니다.
이러한 목적에는 패킷 단위 제어만으로는 부족했고 클러스터 컨텍스트와 서비스 식별 그리고 트래픽 흐름을 제어하는 정책 기반 분기가 필요했습니다.
eBPF로 이러한 구조를 구현하는 것은 기술적으로는 가능하지만 정책 관리, 클러스터 식별, 서비스 맵핑 로직까지 모두 별도로 구현해야 하며 운영과 유지보수 난이도가 높고 Kubernetes 환경과의 자연스러운 연동도 어렵다고 판단하였습니다.
반면, NodeLocal DNSCache를 활용하는 방식은 Kubernetes 네이티브 환경 내에서 DNS 요청을 재작성(rewrite)하는 방식으로 클러스터 간 트래픽 흐름을 단순하고 명확하게 제어할 수 있고 추가적인 인프라 도입 없이도 빠르게 적용 가능하다는 점이 큰 메리트였습니다.
또한, 운영 부담 또한 크지 않아 우리가 원하는 멀티 클러스터 라우팅 구조를 안정적으로 구현하는 데에 훨씬 현실적인 선택지라고 판단하였습니다.
목적에 따른 빠른 트래픽 전환
여기까지 멀티 클러스터에 분산 배포된 서비스 간의 통신을 효율적으로 구성하는 방법에 대해 살펴보았습니다.
이렇게 통신 구성이 잘 되었더라도, 분산 배포된 여러 클러스터 중 한 클러스터에 하나에 장애가 발생한다면 해당 클러스터의 서비스를 호출하는 통신 구간에 연쇄적 이슈가 발생합니다.
또한 경우에 따라 서비스 담당자가 트래픽을 특정 클러스터로 의도적으로 유도하거나 테스트 목적의 비율 조정을 원할 때도 있습니다.
이러한 상황들을 고려한 추가적인 대응 전략이 필요했습니다.
상황별 트래픽 전환 역시 앞서 언급한 NodeLocal DNSCache의 rewrite 기능을 활용해 풀어냈습니다.
기존 NodeLocal DNSCache의 rewrite 설정을 변경함으로써, 특정 클러스터의 서비스 트래픽을 원하는 클러스터로 유도하는 것입니다.
장애 시 빠른 대응을 위해 서비스 담당자가 직접 손쉽게 라우팅 경로를 변경할 수 있도록 긴급 라우팅 변경 잡을 제공하고 있습니다.
위의 아키텍처는 서비스 A에 대한 트래픽을 모두 IDC의 서비스 A가 받게끔 하고 싶을 때, 긴급 라우팅 변경 작업을 통해 트래픽을 전환하는 모습입니다.
KC에 떠있는 서비스 B가 a-api.kakaopaysec.com 도메인으로 서비스 A를 호출 시 KC에 떠 있는 서비스 A로 연결되었지만 긴급 전환 잡을 통해 rewrite 설정을 변경하여 IDC Kubernetes에 배포된 서비스 A를 호출하도록 변경한 것입니다.
이와 같이 긴급전환 잡을 사용 하면 목적에 맞는 라우팅 전환뿐 아니라 클러스터 단위의 장애가 발생했을 때도 최소한의 조치로 빠르게 대응할 수 있게끔 구성하였습니다.
대고객 서비스 트래픽 제어
저희 서비스 인프라는 외부 클라이언트 트래픽을 처리하는 DMZ존이라 불리는 Public 네트워크 영역과 내부 서비스가 배포되는 APP존이라 칭하는 Private 네트워크 영역으로 이루어져 있습니다.
대고객 서비스는 트래픽이 보안 상 DMZ 존을 통해 들어오도록 설계되어 있고 AWS에서는 해당 영역의 진입점으로 ALB(Application Load Balancer)를 사용합니다.
(* DMZ 존에서는 외부 인터넷 트래픽을 수신하고 보안 제어를 적용하기 위해, WAF와 연동이 가능한 ALB를 사용하고 있습니다.
이를 통해 외부 요청에 대한 정밀한 트래픽 제어와 보안 정책 적용이 가능하며, 서비스 앞단에서의 유연한 라우팅과 스케일링도 함께 제공할 수 있습니다.)
일반적으로 카카오페이증권의 모든 서비스는 이중화된 데이터센터 구조 위에서 GSLB(Global Server Load Balancer)를 활용해 트래픽을 분산시키고, 장애 시 자동 우회가 가능한 구조로 운영되고 있습니다.
멀티 클러스터 서비스의 도메인에 대해서도 GSLB를 사용한 방식을 고려하였고 앞서 말했듯 내부 서비스의 경우, 각 클러스터에 위치한 서비스의 엔드포인트를 AWS NLB의 IP 또는 IDC 고정 IP를 기반으로 구성한 뒤 이를 GSLB에 등록하여 하나의 공통 도메인으로 트래픽을 분산시키는 구조를 사용해왔습니다.
하지만, 대고객 서비스는 ALB를 사용하기 때문에 고정 IP를 사용할 수 있는 IDC 환경과 달리, AWS 환경의 서비스는 고정된 IP를 사용할 수 없어 AWS에 서비스와 IDC의 서비스를 GSLB를 사용해 하나의 도메인으로 묶어 구성하는 것은 불가능했습니다.
(* GSLB는 일반적으로 IP 기반으로만 엔드포인트를 등록할 수 있으며, CNAME의 경우 등록 자체가 안되고 등록해도 트래픽 제어나 헬스 체크가 제대로 작동하지 않기 때문에 대부분의 실무에서는 사용하지 않습니다.)
따라서, ALB를 사용하는 AWS와 IDC 2개의 데이터 센터로 안전하게 트래픽을 분산 통신되게 하기 위한 더 나은 방법을 고민해야 했습니다.
또한 외부 인터넷망에서 들어오는 트래픽은 DMZ 존에 직접 접근하기 때문에 클러스터 내부의 DNS 호출을 제어하는 NodeLocal DNSCache로 제어할 수 있는 범위가 아닙니다.
위 그림은 내부 서비스와 대고객 서비스의 트래픽 흐름 차이를 비교한 구조도입니다.
이를 통해 대고객 서비스에 왜 내부 서비스 멀티 클러스터 방식이 적용되지 않았는지를 명확히 확인하실 수 있습니다.
이 문제를 해결하기 위해, 멀티 클러스터 내부 통신에서 사용했던 접근 방식과 동일하게 각 DMZ 존에 배포된 대고객 서비스별로 클러스터 전용 도메인을 발급하였습니다.
그리고 Config 서버를 구성하여 Config 서버에 정의된 라우팅 규칙에 따라 어느 클러스터의 서비스를 호출할지 결정되도록 구성하였습니다.
실제 저희의 config 서버는 다음과 같이 결과를 내려주며 플랫폼 별로 weight를 지정하여 트래픽 유량을 조절하고 있습니다.
"server_info": {
"rest": {
"ace": [
{
"domain": "https://api-idc.kakaopaysec.com",
"weight": "0.9"
},
{
"domain": "https://api-aws.kakaopaysec.com",
"weight": "0.1"
}
],
"redbull": [
{
"domain": "https://api2-idc.kakaopaysec.com",
"weight": "0.9"
},
{
"domain": "https://api2-aws.kakaopaysec.com",
"weight": "0.1"
}
]
}
}
이 방식은 외부 트래픽에 대해 직접적인 DNS 제어가 불가능한 구조적인 제약을 극복하고 서비스 운영자가 의도한 트래픽 흐름을 반영할 수 있다는 점 그리고 장애 발생 시에도 빠르게 다른 클러스터로 전환할 수 있는 유연함을 제공합니다.
또한 버전별 트래픽 분기, feature flag 관리 등 서비스 레벨에서의 세밀한 제어도 가능하게 해 준다는 장점을 가지고 있습니다.
우리가 선택한 Config 서버 기반의 라우팅 방식은 클러스터별 도메인을 동적으로 구성하고, 서비스 단에서 라우팅 결정을 애플리케이션 수준에서 제어할 수 있습니다.
하지만 이 방식은 애플리케이션에 어느 정도 동적 로직과 Config 서버 연동이 필요하다는 점에서 클라우드 인프라의 기능을 활용한 더 간단한 대안도 고려해 보았습니다.
그중 하나가 바로 AWS Global Accelerator(GA)입니다.
GA는 AWS가 제공하는 글로벌 네트워크 기반 트래픽 라우팅 서비스로 ALB와 같은 AWS 리소스 앞단에 고정된 Anycast IP를 제공해 줌으로 ALB가 직접 제공하지 못하는 고정 IP 형태의 엔드포인트를 생성해 줍니다.
결과적으로 GA를 통해 고정 IP를 얻을 수 있게 됨으로써 ALB를 사용하는 멀티 클러스터 환경도 GSLB를 통한 트래픽 분산과 장애 대응이 가능해집니다.
또한 GA는 단순히 고정 IP를 제공하는 것에 그치지 않고 내부적으로 ALB의 상태 헬스체크를 수행하고 문제가 발생했을 경우 장애가 없는 리전의 ALB로 자동으로 트래픽을 전환(failover) 시켜주는 기능도 제공합니다.
이러한 구조는 GSLB와 조합 시, 고정 IP 기반의 헬스 체크 + 자동 우회 + 글로벌 트래픽 최적화까지 가능하게 되는 것입니다.
아쉽게도 현재는 GA 자체에 대한 운영 안정성 및 장애 상황 대응에 대한 실질적인 경험이 부족한 부분이 있어 적용 타이밍을 신중히 조율하고 있는 중입니다.
현재는 우선 Config 기반 라우팅 구조를 유지하고 있지만 GA 도입에 대해서는 내부적으로 충분히 필요성을 인지하고 있으며 안정적인 운영 체계를 갖춘 후 가까운 시일 내에 점진적으로 적용할 예정입니다.
멀티 클러스터의 효용성
이번 멀티 클러스터 구조 설계를 통해 고가용성과 유연한 운영을 실현할 수 있는 클라우드 서비스 환경의 기반을 마련했습니다.
단순히 서비스를 여러 클러스터에 나눠 배포하는 것을 넘어서 트래픽을 효율적으로 분산하고 장애 상황에서도 빠르게 대응할 수 있는 구조를 갖추게 된 것이 가장 큰 의미였습니다.
무엇보다도 이 구조는 기존 배포 흐름에서 크게 변경이 필요하지 않기에 서비스 개발자와 운영자 입장에서 쉽게 시도해 볼 수 있고 실제로 여러 서비스들을 빠르게 확산 적용시켜 볼 수 있었다는 점이 의미 있었습니다.
실제 멀티 클러스터 배포 후 DMZ 존의 서비스 A가 APP 존의 서비스 B를 호출 시, 어떻게 트래픽이 흘러가는지를 전체적인 구조로 보여드리며 멀티 클러스터를 마무리하겠습니다.
하이브리드 클러스터: n개의 플랫폼이 하나의 클러스터에
멀티 클러스터 구조는 기본적으로 서비스를 여러 클러스터에 나누어 배포하고 각 클러스터에서 독립적으로 트래픽을 처리하는 방식입니다.
이 구조는 stateless 한 서비스에 대해서는 트래픽 분산과 장애 대응 측면에서 효과적일 수 있지만 서비스 자체가 stateful 한 특성을 가질 경우에는 상황이 완전히 달라집니다.
대표적인 예로, Jenkins와 같은 CI/CD 서비스는 단순한 웹 애플리케이션이 아니라 빌드 이력, 파이프라인 설정, 플러그인 구성, 워크스페이스 데이터 등 다양한 상태를 내부에 유지하고 관리하는 stateful 서비스입니다.
이러한 특성 때문에 Jenkins를 멀티 클러스터 구조에서 각각 독립적으로 배포하게 되면 각 클러스터의 젠킨스 간 상태가 분리되고 동일한 작업이라도 실행 결과나 설정이 달라질 수 있으며 심지어 Git 리포지토리나 webhook 처리 등에서도 충돌이 발생할 수 있습니다.
클러스터 장애가 발생했을 경우를 살펴보자면, 앞서 멀티 클러스터 구조에서는 GSLB를 통해 각 클러스터의 서비스를 하나의 도메인으로 묶는다고 하였습니다.
GSLB는 연결된 IP로 주기적으로 헬스체크를 하고 특정 클러스터에 장애가 발생하여 헬스체크에 실패할 경우 해당 클러스터의 IP를 자동으로 제외시키기 때문에 “한쪽 클러스터에 장애가 나더라도 문제없이 운영할 수 있지 않을까?”라고 생각할 수 있습니다.
하지만 Jenkins와 같이 상태를 내부에 보유하는 서비스는 얘기가 다릅니다.
GSLB는 단순히 트래픽을 살아 있는 인스턴스로 넘겨줄 뿐 실행 중이던 작업이나 저장된 빌드 이력, 설정 상태까지 함께 넘겨주지는 않기 때문에 두 클러스터에 각각 띄운 Jenkins 인스턴스를 GSLB로 묶는다고 해서 하나의 일관된 Jenkins 환경을 제공할 수는 없습니다.
가령, paysec A라는 서비스가 우연히 IDC K8S에 떠있는 젠킨스에서만 빌드가 수행된 경우 AWK EKS에 떠있는 젠킨스에는 빌드 수행 이력이 존재하지 않게 되고 paysec A 서비스 담당자가 빌드 이력을 보려고 할 경우 확인할 수 있는 정보가 없는 것입니다.
이러한 배경 속에서 우리는 한 가지 아이디어에 도달하게 됩니다.
“그렇다면 여러 플랫폼을 하나처럼 묶어서, 마치 하나의 클러스터처럼 운영하면 어떨까?” 그렇게 시작된 것이 바로 하이브리드 클러스터였습니다.
여러 플랫폼에 존재하는 노드들을 하나의 논리적 클러스터로 묶어 stateful 서비스를 단일한 클러스터 수준에서 운영하되 물리적으로는 여러 플랫폼에 걸쳐 분산된 형태로 구성한 것입니다.
이 방식은 서로 다른 플랫폼에 분산된 노드들이 단일한 클러스터로 동작하기 때문에 특정 클라우드 플랫폼에 장애가 나더라도 다른 플랫폼의 노드가 그대로 클러스터 운영을 이어받을 수 있고 서비스의 상태 역시 단일 클러스터 내에서 관리되므로 장애 전환 과정에서의 복잡성과 리스크가 현저히 줄어듭니다.
하이브리드 클러스터는 stateful 서비스의 고가용성과 플랫폼 단위 장애 시에도 서비스가 흔들리지 않는 운영 안정성을 확보하는 것뿐만 아니라 클러스터 수 또한 줄일 수 있다는 점에서 매력적인 아이디어였습니다.
결국 저희는 단순한 배포 전략의 변화가 아니라, 클러스터 자체의 진화를 꾀하였습니다.
아키텍처
하이브리드 클러스터는 AWS의 Auto Scaling Group 노드, KC VM 노드, IDC 물리 장비 노드 서로 다른 환경의 자원들을 하나의 논리적 Kubernetes 클러스터로 묶은 형태입니다.
하이브리드 클러스터의 효용성: Worker Nodes
실제 저희가 적용하여 사용 중인 예시를 함께 살펴보시죠.
기존에 ArgoCD, Jenkins 등 CI/CD 관련 서비스들 그리고 prometheus 등 저희 DevOps팀에서 담당하는 서비스들을 위한 idc-mgmt 클러스터를 운영하고 있었습니다.
하지만 서비스 규모가 점점 커지고 배포 대상 애플리케이션 수가 증가함에 따라 ArgoCD에 등록된 앱 수도 지속적으로 늘어났고 Jenkins 역시 빌드/배포하는 서비스가 확장됨에 따라 리소스 수요가 빠르게 증가하고 있었습니다.
IDC 환경에서는 이러한 리소스 증가에 대응하기 위해 직접 장비를 구매하고 클러스터에 물리 노드를 추가해야 하는 번거로움이 있기에 빠르게 대응하기 어려운 구조였습니다.
반면, KC나 AWS와 같은 public Cloud 는 필요한 만큼 VM 자원을 즉시 확보할 수 있고 운영 중인 하이브리드 클러스터에 해당 VM을 노드로 붙이기만 하면 기존 구조를 그대로 유지한 채 손쉽게 리소스를 확장할 수 있다는 장점이 있습니다.
그렇다면 KC의 Kubernetes Engine을 사용할 수도 있지 않느냐는 생각이 드실 수도 있는데요.
만약 KC의 Kubernetes Engine로 이전하고자 한다면 새로운 클러스터를 KC KE 위에 별도로 구성하고, 배포 환경과 연동 설정을 다시 구축해야 했습니다.
그러나 하이브리드 클러스터 방식을 사용한다면 기존 클러스터에 KC VM 노드를 추가하는 것만으로 실질적인 클라우드 이전이 가능해졌습니다.
Jenkins, ArgoCD 등과 같은 서비스가 뜰 수 있는 노드가 늘어남과 동시 다음과 같이 IDC 물리 장비 노드, KC VM 플랫폼으로 분산되어 뜨게되는 것입니다.
따라서, idc-mgmt 클러스터를 하이브리드로 탈바꿈함으로써 별도의 클러스터를 추가로 만들거나 복잡한 이관 과정을 거치지 않고도 클러스터 리소스 증설 필요 상황을 자연스럽게 해결할 수 있게 되었습니다.
또한, 하이브리드 클러스터로 전환함으로써 만일 idc 장비 혹은 네트워크에 이슈가 발생할 경우 해당 플랫폼의 노드는 NotReady가 됨으로 자동으로 정상적인 플랫폼 노드로 이전되어 운영됩니다.
즉, 사용자가 직접 이관할 필요도 없이 self healing 되며 서비스 운영 안정성이 올라가는 것입니다.
이 방식은 클러스터 운영 관점에서는 매우 큰 전환점을 의미합니다.
이전에는 클라우드 플랫폼이 하나 추가될 때마다 새로운 클러스터를 구성하고 그에 따른 모니터링, 설정, 배포 파이프라인 등 운영 전반의 복잡도가 함께 증가하는 구조였습니다.
하지만 하이브리드 클러스터는 다양한 플랫폼의 노드를 하나의 클러스터에 통합함으로써 클러스터 단위의 관리 부담에서 벗어나고 플랫폼 단위 장애 시에도 서비스는 자동으로 정상의 다른 플랫폼 노드로 이전됨으로써 단순한 편의를 넘어 실질적인 안정성과 복구 속도를 향상시킬 수 있는 운영상의 큰 이점을 가져다주었습니다.
하이브리드 클러스터의 효용성: Master Nodes
지금까지 하이브리드 클러스터의 장점을 워커 노드의 유연한 확장성 측면에서 다루었습니다.
하지만 우리가 실제로 하이브리드 클러스터 구조를 설계하면서 더욱 중요하게 여겼던 부분은 Kubernetes의 마스터 노드까지도 여러 클라우드 플랫폼으로 분산 구성한다는 점이었습니다.
Kubernetes는 클러스터의 상태를 관리하고 워커 노드에 작업을 분배하는 Control Plane 구조를 갖고 있으며 크게 다음과 같은 핵심 컴포넌트로 구성되어 있습니다.
• kube-apiserver
모든 클러스터 요청의 진입점, 상태 확인 및 리소스 조작 수행
• etcd
클러스터의 전체 상태를 저장하는 분산 key-value 저장소
• kube-controller-manager
ReplicaSet, Deployment 등 클러스터 상태를 실제로 유지하는 컨트롤러
• kube-scheduler
워크로드를 어떤 워커 노드에 배치할지 결정
이 중 특히 etcd와 kube-apiserver는 클러스터의 생명줄과도 같은 존재로 마스터 노드가 완전히 장애가 나면 워커 노드가 살아 있어도 클러스터는 기능을 잃게 됩니다.
기존 클러스터 구조에서는 이 마스터 노드들이 특정 클라우드(AWS, KC, IDC)나 특정 존에 묶여 있어 만약 해당 플랫폼이나 존에 장애가 발생할 경우 Control Plane 이 통째로 불능 상태에 빠지고 클러스터 자체가 조작 불가능해지는 심각한 문제가 발생할 수밖에 없습니다.
즉, 기존의 클러스터는 클라우드 서비스 그리고 존에 강한 디펜던시를 가지고 있었음을 의미합니다.
이를 해결하기 위해 Control Plane의 마스터 노드 또한 하이브리드 구조로 설계하였습니다.
etcd 클러스터와 각종 제어 컴포넌트들이 AWS, IDC, KC 등 여러 플랫폼과 존에 걸쳐 분산 배치되도록 구성하여 플랫폼 혹은 존 단위 장애에도 끄떡없는 철통 클러스터를 구축하였습니다.
이러한 구조 덕분에, 만약 특정 클라우드 플랫폼에 장애가 발생하더라도 다른 플랫폼의 살아 있는 마스터 노드들이 있기에 해당 마스터 노드만으로도 클러스터의 제어 플레인이 정상적으로 동작할 수 있습니다.
etcd와 같은 핵심 컴포넌트는 다수결(quorum) 기반으로 동작하기 때문에 일부 마스터 노드가 내려가더라도 과반수 이상의 노드가 살아 있으면 클러스터는 문제없이 운영되기 때문입니다.
단일 클라우드 기반 클러스터는 결국 해당 플랫폼에 장애가 나면 전체가 흔들릴 수밖에 없습니다.
하이브리드 클러스터는 이 구조적 한계에서 벗어나 플랫폼 종속을 줄이고 장애 상황에서도 멈추지 않는 탄력적인 클러스터 운영을 가능하게 만들었습니다.
성능 테스트
1. 마스터 노드 개수에 따른 성능 비교
하이브리드 클러스터에는 다양한 플랫폼의 노드를 마스터 노드로 연결할 수 있지만 etcd는 Raft 합의 알고리즘을 기반으로 다수결 방식으로 동작하기 대문에 클러스터 성능을 고려한 적절한 마스터 노드의 개수 선정이 필요합니다.
노드 수가 너무 많을 경우에는 Raft 내부의 리더 선출과 동기화 비용이 오히려 성능 저하로 이어질 수도 있기 때문입니다.
따라서, 노드 스펙 개수별 성능 측정 지표를 바탕으로 몇 개의 마스터 노드가 적합한지 선정하였습니다.
Node Type | Test | Master 3개 | Master 5개 | Master 7개 | Master 9개 |
---|---|---|---|---|---|
m4.large | Write to leader | 2200 | 2069 | 1999 | 1928 |
Write to all members | 5815 | 7396 | 7778 | 7406 | |
Linearized concurrent read requests | 9100 | 15540 | 20655 | 25646 | |
Read requests with sequential consistency only | 13312 | 22756 | 31305 | 40723 | |
m4.2xlarge | Write to leader | 12680 | 12181 | 11586 | 11013 |
Write to all members | 14757 | 25419 | 23731 | 21789 | |
Linearized concurrent read requests | 38409 | 54918 | 73879 | 81548 | |
Read requests with sequential consistency only | 59653 | 95124 | 115605 | 122488 |
(* 단위 : Number of control plane nodes/operations per sec)
실험 결과를 분석해 보면, 읽기 성능은 마스터 노드 수가 증가할수록 선형에 가깝게 향상되는 반면 쓰기 성능은 오히려 노드 수가 늘어날수록 소폭 하락하는 경향을 확인할 수 있습니다.
이는 etcd가 Raft 합의 알고리즘을 기반으로 동작하기 때문에 쓰기 연산 시에는 quorum을 만족하기 위한 다수 노드 간의 통신 지연 성능 병목으로 해석됩니다.
반면, 읽기 요청(특히 sequential consistency 기반)은 리더 노드만으로 처리되거나 일부 분산이 가능해 전체 노드 수가 많아질수록 읽기 요청 분산에 유리하게 작용한 것으로 보입니다.
이러한 결과를 바탕으로 읽기 위주의 워크로드가 많고 고가용성이 중요한 클러스터에는 5~7개 마스터 구성이 성능적으로도 유리하며 반대로 쓰기 지연을 최대한 줄여야 하는 환경에서는 3개 정도의 컴팩트한 구성이 효율적일 수 있다는 인사이트를 도출할 수 있었습니다.
따라서, 하이브리드 클러스터에 마스터 노드를 구성할 때 각 플랫폼으로 분산 구성하되 워크로드 특성과 네트워크 지연, 운영 복잡도 등을 함께 고려하여 최적의 개수를 선정하였습니다.
2. 서로 다른 플랫폼 노드 간 통신 속도 테스트
하이브리드 클러스터와 멀티 클러스터 모두는 서로 다른 플랫폼(AWS, IDC, KC)의 노드들 위에 서비스들이 올라가고 서로 통신하는 구조입니다.
따라서, 노드 간 네트워크 대역이나 라우팅 경로가 상이하여 플랫폼별 통신 성능 차이가 발생할 수 있다는 점도 함께 고려해야 했습니다.
특히 하이브리드 클러스터의 경우 etcd나 kube-apiserver와 같은 제어 트래픽은 마스터 노드 간의 안정적인 통신을 전제로 하므로 노드 간 통신 지연이나 패킷 손실률이 전체 클러스터 안정성에 영향을 줄 수 있다고 판단했습니다.
이에 따라, 노드 간 통신의 Client, Server, DB 플랫폼 위치 조합별 네트워크 TPS 성능을 측정하고 비교하는 실험도 진행하였습니다.
Client | Server | DB | 통신 방법 | TPS | 최고 TPS | latency |
---|---|---|---|---|---|---|
DC | DC | DC | server(도메인) → DB(node) | 2,198.7 | 2,481 | 90.01 ms |
server(도메인) → DB(클러스터 도메인) | 2,258.1 | 2,430 | 87.56 ms | |||
DC | DC | AWS | server(도메인) → DB(node) | 746.8 | 787 | 264.96 ms |
DC | AWS | DC | server(도메인) → DB(node) | 751.5 | 932 | 262.77 ms |
DC | AWS | AWS | server(도메인) → DB(node) | 2,312.7 | 2,445 | 85.57 ms |
AWS | server(도메인) → DB(클러스터 도메인) | 2,342.2 | 2,479 | 84.49 ms | ||
AWS | DC | DC | server(도메인) → DB(node) | 2,224.3 | 2,507 | 89.22 ms |
DC | server(도메인) → DB(클러스터 도메인) | 2,321.7 | 2,539 | 85.25 ms | ||
AWS | DC | AWS | server(도메인) → DB(node) | 703.0 | 858 | 281.38 ms |
AWS | AWS | DC | server(도메인) → DB(node) | 717.9 | 872 | 274.73 ms |
AWS | AWS | AWS | server(도메인) → DB(node) | 2,312.8 | 2,423 | 85.58 ms |
AWS | server(도메인) → DB(클러스터 도메인) | 2,433.5 | 2,588 | 81.31 ms |
성능 측정 결과, 클라이언트–서버–DB가 모두 동일한 플랫폼에 위치한 경우 가장 높은 TPS와 가장 낮은 지연 시간을 기록했습니다.
특히 서버와 DB가 동일한 위치에 있을 때 성능이 안정적으로 유지되었으며, 클라이언트가 다른 플랫폼에 있더라도 성능 저하 폭은 크지 않았습니다.
반면, DB가 서버와 다른 플랫폼에 위치한 경우에는 TPS가 1/3 수준으로 급감하고 평균 응답 시간도 3배 이상 증가하는 등 데이터 계층의 위치가 전체 성능에 가장 큰 영향을 미친다는 점이 확인되었습니다.
이러한 실험 결과를 바탕으로, 추후 서비스를 운영하는 클러스터를 하이브리드 클러스터로 전환할 때는 지연이 발생이 크리티컬한 서비스의 경우 사용하는 DB를 가능한 동일 클러스터 내에 배치되도록 운영 원칙을 마련할 예정입니다.
클러스터 간 네트워크 지연이 전체 TPS와 응답 성능에 미치는 영향이 상당하다는 점이 확인된 만큼 데이터 계층의 위치가 서비스 품질 유지에 있어 핵심적인 요소라는 인사이트를 얻을 수 있었습니다.
앞으로 더 나아갈 방향
현재의 멀티 클러스터 구조는 사용자가 서비스명세서.yaml의 platform 항목에 [aws-app, idc-app]과 같이 구체적인 클러스터를 명시해야만 분산 배포가 가능하도록 되어 있습니다.
이는 서비스 분산 환경의 장점을 제공하지만 사용자가 클러스터 종류에 대한 이해와 어느 클러스터로 올릴지에 대한 선택이 필요하다는 점에서 아쉬움이 있었습니다.
하지만 멀티 클러스터 기반 환경이 점차 자리 잡으면서, 클러스터 선택에 대한 부담 없이 단순히 platform: multi 한 줄만 명시하면 시스템이 알아서 클러스터 상태와 서비스 특성을 기반으로 가장 적절한 배포 위치를 자동으로 판단하여 분산 배포하는 구조를 목표로 하고 있습니다.
이렇게 되면 사용자는 더 이상 클러스터와 인프라 구조에 얽매이지 않고 오직 서비스 그 자체에만 집중할 수 있게 됩니다.
우리가 만들고자 하는 멀티 클러스터 환경은 단순히 기술적인 분산 구조를 넘어 플랫폼 사용자 경험의 단순화와 서비스 품질 향상을 위한 발걸음입니다.
하이브리드 클러스터는 현재 stateful 서비스와 CI/CD, monitoring 등 DevOps 담당의 서비스가 운영되는 management 클러스터에 우선 적용되어 그 유연성과 실효성을 검증하고 있는 단계입니다.
앞으로는 더 다양한 클러스터까지 이 구조를 단계적으로 확장해 나갈 예정이며, 궁극에는 하이브리드 클러스터들에 서비스를 멀티 클러스터로 운영하도록 하여 고가용성에 고가용성을 더한 구조로 나아가고자 합니다.
이를 통해 플랫폼 간 경계를 유연하게 넘나들 수 있는 민첩함과 클라우드 종속성에서 벗어난 진정한 고가용성 인프라 환경을 구축하는 것을 목표로 하고 있습니다.
이러한 방향이 실현된다면, 누구나 손쉽게 고가용성 인프라를 활용할 수 있는 유연하고 안정적인 서비스 생태계로 나아갈 수 있을 것이라 기대하고 있습니다.
마치며
지금까지 클라우드 환경에서 클러스터 기반 서비스를 운영하면서 마주했던 고가용성에 대한 고민과 그 과정에서 저희 DevOps가 시도하고 있는 다양한 접근 방식에 대해 공유해 드렸습니다.
단순히 장애를 대비하는 것을 넘어서 서비스의 연속성과 운영 효율성을 함께 고려하는 구조가 무엇인지를 계속해서 고민한 결과물입니다.
이 포스팅이 클러스터 운영에 있어 비슷한 고민을 하고 계신 분들께, 특히 다수의 클러스터를 어떻게 활용하고 발전시켜야 할지 고민하는 클라우드 인프라 운영자분들께 작은 인사이트가 되었기를 바랍니다.
올해 카카오페이증권의 SLO 목표는 99.99% 가용률입니다. 이 숫자는 단지 SLA 문서에 적힌 목표치가 아니라, 모든 팀이 같은 방향을 보고 함께 움직이겠다는 약속이자 기준선입니다.
이번 멀티 & 하이브리드 클러스터 구조는 그 약속을 현실로 만들기 위한 우리 DevOps 팀의 첫걸음이었고, 앞으로도 각 팀이 자신의 위치에서 할 수 있는 최선의 노력을 다해 더 안정적이고 신뢰받는 금융 서비스를 만들어가고자 합니다.
앞으로도 더 단단한 인프라와 더 나은 서비스 제공을 위한 카카오페이증권의 여정에 지속적인 관심과 응원 부탁드립니다.