Tech

서버리스 러버의 쿠버네티스 구축기(EKS & Pulumi)

May 20, 2025
-
Backend Developer
-
VIctor

안녕하세요 저는 개발자 커리어를 시작한 21년도 부터 쭉 5년 동안 서버리스를 애용 해왔었는데요. 서버리스의 대척점으로 볼 수 있는 쿠버네티스 환경을 구축하는 기회를 얻게 되었습니다. 이번 아티클에서는 k8s가 Kubernetes의 축약어 인지도 몰랐던 쿠버네티스 초보가 IaC 도구인 Pulumi를 사용해서 EKS (Elastic Kubernetes Service) 환경을 구축하기 까지의 이야기를 소개 하려고 합니다. 해당 아티클에서는 쿠베네티스의 기본적인 이해를 가지고 있다는 전제하에 진행 됩니다.

쿠버네티스에 대한 선행 지식이 필요하신 분은 Monday9pm 모임에 Hadam Cho님께서 작성 하셨던 쿠버네티스 톺아보기 Part1, Part2, Part3 시리즈를 읽어 보시는 것을 추천 드립니다.

목차

  • 왜 쿠버네티스?
  • 고마워요 EKS!
  • 쿠버네티스는 괴로워?
  • 고마워요 Pulumi!
  • 마무리

왜 쿠버네티스?

지금 까지 제가 작성한 개발팀 기술 블로그를 읽어 본 분이라면 쿠버네티스와 관련한 주제를 다룬다는 것을 의아하다고 생각 하실 것 같습니다. “서버리스 환경에서 2천만 유저에게 이메일 전송하기”, “서버리스 배포가 너무 느려요!! 속도 10배 개선하기” 이전에 작성했던 두개의 글에서 저희 개발팀의 백엔드 시스템은 서버리스를 채택하고 있고, API 기능의 비즈니스 로직 또는 메시지 큐를 통한 비동기 작업과 일괄 처리를 위한 배치 작업 모두 AWS Lamda 에서 처리하고 있다고 소개 했었습니다. 그렇다면 저희 팀은 어떤 부분의 시스템을 쿠버네티스로 구축 하려고 하는 것 일까요?

일반적으로 서버리스 구조를 사용하여 API 서버를 구축 한다면 Amazon API Gateway와 AWS Lambda 를 조합한 구조를 채택 할 것 입니다. API Gateway 에서는 클라이언트의 HTTP 요청을 받아 API에 맞는 Lambda 로 라우팅 하고, Lambda 에서는 API 요청에 맞는 비즈니스 로직을 처리하여 응답을 API Gateway로 넘겨주게 됩니다.

저희 팀에서는 시스템 구조를 처음 설계 할 당시 Amazon API Gateway를 사용하는 방식은 우리 시스템에 요구 되는 처리량 목표에 부합 하지 않고, 은근 고성능 컴퓨팅 자원인 Lambda 를 잘 활용하지 못한다고 판단 했습니다. 그렇기 때문에 Go 로 자체 개발한 Stayge API Gateway 서버로 Amazon API Gateway를 대체 했습니다.

화력이 좋은 팬덤 유저를 대상으로한 투표 서비스를 준비 중이었고, 생방송과 연계된 기간 참여 투표 기능 때문에 순간적으로 많은 트래픽이 몰리는 것이 예상되어 10만 RPS의 높은 처리량이 요구되는 시스템을 설계 해야 했습니다.

서버리스 구조를 채택 할 때는 서비스 할당량(Service Quota)에 대한 이해가 필요합니다. 서비스 할당량은 AWS에서 제공하는 서버리스 서비스가 얼마 만큼의 성능을 발휘 할 수 있고 해당 성능의 할당량을 상향 조정 할 수 있는지 여부 입니다. API Gateway의 기본 할당된 초당 처리 가능 횟수는 1만 RPS로 상향 조정 가능합니다. 1만 RPS 는 저희가 목표하는 10만 RPS에 미치지 않지만, 상향 조정 가능 했기 때문에 목표 수치의 성능까지 도달 할 수 는 있습니다. 다만 두번째 문제가 있었습니다.

Lambda는 컴퓨팅 자원이며 설정한 메모리에 따라서 할당 되는 vCPU의 갯수가 정해 집니다. 메모리는 128MB~10240MB 사이에서 설정 가능하며 vCPU는 최대 6개 까지 사용 가능 합니다. 성능이 상당히 좋은 서버 컴퓨터를 밀리세컨드 단위로 빌려 쓸수 있다는 뜻인데, Amazon API Gateway는 클라이언트 요청 한개당 Lambda 실행을 한번 하고 종료 되어 닭잡는데 소잡는 칼을 사용하는 격이 됩니다. 또한, Lambda 의 서비스 할당량 중 동시 실행 가능 갯수의 기본 값이 1000개 인데 만약 모든 API의 응답 속도가 1초라고 가정한다면, 초당 처리 가능한 요청량은 1천 RPS 가 되어 목표치에 도달 하지 못합니다.

Go 로 자체 개발한 Stayge API Gateway의 컨셉은 매우 단순합니다.

  • 클라이언트의 요청을 x ms 동안 n 개를 모아서 Lambda에 전달한다.
  • 먼저 처리된 요청별로 클라이언트에게 응답한다.

저희는 Stayge API Gateway를 통해 시스템의 성능을 요구하는 목표 치 까지 달성 할 수 있었고, 3년간 AWS EC2 위에서 운영 해왔습니다. 쿠버네티스 도입을 고민하기 시작한 시점은 StaygeWebSocket Gateway를 개발 하기로 계획 했을 때 입니다. StaygeWebSocket Gateway는 서비스 내에 채팅 기능을 담당하던 AWS Iot Core의 MQTT를 대체하기 위한 Go로 자체 개발한 Web Socket 서버입니다.

저희는 다음의 이유로 쿠버네티스 도입을 결정하게 됩니다.

  • 관리가 필요한 서버 증가를 대비한 운영 효율화
  • 쿠버네티스와 잘 통합된 오픈 소스 프로젝트 생태계에 탑승
  • 서버리스 구조에서는 불리한 작업에 대해서 컨테이너 환경으로 대체

기존의 EC2만을 사용한 운영 방식은 시스템의 확장성과 자동화 측면에서 불리한 측면이 있었고 컨테이너 백엔드 어플리케이션이 하나 더 늘어 나는 시점에서 쿠버네티스와 같은 컨테이너 오케이스레이션이 필요 했습니다. 로그 수집을 위한 Fluentbit, 서버 자동 확장을 지원하는 Karpenter, 그리고 부하 테스트 도구인 K6와 같은 쿠버네티스 생태계에 잘 통합 되어 있는 오픈 소스 도구와의 시너즈도 기대 했습니다. 그리고, 시간이 오래 소요 되고 빈번하게 실행 되는 작업을 Lambda에서 수행 시키면 비용 효율 측면에서 불리한데, 서버리스 구조에서는 불리한 워크로드에 대해 컨테이너 기반 실행 환경을 제공함으로써, 비용 최적화 및 유연성을 확보 하고자 했습니다.

고마워요 EKS!

쿠버네티스는 정말 강력한 컨테이너 오케스트레이션 도구 입니다. 다만, 쿠버네티스 초보인 제가 구축해본 경험으로는 정말 다루기 어려운 도구입니다. 그래서, 쿠버네티스 초기 환경 구축 난이도를 낮추고 고가용성을 유지하기 위해서 Amazon EKS(Elastic Kubernetes Service)의 선택 했습니다. EKS는 쿠버네티스 클러스터 구축에 필요한 구성 요소들과 보안 및 네트워크 설정을 간소화 시켜주는 관리형 서비스로 API Server, etcd 상태 저장소, 스케줄러, 컨트롤러 매니저 등 모든 ‘컨트롤 플레인 구성 요소의 관리 책임을 AWS에 완전히 위임합니다. 고객은 워커노드, Add-on, NLB, 어플리케이션 컨테이너 등 ‘데이터 플레인에 해당 하는 구성 요소만 관리하면 됩니다.

*컨트롤 플레인은 클러스터를 관리하고 제어하는 역할을 하는 핵심 구성 요소입니다.

*데이터 플레인은 사용자의 워커 노드에서 파드 형태로 애플리케이션이 실제로 실행되는 공간입니다.

EKS에서는 고가용성을 유지하기 위해 컨트롤 플레인의 핵심 요소인 API Server최소 2개, etcd 상태 저장소 최소 3개를 여러 ‘가용 영역에 걸쳐 분산 합니다. API Server와 etcd 의 고가용성을 유지해야 하는 이유는 쿠버네티스에서 유저의 파드 생성 요청으로 파드가 생성 되는 과정을 보면 금방 이해 할수 있습니다. 아래의 사진은 파드가 생성되는 과정을 그린 구성도입니다.

*가용 영역은 독립적으로 운영되고물리적으로 위치가 떨어진 데이터센터 입니다.

*고가용성은 IT 시스템이 다운타임을 제거하거나 최소화하여 거의 100% 상시 액세스 가능하고 신뢰성을 유지하는 능력입니다.

  1. 유저가 API Server에 파드 생성 요청
  2. API Server가 etcd에 “파드 생성 요청” 상태 저장
  3. Controller가 새 파드 요청 확인
  4. Controller가 API Server에 파드 할당 요청
  5. API Server가 etcd에 “파드 할당 요청” 상태 저장
  6. Scheduler가 새 파드 할당 확인
  7. Scheduler가 파드를 노드에 할당 요청
  8. API Server가 etcd에 “파드를 노드에 할당 / 미실행” 상태 저장
  9. kubelet가 자신의 노드에서 미실행 파드 확인
  10. kubelet가 파드를 노드에 생성
  11. API Server가 “파드를 노드에 할당 / 실행중” 상태 저장

위 구성도의 통신들은 Restfull API 방식으로 데이터를 주고 받으며 서버간에 소통을 하고 있습니다. 아래의 사진은 워커 노드에 생성된 kubelet이 워커 노드가 동작 가능한 상태라고 API Server에 보고하는 API 통신에 대한 로그의 일부 입니다. 이해를 돕기 위해 추가했습니다.

*워커 노드는 사용자의 컨테이너가 동작 할 서버 컴퓨터 혹은 EC2 인스턴스 입니다.

쿠버네티스가 동작하는 방식을 잘 보면 정말 잘 짜여진 마이크로 서비스와 같습니다. 마이크로 서비스 구조의 장점 중 하나는 특정 서비스에서 장애가 발생하더라고 해당 장애가 다른 서비스 까지 전파 되는 것을 방지 할수 있다는 점인데, API Server 또는 etcd에 장애가 발생 할 경우에는 쿠버네티스 클러스터에 전반적인 장애가 발생하게 됩니다. 쿠버네티스에 구성 요소들은 모두 API Server를 통해서 통신하고 etcd 에는 API Server만 접근하고 있기 때문입니다.

AWS 환경에서 쿠버네티스 클러스터를 구축하는 방식은 EKS를 사용 하는 것 말고도 EC2에 API Server와 etcd 그리고 컨트롤 플레인을 구성하는 다른 구성 요소를 분산된 환경에 직접 설치하고 네트워크와 보안을 구성하는 DIY 방식이 있습니다. 단순히 내가 만든 백엔드 서버가 잘 운영 되기를 바라는 저 같은 사람에게는 DIY 방식으로 하나 하나 직접 구축하고 고가용성을 유지하며 운영하는 상상을 하기만 해도 끔찍하죠. 뿐만 아니라 EKS는 보안 패치와 기본적인 모니터링 도구 세팅을 도와 줍니다. 이 모든게 시간당 0.1 달러, 한달에 72 달러로 본다면 상당히 합리적인 가격인걸 알 수 있습니다.

쿠버네티스는 괴로워?

EKS를 선택했기 때문에 쿠버네티스 구축의 절반을 처리 해낼 수 있었습니다. 우리에게 남아 있는건 데이터 플레인!! 우리의 백엔드 어플레이션의 컨테이너와 워커노드를 보조 해줄 구성 요소들을 세팅하고 운영하기만 하면 됩니다. 참 쉽죠?

사실 제가 EKS의 데이터 플레인을 구축하기 위해 많은 역경이 있었고 잘 운영하기 위한 많은 고민들이 있었습니다. 많은 역경과 고민 중 일부를 공유 드리겠습니다.

넓은 생태계, 많은 선택지

쿠버네티스는 구글에서 2014년에 오픈소스로 처음 공개 되었고, 2015년에 CNCF(Cloud Native Computing Foundation) 재단에 기증되어 빠르게 발전되고 생태계가 확장 되어 왔습니다. 아래의 사진은 CNCF에서 관리하는 제품들을 카테고리 별로 정리 되어 있는 표 입니다. 모든 제품들이 메이저하다고 할 수 없지만 정말 많은 것을 알 수 있습니다.

이러한 이유로 저도 어떤 도구를 선택하여 워커 노드의 운영을 보조 할 지 많은 연구와 고민을 했습니다.

시스템 자동 확장을 위해서 Cluster AutoScaler와 Karpenter의 선택지가 있었고, 확장 속도와 유연한 EC2 인스턴스 타입 선택 그리고 비용 절감 등의 이유로 Karpenter를 선택 했습니다. 시스템을 외부의 인테넷으로 노출 하기 위해서 AWS NLB(Network load balancing)와 AWS ALB(Application load balancing)의 선택지가 있었고, 확장성 보다는 더 높은 네트워크 처리량 등의 이유로 AWS NLB를 선택 했습니다. 시스템의 옵저버빌리티를 확보하기 위해서 AWS CloudWatch와 Prometheus + Grafana + Fluentd + Loki 를 조합한 오픈소스 방식의 선택지가 있었고, 관리에 대한 단순화와 CloudWatch에 쌓여온 팀내에 신뢰 등의 이유로 CloudWatch를 선택 했습니다.

다 소개 하지는 못했지만 보안, 인증, 네트워크, 컴퓨팅 자원, IaC, 컨테이너 마다 세세한 설정 까지 정말 많은 선택지와 선택이 있었습니다.

해당 버전은 지원되지 않습니다.

쿠버네티스의 버전은 약 3개월에 한번씩 올라가고 있으며 1년에 4번의 업데이트가 이루어 집니다. 제가 쿠버네티스를 개인적으로 스터디 할 때는 v1.31이었고 운영 환경에 적용한 시점은 v1.32였으며, 25년 5월 15일 기준 현재 v1.33가 가장 최신의 버전입니다. 쿠버네티스의 빠른 버전 업데이트에 맞춰 생태계를 구성하는 다른 도구들도 같이 빠른 버전 업데이트가 생기고 있습니다.

만약 쿠버네티스의 버전과 써드파티 도구의 버전이 알맞지 않으면 도구를 설치 할때 오류가 발생하거나, 정상 동작을 하지 못하는 상황이 발생합니다. 클러스터 오토스케일러 도구인 Karpenter를 예를 들어 설명 한다면, Karpenter v1.2은 쿠버네티스 v1.32 부터 지원됩니다.

앞서 EKS의 비용이 시간당 0.1$ 로 소개 했었는데 시간당 0.6$ 로 비용이 증가 할수 있는 시점이 있습니다. 쿠버네티스 버전은 Amazon EKS에서 출시된 후 처음 14개월 동안 표준 지원을 받습니다. 표준 지원 종료일이 지난 쿠버네티스 버전은 향후 12개월 동안 연장 지원으로 전환되어 시간당 0.6$의 추가 비용으로 더 오랫동안 해당 쿠버네티스 버전을 사용할 수 있습니다. 6배의 추가 비용을 지출 하지 않기 위해서는 1년에 4번의 쿠버네티스 버전을 업데이트 하거나, 1년에 한번 4개의 버전을 한번에 업데이트 해야 한다는 것을 의미합니다. 버전을 업데이트하는 작업은 버전이 올라갔을 때 다양한 사이드 이팩드까지 꼼꼼하게 점검해야 합니다. 여기에는 Karpenter와 같은 써드 파티 도구들의 업데이트까지 챙겨야 하죠.

새로운 도구를 도입한다면, 버전 별 호환성과 버전 업데이트 시 점검 사항은 각 도구의 공식 문서에서 각각 확인를 해야 하기에 쉽지 않습니다.

고가용성을 사수하라

고가용성을 유지하는 인프라 구조 설계의 방법중 하나는 시스템이 실패(failure)할 수 있다는 것을 인정하고 다양한 방지책을 만들어 두는 것 입니다. 시스템에 실패가 발생 할수 있는 경우의 다음의 예시들이 있을 것 입니다

  • 서버에 요청 되는 트래픽의 양이 한개의 서버로 처리 할수 있는 수준을 넘었을 경우
  • 특정 가용 영역에 일시적인 장애가 발생한 경우
  • 스팟 인스턴스 사용시 할당 받은 EC2 인스턴스를 갑작스럽게 반납 해야하는 경우
  • 신규 기능 배포나 보안 및 버전 패치를 위해서 서버의 재기동이 필요한 경우

EKS 환경에서 Stayge Gateway와 같은 사용자 서버의 고가용성을 유지하는 책임은 고객에게 있습니다. 저희 팀도 예상 되는 시스템의 실패 상황을 대비하기 위해 다양한 방지책을 만들어 두었습니다.

가장 기본적으로 서버 요청 증가에 따른 서버 자원 사용량 증가를 대비 하기 위해서 HPA(Horizontal Pod Autoscaler)와 Karpenter를 사용하여 서버를 최소 2개 이상 유지하고 CPU, Memory와 같은 자원 사용량이 특정 임계치를 넘으면 수평적인 확장을 할수 있게 설정해 두었습니다.

특정 가용영역 또는 워커노드에 장애 발생이 전체 시스템의 장애로 전파되지 않도록 파드가 가용영역과 워커노드에 균등하게 배치 되도록 설정 했습니다.

스팟 인스턴스가 동시에 회수 되어 순간적으로 가용 가능한 파드가 없어지는 상황을 방지하기 위해서 파드가 온디멘드와 스팟 인스턴스에 특정 비율로 할당 되게 설정하여 비용 효율성과 안정성을 챙겼습니다.

서버 재기동이 필요한 상황에서 서비스 중단 없이 작업하기 위해, 파드를 순차적으로 재기동하는 Rolling Update, 가용한 파드의 숫자를 보장하기 위한 PDB(Pod Disruption Budget), 파드 종료 시점에 이미 처리중인 요청의 처리를 기다려 주기 위한 terminationGracePeriodSeconds, preStop hook, 그리고 NLB튜닝 까지 다양한 방지책들을 만들어 두었습니다. (이것에 대한 상세한 내용들은 추후 아티클에서 별도로 다룰 예정입니다!)

저희는 고가용성을 유지하기 위해 예상 되는 범위안에서 다양한 방지책을 만들었고, 컨테이너 성능 측정을 위한 성능 테스트 그리고 옵저버빌리티 확보를 위한 대시보드 및 알람 시스템을 구축했습니다. 저희가 아직 모르는 방지책들은 앞으로 쿠버네티스 환경을 운영하면서 격게될 다양한 문제 상황을 통해 성숙해 질 것으로 기대합니다.

개발, 테스트, 운영 환경

EKS 환경을 바닥부터 현재 상태 까지 구축하면서 들었던 느낌은 마치 AWS 클라우드 환경을 구축하는 것 같았습니다. 비유가 틀렸을 수도 있지만 커다란 인프라 환경을 만들어 나가는 경험이었고, 다양한 도구들 그리고 이것에 따른 보안, 네트워크, 권한, 보조 설정들 까지 많은 것을 하나 하나 만들어야 했기 때문입니다. 저희의 쿠버네티스 클러스터가 거대한 편은 아니지만 Pulimi를 통해 선언된 리소스가 약 150개로 꽤 많았고 엔지니어가 수동으로 개발, 테스트, 운영 환경을 하나의 오차도 없이 동일하게 인프라를 찍어내는 것은 불가능에 가까운 일이었습니다. 그래서 저희는 IaC(Infra as Code) 도구인 Pulumi를 사용하여 환경별로 일광성 있는 인프라를 만들어내고 유지보수를 할수 있었습니다.

고마워요 Pulumi!

Pulumi는 오픈소스 IaC 플랫폼으로, TypeScript· JavaScript· Python· Go· C#· Java· YAML 등을 사용해 AWS· Azure· GCP· Kubernetes 등 150여 개 이상의 클라우드 및 서비스 제공자에 인프라를 생성 및 관리 할 수 있습니다. 사용자가 작성한 Pulumi 코드를 실행해 “원하는 상태(desired state)”를 산출하고, Pulumi 엔진이 실제 클라우드 리소스를 생성·업데이트·삭제하는 과정을 자동으로 처리합니다.

아래의 코드는 EKS 클러스터를 생성하는 타입스크립트로 작성된 Pulumi 코드입니다. eksNodeRole은 EKS 클러스터가 동작 하기 위한 권한을 IAM으로 생성하고 변수로 참조합니다. vpc와 public subnet의 아이디도 변수로 관리하여 할당 된 것을 볼 수 있습니다. Pulumi Cli 명령어인 pulumi up을 실행시키면 EKS 클러스터가 생성됩니다.

pulumi up 명령어를 사용했을 때 인프라가 생성 되는 과정은 다음과 같습니다.

  1. 언어 호스트가 리스소를 생성 하는 구문을(new Resource()) 감지해 Pulumi 엔진에 리소스 등록 요청을 보냅니다.
  2. 엔진이 상태 저장소에서 가장 최근의 배포된 리소스의 상태를 조회하고 해당 리소스의 생성, 수정, 삭제 여부를 판단하여 작업 방식을 결정 합니다.
  3. 엔진이 리소스 제공자(AWS/Azure/K8s 등)에서 제공되는 SDK를 사용해서 생성/수정/대체/삭제 작업을 수행 합니다.
  4. 작업이 완료 되면 엔진이 변경된 인프라 상태를 상태 저장소에 저장합니다.

Pulumi를 사용해본 경험으로 알게된 장점과 특징들이 있습니다.

  • 인프라 생성/수정/삭제하는 작업의 결과에 멱등성이 생깁니다. pulumi up 실행 시 현재 상태와 코드로 표현된 원하는 상태를 비교하고, 차이가 없으면 아무 작업도 수행하지 않으므로, 같은 명령을 여러 번 실행해도 같은 결과가 나옵니다.
  • 실제 인프라 작업을 하기 전에 작업 실행 계획과 전/후에 대한 차이점을 알 수 있습니다. 이것은 작업에 대한 안정성과 신뢰를 높혀줍니다.
  • 인프라의 변경점들은 Git과 같은 버전 관리 도구로 관리 할수 있어서 작업 내용을 추적 할 수 있고, 롤백이 쉬워집니다.
  • 개발팀에서 사용하는 친숙한 프로그래밍 언어는 타입스크립트를 사용 함으로써 조직내에 유지보수 난이도가 쉬워 졌습니다. 코드를 모듈화가 쉽기 때문에 코드의 재사용성도 높아 집니다.
  • Deployment, HPA 와 같은 쿠버네티스와 관련한 리소스는 주로 YAML파일로 관리하는데, 이것 또한 타입스크립트로 관리가 가능해져 코드의 일관성이 생깁니다.
  • 100 퍼센트 신뢰 할 수 없지만 코드이기 때문에 요세 핫한 Cursor나 GPT와 같은 AI의 도움을 받을 수 있습니다.

저희는 타입스크립트로 작성된 Pulumi 코드에 독립된 배포 환경을 구분하는 Stack을 사용하여 EKS 클러스터의 개발/테스트/운영 환경을 아래의 그래프 처럼 쉽게 구축 하고 관리 할 수 있었습니다. Pulumi를 사용했을 때 확실히 인프라 작업에 대한 자신감과 신뢰가 올라가고 작업 생성성이 엄청 높아졌다는 걸 채감 했습니다. 만약 IaC를 사용하지 않았다면 쿠버네티스 구축 프로젝트는 무조건 실패했을 것이라고 확신합니다.

마무리

EKS와 Pulumi를 사용해서 쿠버네티스 환경을 구축 하는 저희 팀의 사례를 소개했습니다. 쿠버네티스 환경을 구축하는 방법은 다양한 방법이 있을 것이고, 이번 아티클에서 여러 방법 중 하나의 사례로 알아 가셨으면 합니다. 이번 아티클을 시작으로 앞으로 쿠버네티스와 관련한 다양한 주제로 다시 돌아오도록 하겠습니다!

Backend Developer
-
VIctor

스테이지랩스는 목표와 성장에 몰입할 수
있도록 최고의 환경을 제공해요.

Culture 바로가기