안녕하세요, 렌딧 Engineering 팀의 Colin입니다.

어느덧 60명이 넘는 렌딧맨들이 함께 일하고 있습니다. Engineering 팀 뿐만 아니라 기획, 마케팅, 운영 등 다양한 직군의 좋은 분들이 많이 조인하고 계신데요, 그러다 보니 점점 동시에 진행되는 프로젝트가 많아지면서 테스트 서버 부족 현상이 생겼습니다. 그런데 테스트 서버를 늘리려다 보니, 기존 테스트 환경에 몇 가지 문제가 있는 것을 발견하게 되었습니다.

기존 테스트 환경의 문제점

  1. 일관되지 않은 설정: 테스트 환경이 총 2벌 있었는데, 다른 사람이 각각 세팅하다 보니 설정이 제각각이었습니다. 설정에 따라 어떤 기능은 둘 중 하나에서만 테스트할 수 있는 등의 문제가 있었습니다. 또한 일관된 설정이 정리되어 있지 않다 보니 새로운 서버를 세팅할 때 어떤 설정을 따라가야 할지도 애매했고, 시간도 오래 걸렸습니다.

  2. 데이터베이스 sync 문제: 최대한 운영 환경과 비슷한 데이터로 테스트를 하기 위해, 매일 새벽 production DB를 dump 하여 테스트용 DB에 restore 하고 있었습니다. 그런데 갈수록 데이터가 많아지면서 dump & restore에 소요되는 시간이 길어졌고, 테스트 서버가 늘어나는 만큼 sync에 대한 부담이 커졌습니다.

이 글에서는 위의 두 가지 문제를 어떻게 해결했는지 공유하려고 합니다.

Packer

packer-logo

일관된 테스트 서버 세팅을 만들고 관리하기 위해 Packer를 사용해보기로 했습니다. 먼저 아래와 같이 builder를 설정해서 Amazon AMI를 생성할 준비를 합니다. Provisioner는 Ansible, Chef 등 다양한 옵션이 있지만 우선 간단하게 shell script를 사용하기로 했습니다.

1
{
2
  "variables": {
3
    "version": "{{ env `version` }}",
4
    "type": "{{ env `type` }}"
5
  },
6
  "builders": [{
7
    "type": "amazon-ebs",
8
    "region": "ap-northeast-2",
9
    "source_ami": "ami-********",
10
    "instance_type": "t2.small",
11
    "ssh_username": "lendit",
12
    "ami_name": "lendit-v{{ user `version` }}-{{ user `type` }}-{{ timestamp }}",
13
    "ami_description": "Lendit Image v{{ user `version` }}",
14
    "run_tags": {
15
      "Name": "packer-builder"
16
    }
17
  }],
18
  "provisioners": [{
19
    "type": "shell",
20
    "script": "lendit.sh"
21
  }]
22
}

이어서 provisioner로 설정한 lendit.sh를 만들어줍니다.

1
#!/bin/bash
2
3
echo "# INSTALL - UPGRADES"
4
sudo yum upgrade -y
5
6
echo "# INSTALL - PACKAGES"
7
sudo yum install -y htop ruby wget python36 nginx
8
9
echo "# INSTALL - CODEDEPLOY"
10
cd /home/lendit
11
wget https://aws-codedeploy-ap-northeast-2.s3.amazonaws.com/latest/install
12
chmod +x ./install
13
sudo ./install auto
14
rm install
15
16
# ... 이하 생략

아래의 명령어를 실행하면 몇 분 후에 lendit-v1-dev-xxxxxxxxxx 이름을 가진 AMI가 생성된 것을 확인할 수 있습니다. 이제 이 AMI를 사용해서 테스트용 EC2를 필요한 만큼 생성하면 되겠네요. 이렇게 첫번째 문제를 해결했습니다.

1
packer build -var "version=1" -var "type=dev" lendit.json

(사실 packer 설정은 sam이 거의 다 해주셨고, 저는 숫가락만 얹었습니다. 이 자리를 빌어서 sam에게 다시 한번 감사를…)

Amazon Aurora Clone

이어서 두번째 문제를 해결한 방법을 소개해 드리겠습니다.
렌딧은 주요 DB 중 하나로 Amazon Aurora를 사용하고 있는데요, 2017년 8월에 빠른 데이터베이스 복제 기능이 출시되었습니다. 이 기능을 활용하면 1시간 이상 소요되는 dump & restore 방식 대신 몇 분 안에 복제본 DB를 원하는 만큼 구성할 수 있어서 현재 니즈에 꼭 맞는 기능이었습니다.

다만 약간 번거로운 부분이 있었는데요, 빠른 데이터베이스 복제 기능은 항상 새로운 복제본 DB 클러스터를 만들기 때문에 매일 새벽에 새로운 복제본 DB 클러스터를 생성한 후 테스트 서버가 새로운 복제본 DB를 보게 만들고, 이전에 만들어진 복제본들을 정리해줘야 했습니다. 물론 하면 되는 일이지만 너무 귀찮았기 때문에 혹시 그런 일을 해주는 게 없을까 찾아보던 와중에… aurora-echo 라는 것을 발견했습니다. 제가 해야 했던 일들을 다 해주는 너무나 아름다운 도구였습니다…

aurora-clone

aurora-echo를 활용해서 위의 그림처럼 테스트용 DB 클러스터를 구성했습니다. 각 단계의 작업은 적당한 서버에 aurora-echo를 설치하고 cron으로 매일 정해진 시각에 실행되도록 했습니다.

  1. Clone: 복제본 DB를 생성합니다.
    1
    /usr/local/bin/aurora-echo clone -a [aws_account_number] -r ap-northeast-2 -n lendit-dev1 -s [source_cluster_name] -sub default -c db.t2.small -az [availability_zone] -sg [security_group_id] -i false
  2. Modify: 복제본 DB의 상태를 modified로 변경해줍니다. (이 과정에서 추가적인 IAM role을 부여할 수도 있습니다)
    1
    /usr/local/bin/aurora-echo modify -a [aws_account_number] -r ap-northeast-2 -n lendit-dev1 -i false
  3. Promote: modified 상태의 클러스터를 찾아서 Route 53의 DNS entry에 연결하고 상태를 promoted로 변경합니다. 이 때 기존에 promoted 상태이던 instance가 있으면 retired로 변경해서 이후에 삭제될 수 있도록 합니다.
    1
    /usr/local/bin/aurora-echo promote -a [aws_account_number] -r ap-northeast-2 -n lendit-dev1 -z [hosted_zone_id] -rs db-dev1.lenditprivate.xyz -i false
  4. Retire: retired 상태인 instance를 삭제합니다.
    1
    /usr/local/bin/aurora-echo retire -a [aws_account_number] -r ap-northeast-2 -n lendit-dev1 -i false

테스트 서버에서는 db-dev1.lenditprivate.xyz 주소만 바라보면 매일 최신의 데이터로 sync 되는 효과를 얻게 됩니다. 참고로 lenditprivate.xyz는 실제 public domain은 아니며, Route 53의 private hosted zone을 활용해서 VPC 내에서만 접근이 가능하도록 했습니다. Private Hosted Zone에 관한 자세한 내용은 Working with Private Hosted Zones 문서를 참고하세요.

CodeDeploy & Application Config

이제 테스트 서버와 테스트 DB를 각각 N벌씩 쉽게 구성할 수 있는 환경이 갖춰졌습니다. 그렇다면 이제 배포를 할 수 있도록 만들어야겠네요. 배포를 배포답게! - 배포 시스템 개선하기 (1)에서 보셨듯이 렌딧은 배포에 CodeDeploy를 사용하고 있는데요, 먼저 배포를 위해 dev1이라는 이름의 deployment group을 만들고, 새로 만든 테스트 서버를 한 대 넣어줍니다.

그런데 아까 테스트 서버에서 db-dev1.lenditprivate.xyz을 바라보게 하면 된다고 했는데, 만약 dev2 서버가 생겨서 db-dev2를 바라봐야 한다면 어떻게 할까요? 그래서 CodeDeploy의 DEPLOYMENT_GROUP_NAME 환경변수를 활용해서 spring의 datasource를 세팅해주었습니다. 이렇게 하면 dev2, dev3 등이 생기더라도 application 설정에 영향을 주지 않고 확장할 수 있게 됩니다.

1
spring:
2
  datasource:
3
    url: jdbc:mysql://db-${DEPLOYMENT_GROUP_NAME}.lenditprivate.xyz/database_name

소감

지금까지 설명한 방법으로 총 세 벌(dev1, dev2, dev3)의 테스트 서버와 DB를 구성해서 사내에 공지했고, 불과 1주일 만에 dev4를 만들어달라는 요청이 들어왔는데… 정확히 1시간 만에 모든 세팅을 마치고 기존 테스트 서버들과 동일하게 사용할 수 있었습니다. dev4를 세팅하면서 일부 미비했던 부분을 보강한 점을 감안하면, 아마 dev5를 구성하는 데는 30분 정도면 충분할 것이라 생각합니다.

사실 글에는 적지 않았지만 아직 자동화되지 않은 자질구레한 세팅들이 있는데요, 짬이 날 때마다 하나씩 자동화를 해 나가려고 합니다. Packer도 현재는 테스트용 AMI 생성에만 사용하고 있지만 production용 AMI나 다른 용도로도 사용할 여지가 있어 보여서 계속 관심을 가지고 볼 생각입니다.

테스트 환경 구축에 고민이 있으신 분들께 조금이나마 도움이 되었기를 바랍니다. 감사합니다.