AX에서 IaC를 이용해 MSA 를 도입한 이야기: AWS CDK를 중심으로

서비스 구조를 Monolithic 구조에서 MSA 로 이전하는 큰 변화를 겪었습니다. 이 과정에서 수많은 리소스와 아키텍처 구조가 생겨나 이를 잘 관리할 수 있는 방법이 필요했는데요, 이번 글에서는 AX 에서 IaC 를 통해 MSA 구조를 관리하게 된 이야기를 해볼까 합니다.

AX에서 IaC를 이용해 MSA 를 도입한 이야기: AWS CDK를 중심으로

안녕하세요 저는 AX 에서 서비스 인프라 구성과 서버/백엔드 개발을 담당하고 있는  Kevin 입니다. 얼마 전 AX 는 서비스 구조를 Monolithic 구조에서 MSA 로 이전하는 큰 변화를 겪었습니다. 이 과정에서 수많은 리소스와 아키텍처 구조가 생겨나 이를 잘 관리할 수 있는 방법이 필요했는데요, 이번 글에서는 AX 에서 IaC 를 통해 MSA 구조를 관리하게 된 이야기를 해볼까 합니다.

태초에 Monolith 가 있었다

어떤 서비스건 처음 만들 때는 한 곳에서 모든 걸 처리할 수 있도록 만드는 것이 편합니다. 네트워크 구성을 하기도 편하고, 서비스 아키텍처도 단순하고, 개발도 한곳에서 모든걸 보면서 할 수 있죠. 그렇기 때문에 관리 포인트도 적고 뭐가 어디 있는지, 이게 어디서 와서 어디에 영향을 주는지도 확인하기가 용이합니다. 지금도 많은 서비스가 이렇게 만들어져 운영되고 있고, 또 새로이 만들어지고 있을 것이며, 지금까지의 AX 서비스도 이러한 구조로 개발되고 있었습니다. 보통 이런 식으로 한곳에서 모든 기능을 다 담당하는 구조를 모노리식(Monolithic) 구조라고 합니다. 예를 들어 PoC(Proof of Concept, 개념증명) 를 하기 위해 간단히 만들어볼 서비스라면 굳이 리소스를 분리하지 않고 작은 서버 하나에 DB, 서버 로직, web proxy, cache 까지 모든 것을 다 집어넣어 만드는 쪽이 목표 달성에 유리합니다. 빠르게 작동 여부와 사용자의 반응을 보는 것이 목표이기 때문에 고도화된 서비스 아키텍처를 구성하는 것은 비용적으로도 시간적으로도 별로 좋지 못한 결정이죠.

모노리스 덕에 인류는 발전했다(?) 영화 2001 스페이스 오디세이에 나오는 monolith

하지만 서비스가 실제로 작동하기 시작하고 트래픽이 늘며 다른 서비스와의 접점이 늘고 심지어 서비스 자체가 확장되기 시작하면 이 구조는 큰 문제를 드러내기 시작합니다. 바로 이 거대한 서비스 모든 곳이 서비스 전체의 SPOF(Single Point of Failure) 로 작동하기 때문인데요, 예를 들어 광고 서비스와 웹서버와 DB를 한 서버에 올리고 모든 로직을 하나의 코드베이스로 개발하는 상황을 생각해보겠습니다. 이때는 광고 노출이 많아지면 덩달아 웹 서비스가 느려지고, 웹에 요청이 많으면 DB가 부하를 받아 서버 전체 메모리가 부족해지는 상황을 만나기 쉽습니다. 누군가 광고 서비스의 로직을 건드리다 잘못해서 서버가 내려가면 같은 코드베이스에서 돌고 있던 웹서버는 한 것도 없는데 같이 누워야 하는 상황이 되기도 합니다. 광고 서비스의 버그를 열심히 고쳐 배포를 해보겠습니다. 같이 누운 것도 억울한 웹 서비스는 단지 같은 코드베이스에 있다는 이유 하나만으로 같이 배포에 참가해야 합니다. 여기에 관리자 기능이랑 CS 를 위한 고객관리 페이지 정도만 추가해본다면? Monolithic 구조에서 서비스가 발전할수록 하나의 작은 실수, 작은 변경이 만들어내는 서비스 전체의 영향은 갈수록 커질 것입니다.

영 좋지 못한 설계를 가진 서비스는 이렇게 되기 십상이다

여기서 하고자 하는 이야기는 monolithic architecture 가 잘못된 구조라거나 SPOF 가 있는 구조가 잘못되었다는 것이 아닙니다. 일단 기본적으로 저는 SPOF 가 아예 존재하지 않는 서비스를 만드는 것은 불가능하다고 생각합니다. DB 를 생각해보겠습니다. Amazon Aurora 같은 서비스가 있기는 하지만 DB 는 대표적으로 떠올릴 수 있는 SPOF입니다. 그것보다는 SPOF 의 개수를 어떻게 줄일 수 있는지, 그리고 어떤 사정으로 인해 하나의 서비스가 사용 불가한 상태가 되었을 때 다른 서비스에 주는 영향을 최소화하기 위해서는 어떻게 해야 하는지를 고민해보도록 하겠습니다.

자 드가자 MSA 하러

MSA는 Microservice Architecture 의 준말로, 말 그대로 서비스를 micro 하게 나누어 개발, 관리하는 구조를 말합니다. 간단히 게시판을 예로 들어 보자면 회원, 게시판 보기, 글쓰기, 댓글, 관리자 기능을 다 잘게 나누어 개별 서비스로 만들고, 서비스 간의 통신을 통해 데이터를 주고받는 구조를 구축하면 MSA 게시판 서비스가 됩니다. 이런 구조에서는 댓글 기능에 문제가 생겨 댓글을 쓸 수 없게 되어도 다른 기능은 다른 곳에 분리되어 있기 때문에 사용자는 여전히 가입을 하고 글을 읽고 쓸 수 있습니다. Monolithic 구조에서는 회원, 게시판, 글쓰기, 댓글이 각각 서로의 SPOF 였다면 MSA 구조를 구축하면 일부 기능이 죽어도 다른 부분은 정상적으로 작동하면서 서비스 전체가 다운되는 상황을 최대한 방지할 수 있게 됩니다.

물론 다른 모든 것이 그렇듯 MSA 도 silver bullet은 아닙니다. 위의 구조를 따라 네트워크, 하드웨어 구성을 하려면 아마 개발하는 시간보다 인프라를 까는 데 더 많은 시간과 노력이 들 것이고 그게 다 어디에서 어떻게 돌아가고 있는지, 서로 어떤 관계를 가지고 있고 어떤 영향을 주는지도 관리해야 합니다. 관리 포인트가 1개에서 2개가 되는 수준과는 차원이 다른 이야기가 MSA 를 도입하는 순간 펼쳐지게 됩니다.

Amazon 과 Netflix 의 MSA 구조도. 어떤게 머리나 칠판에 그림 그려서 해볼 만한 수준을 아득히 뛰어넘고 있다

게시판 만드는 데 서버가 5~6개 필요해진다는 시점에서 이미 MSA 를 포기하고 싶을 수도 있겠지만, 단 몇 초의 시간이 회사의 매출, 신뢰도와 직결되는 비즈니스 필드에서는 가능한 서버의 다운타임을 줄이고 SPOF 를 컨트롤하며 동시에 확장성도 가지는 구조를 만드는 것이 선택이 아닌 필수이기 때문에 결국 MSA 를 도입하는 것은 필연적인 수순이 될 것입니다. 그려면 이 MSA 구조를 어떻게 하면 좀 더 잘 굴릴 수 있을까요?

IaC: 코드가 인프라가 되는 세상이 있다?!

개발자라면 뭘 보든 뭘 하든 이런 생각을 한번씩 하게 마련일 겁니다. "아 이거 스크립트 만들어서 돌리면 금방인데", "아 이거 코딩 간단하게 하면 두고두고 써먹을 수 있는데", "아 이거 저번에 만든거 재활용할 수 있는데". 서비스 구조와 인프라에 대해서도 같은 생각을 해볼 수 있을 것 같습니다. "아 이거 스크립트 만들어서 자동으로 인프라 깔리면 좋겠는데", "아 이거 인프라 코딩 간단하게 해서 두고두고 써먹으면 좋겠는데", "아 이거 저번에 만든 구조 재활용할 수 있으면 좋겠는데". 점점 커지는 서비스와 복잡해지는 인프라는 이러한 니즈를 점점 더 강화시켰고, 그 결과 서비스 인프라도 코드로 만들어 관리하는 지점에 도달했습니다. 이 결과물로 나온 것이 IaC, Infrastructure as Code 라는 것입니다. 말 그대로 서비스 인프라를 코딩을 통해 구조화/문서화 하고 이를 자동으로 서비스에 반영시키며 고도의 재사용성도 갖출 수 있도록 하는 인프라 개발/관리 방법입니다.

AX에서는 AWS 를 사용하고 있으니 AWS 를 기준으로 살펴보겠습니다. AWS 에는 CloudFormation (이하 CFN)이라 불리는 서비스가 있습니다. CFN이 하는 일은 AWS 에 있는 모든 리소스를 일정한 포맷을 따르는 문서로 작성할 수 있도록 하는 규칙을 제공하고, 문서를 작성해 업로드하면 이를 해석해 실제 AWS 서비스에 반영시켜주는 것입니다. 예를 들어 t4g.small EC2 를 하나 만들어서 VPC 에 올리고 80 포트를 열고 싶다면 이것을 문서로 작성해 CFN 에 업로드만 하면 실제로 AWS 에서 EC2를 만들어 VPC 에 놓고 80 포트를 열어줍니다. 문서로 작성한 스펙이 그대로 서비스, 인프라가 되는 것이죠. 하지만 이 CFN 에는 큰 단점이 있습니다. AWS 의 서비스가 많은 만큼 매우 많은 내용을 작성해야 하고 각각의 서비스가 가지는 수많은 속성과 가질 수 있는 값을 확인해야 하며, 심지어 요소 간에 의존성이 있는 경우 이 관계를 사람이 파악해서 작성해야 한다는 것입니다. 우리가 할 것은 이런 게 아니니 예시만 슥 보고 진저리나 한번 치고 넘어가면 됩니다.

Resources:
  SimpleVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.21.0.0/16

  SimpleVPCIpv6:
    Type: AWS::EC2::VPCCidrBlock
    Properties:
      VpcId: !Ref SimpleVPC
      AmazonProvidedIpv6CidrBlock: true

  SimpleIGW:
    Type: AWS::EC2::InternetGateway

  SimpleVPCIGWAssoc:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref SimpleVPC
      InternetGatewayId: !Ref SimpleIGW


VPC 를 하나 만들고 Internet Gateway 를 하나 만들어 VPC 에 붙이는 구조를 CFN 문서로 작성한 것입니다. 그나마 JSON 이 아닌 yaml 인 것은 다행인 것 같지만 (불행하게도 JSON 으로도 작성할 수 있습니다) 모든 리소스 하나하나를 만들 때마다 Resource Type 을 찾아야 하고, 어떤 Property 가 있는지도 찾아야 하고, AWS web console 에서도 본 적 없는 녀석을 알아내 한땀한땀 작성해주어야 합니다. 예를 들어 맨 아래에 있는 VPCGatewayAttachment 를 추가하지 않으면 기껏 만든 Internet Gateway 는 어떤 일도 하지 못하고 고아가 돼버립니다. 직접 AWS console 에서 작업할 경우에는 버튼 두번 클릭하면 되는 과정이라 저런 것까지 CFN 에 작성해야 한다는 것을 알기가 매우 어렵습니다. 물론 CFN 은 그 복잡하고 거대한 AWS 서비스를 결정론적인(Deterministic한) 방법으로 만들 수 있도록 매우 잘 설계되어 있습니다. 공식 문서에 내용이 매우 상세하게 작성되어 있고, 구글링을 하면 예시도 꽤 찾을 수 있지만 지면이 아까우니 링크는 생략하도록 하겠습니다. 이런 불편함을 개선해보겠다고 CFN visual editor 같은 것도 있는데, 어떻게 된 게 쓰기가 더 어렵습니다. 그만 알아보죠.

그럼 IaC 는 이쯤에서 절망해야 할까요? 댓츠노노. HashiCorp 라는 곳에서 만들어 IaC 계의 사실상 표준으로 여겨지는 Terraform 이라는 녀석이 있습니다. 이걸 이용하면 상대적으로 훨씬 간단하게, 그리고 조금 더 직관적으로 IaC 를 적용할 수 있게 됩니다.

resource "aws_vpc" "SimpleVPC" {
  cidr_block       = "10.21.0.0/16"
  assign_generated_ipv6_cidr_block = true
}

resource "aws_internet_gateway" "SimpleIGW" {
  vpc_id = "${aws_vpc.SimpleVPC.id}"
}


위의 CFN 코드와 같은 작업을 하는 Terraform 코드입니다. 훨씬 더 직관적이고 짧은 코드로 같은 목표를 달성할 수 있다는 장점과 함께 하나의 Terraform 파일로 여러 개의 cloud provider(AWS, Azure, GCP) 에 인프라를 구성할 수 있는 장점, 그리고 기존에 cloud 에 직접 만들어 사용하고 있던 리소스도 편리하게 가져와 사용할 수 있다는 강점이 있습니다. Terraform 을 이용하면 CFN 을 쓸 때에 비하면 매우 높은 생산성으로 인프라를 구축할 수 있게 됩니다. 하지만 Terraform 도 만능은 아닌데, 사실 좀만 뜯어보면 이것도 결국 CFN 의 축약형에 불과하고, 유동적으로 리소스 일부만을 변경해 원하는 구조를 만드는 것이 매우 어렵습니다. 진정한 의미의 code 를 통한 인프라는 아니라고 볼 수 있는 것이죠.

두둥등장, AWS CDK

AWS CDK, 보통 CDK 라고 부르는 이놈의 정식 명칭은 무려 Cloud Deveplopment Kit 입니다. 클라우드를 개발 할 수 있도록 해주는 키트라는 말인데요, 말 그대로 원하는 프로그래밍 언어로 인프라를 코딩하면 이것을 AWS 에 그대로 구현해주는 개발도구라고 할 수 있겠습니다. 현재 TypeScript, JavaScript, Python, C#, Go, Java 를 공식적으로 지원하고 있습니다. AX 에서는 Python을 주 언어로 사용하고 있으니 Python 으로 작성한 예시를 보겠습니다. 동일하게 VPC 를 하나 만들어 Internet Gateway 를 만들어 달아주는 구성입니다.

from aws_cdk import aws_ec2

vpc = aws_ec2.Vpc(
            self, "SimpleVPC", is_default=False,
            cidr="10.21.0.0/16",
        )


이게 끝입니다. VPC 가 인터넷과 통신하기 위해서 Internet Gateway 는 필수이기 때문에 굳이 코딩하지 않아도 알아서 만들어 달아줍니다. 위의 두 개와 CDK 가 다른 것은 아예 모든 리소스가 class 화 되어 있다는 점입니다. IDE 에서 사용하고자 하는 리소스 이름을 적고 괄호를 열면 이 서비스를 구성할 때 설정해야 하는, 설정할 수 있는 모든 선택지를 확인하고 골라서 사용할 수 있습니다. 게다가 IDE 에서 내가 익숙한 언어로 type hinting 과 자동완성의 도움을 받아가며 인프라를 뚝딱뚝딱 만들어갈 수도 있습니다. 그리고 터미널에서 cdk depoly 를 하면 알아서 CFN 파일을 빌드해 AWS 에 업로드하고 인프라를 구성해줍니다. 명령어 한 줄이면 되기 때문에 그냥 CI/CD 에 달아서 자동화도 할 수 있구요. 여기까지 보면 그저 Terraform 보다 좀 더 짧은 AWS 전용 무언가라고 생각할 수도 있겠지만, CDK가 다른 IaC 도구와 어떻게 다른지 몇 가지 예시를 통해 살펴보도록 하겠습니다.


인프라가 복사가 된다고?

서비스를 개발하면 아마 거의 모든 경우 최소한 2개 이상의 환경을 구축하고 있을 겁니다. 개발 환경과 프로덕션 환경 같이요. 개발 환경에서는 개발한 내용을 확인하고 테스트를 하며, 잘 테스트된 코드는 프로덕션 환경에 배포되어 사용자들이 사용할 수 있게 됩니다. 그런데 MSA 를 잘 구성하면 할수록 이렇게 환경을 여러개 만드는 것이 불편하고 귀찮은 작업이 됩니다. 기본적으로 개발 환경은 프로덕션 환경과 동일하거나 유사하게 구성되어 있어야 인프라 구조의 차이에서 생기는 문제를 방지할 수 있으며, 디버그를 할 때에도 재현성을 높일 수 있습니다. 그런데 VPC 안에 여러 layer 의 subnet 을 두고 10단위의 트리거와 100단위의 람다가 움직이는 환경을 여러개 만들어 관리한다는 것은 상상만으로도 아찔합니다. CFN 이나 Terraform 을 이용할 때에는 같은 구조의 여러 개 인프라를 만들기 위해 환경변수를 넣어서 처리해주거나 아예 여러 벌의 stack 을 만들어야 하는데, 변경이 있을 때 여기저기 돌아다니며 관리를 한다는 것이 여간 불편한 일이 아닐 겁니다. 이런 경우 CDK 는 매우 편리하게 동일한 구
성을 가진 여러 개의 stack 을 생성할 수 있습니다.

위 예시를 보면 동일한 CdkProdjectStack 이라는 것을 두번 생성하는데 인자로 stage 를 전달합니다. 이렇게 하면 간단하게 동일한 구성을 가지는 2개의 인프라를 만들 수 있습니다. 추가로 설정하기에 따라 서로 다른 stage 를 다른 region 에 배포할 수도 있고, stage 별로 배포 시기를 다르게 할 수도 있습니다. 완전히 동일한 stack 을 기반으로 설정값만 추가해 복제된 stack 을 만들었기 때문에 인프라 구조에 변경이 생겼을 때 한 곳에서만 stack 을 변경하면 한번에 여러곳에 변경을 반영할 수 있는 장점도 있습니다.

어때요, 참 쉽죠?

CDK건 Terraform 이건 CFN 이건 최종 결과물은 동일합니다. 결정론적으로(Deterministic) 구조화되어 AWS 가 해석할 수 있는 CloudFormation Stack 문서가 그것인데요, CDK 는 이 결정적인 문서를 아주 내 입맛대로 동적으로 생성할 수 있습니다. 다음 코드를 보도록 하죠.

이 코드는 route53에 custom domain 을 만들어 API Gateway 와 연결하고, 이곳을 통해 들어온 요청을 lambda 를 통해 처리하는 간단한 서버리스 웹 서비스를 구성하는 CDK stack 입니다. 그런데 안을 보면 if 문도 있고, boto3 를 통해 값을 가져오는 것도 있고... 뭔가 결정적인 구조를 만들어내는 코드라고 보기에는 너무도 동적이고 자유롭습니다. CDK를 사용하면 범용 프로그래밍 언어를 이용해 인프라를 코딩할 수 있기 때문에 필요한 곳에 원하는만큼 로직을 추가해 stack을 구성할 수 있게 되었습니다. 앞에서 만든 리소스를 뒤에서 인자로 넘길 수도 있고, 조건에 따라 다른 값을 사용하도록 할 수도 있습니다. 인프라가 단순히 복사만 되는 것이 아니라 그 안에서 원하는 만큼의 변주도 줄 수 있다는 것은 상황에 따라 유사하거나 다른 환경을 한 곳에서 편리하게 구성하고 관리할 수 있는 CDK 만의 강력한 기능이라고 할 수 있겠습니다.


당연한 말씀이지만, 무적은 아닙니다.

AX 에서는 이번에 MSA 를 도입하면서 적극적으로 CDK를 이용해 서비스 리소스와 인프라를 관리하고 있습니다. CDK 도 당연히 완벽한 도구는 아닙니다.

CDK 초기화를 위해선 꼭 빈 directory에 있어야 한다거나, cdk terminal 명령어 자체는 JS 로 작동하고 있어 다른 언어를 선택해도 npm을 하나 달고 다녀야 한다거나 하는 작은 불편함에서부터 AWS 에 더욱 강하게 종속되어 나중에 다른 cloud provider로 이동하는 것이 매우 어려워지는 큰 원인이 된다거나, 앞으로 인프라에 어떤 변경을 가하더라도 CDK 를 통하지 않으면 추후 stack 배포 시 에러가 날 수 있다는 문제, 그리고 최악의 경우 복구를 못하거나 CDK 를 버려야 하는 상황을 만나는 등의 critical 한 것까지 CDK 를 사용하는 데에도 감수해야 하는 어려움이 여럿 존재합니다.

하지만 현재 AX 의 상황과 앞으로 AX 가 가고자 하는 방향성, 개발 생산성 등을 종합적으로 고려했을 때 AX 에 가장 잘 어울리는 IaC 도구는 CDK 라고 판단했습니다.
그렇다고 CDK 가 유일한 정답이라고 생각하지는 않습니다. 상황에 따라 적절한 도구를 사용하면 됩니다.

예를 들어 여러 개의 cloud provider 를 사용하는 경우 Terraform 을 사용하는 것이 가장 좋은 선택지가 될 수 있습니다.
어떤 도구를 사용하건 간에 인프라를 구성하고 다른 사람과 협업해 관리하고 다른 사람에게 관리를 이관해야 하는 업무 전반에 걸쳐 IaC 가 큰 장점으로 작용할 것은 명확하다고 생각합니다.

서버 인프라를 구축하고 관리해야 하는 분들이 IaC를 통해 안정성과 가용성 높은 인프라를 조금이라도 편하게 관리할 수 있길 바라며 글을 마치도록 하겠습니다. 긴 글 읽어주셔서 감사합니다.

액스(AX)로 다 채널 여행 상품 동시 판매 및 운영


액스는 투어 & 액티비티 판매자의 온라인 판매를 위한 최적화된 다 채널 자동 판매 서비스 입니다.
지금 무료로 시작해보세요. 써보면서 이해하는 것이 가장 빠릅니다!