기술 블로그

AWS ECS로 PR마다 독립된 프리뷰(Preview) 환경 구축해 개발 병목 개선하기

개발 환경 하나에 모든 기능이 몰리던 QA 병목을, 기존 ECS 인프라 위에 PR 단위 프리뷰 환경을 띄워 풀어낸 과정을 공유합니다.

AWS ECS로 PR마다 독립된 프리뷰(Preview) 환경 구축해 개발 병목 개선하기

최화재BlazeCTO

들어가며

안녕하세요, 피클플러스 CTO(최고 기술 책임자) 블레이즈입니다.
지난해 피클플러스에 합류해 좋은 동료들과 함께 팀의 기술 기준을 높이고, 저희만의 개발 문화를 만들어 왔습니다. 그 과정에서 했던 고민과 일하는 방식을 나누고, 저희도 한 단계 더 성장하고자 기술 블로그를 시작합니다.
첫 글에서는 내부에서 체감이 가장 컸던 프리뷰 환경 구축기를 소개하려고 합니다.

피클플러스는 그동안 dev(개발)와 prod(운영), 두 개의 환경으로 서비스를 개발하고 운영해 왔습니다. 팀이 작고 동시에 진행하는 작업이 많지 않을 때는 비용 면에서도 효율적인 구조였는데요. 팀이 커지고 AI 덕분에 생산성도 높아지면서 병렬로 개발하는 기능이 늘었고, 하나뿐인 dev 환경이 전체의 병목이 되기 시작했습니다.

이 글에서는 이 병목을 풀기 위해 PR마다 독립된 프리뷰(Preview) 환경을 도입한 과정을 공유합니다.
특히 Kubernetes가 아닌 기존 AWS ECS 인프라 위에서 이 환경을 구성한 이유와 방법, 그리고 운영하면서 고민한 비용과 관리 문제를 함께 다룹니다.

문제: 모든 기능이 dev 환경 하나로 모인다

피클플러스는 제품을 만드는 스쿼드 조직과 기술 영역을 맡는 기능 조직이 함께 있는 매트릭스 구조로 일합니다. 기존에는 QA를 dev 환경에서 진행했는데, dev 환경이 하나뿐이라 다음 배포에 나갈 기능을 모두 합친 뒤 한 번에 QA를 받아야 했습니다.

이 구조에서는 두 가지 문제가 반복됐습니다.

  • 커뮤니케이션과 관리 비용이 크다.
    • 배포할 기능을 dev 환경에 모으려면 스쿼드와 팀이 일정을 맞춰야 합니다. 한 기능이 늦어지면 나머지 기능도 함께 기다려야 해 일정 유연성이 떨어졌습니다.
  • 병목이 생긴다.
    • 개발이 끝났고 QA 엔지니어도 여유가 있는데, 다른 기능이 dev 환경을 쓰고 있으면 끼어들 수 없습니다. 앞선 QA가 끝나기만 기다려야 했죠.
    • 한 번에 몰아서 QA를 하다 보니 변경점만 빠르게 확인하기 어려웠고, QA 한 회차가 무거워진 만큼 QA 팀의 부담도 커졌습니다.
    • 간단한 업데이트, 디자인 변경 등은 QA 엔지니어에게 검토받지 않고 개발자, PM, 디자이너들의 확인만으로 충분한 경우들도 많았습니다.

결국 문제의 핵심은 검증할 수 있는 환경이 하나뿐이라는 점이었습니다.

해법: PR마다 독립된 프리뷰 환경

그래서 선택한 방법이 프리뷰 환경인데요. 브랜치(PR)마다 독립된 인프라를 띄워서, 각 기능을 다른 기능과 섞지 않고 따로 확인할 수 있게 하는 방식으로, 이미 많은 회사가 도입한 DevOps 프랙티스이기도 합니다.

프리뷰 환경이 있으면 기능 하나가 준비되는 대로 바로 QA를 시작할 수 있습니다. 디자인 검수, 기획 확인, QA를 병렬로 진행할 수 있으니 배포 단위와 주기도 자연스럽게 작아집니다.

왜 Kubernetes가 아니라 ECS인가?

프리뷰 환경 사례를 찾아보면 Kubernetes 기반 구성이 많습니다. 저희도 인프랩의 PR Preview 환경 구축기 사례를 참고하기도 했는데, 저희 상황에는 Kubernetes가 맞지 않았습니다.

  • 기존 인프라가 이미 ECS 기반이다.
    • 서비스는 모두 ECS 위에서 운영되고 있었고, 배포 파이프라인과 운영 노하우도 ECS에 맞춰져 있었습니다.
  • 인프라 전담 인력이 없다.
    • Kubernetes 클러스터는 띄우는 것보다 계속 운영하는 것이 더 어렵습니다. 업그레이드, 애드온 관리, 장애 대응을 맡을 전담 인력 없이 도입하면 운영 부담이 크게 늘어난다고 판단했습니다.
  • 전환 비용이 크다.
    • 프리뷰 환경 하나를 위해 인프라 기반을 통째로 바꾸는 건 문제에 비해 과한 해법, 전형적인 오버 엔지니어링이었습니다. Kubernetes 전환은 회사와 비즈니스가 더 성장하고 팀의 기술 성숙도가 올라간 뒤에 다시 검토하려고 합니다.

그래서 다음과 같이 목표를 정했습니다.

이미 가진 ECS 인프라를 최대한 재사용해서, PR 단위로 격리된 환경을 띄운다.

현재 프리뷰 환경은 프론트엔드 모노레포를 대상으로 운영하고 있습니다. 피클플러스 웹은 경로에 따라 서로 다른 3개의 웹 서비스로 요청이 라우팅되는 구조라, 프리뷰 환경 하나에 이 3개의 웹 서비스가 모두 독립적으로 뜹니다.

사용 흐름: 라벨 하나로 생성부터 삭제까지

개발자가 프리뷰 환경을 쓰는 방법은 최대한 단순하게 만들었습니다. 별도 도구나 요청 절차 없이 PR에 preview 라벨을 붙이는 것이 전부입니다.

생성과 갱신

PR에 preview 라벨을 붙이면 GitHub Actions의 프리뷰 워크플로가 실행되고, 다음 세 단계를 거칩니다.

  1. check: 모노레포에서 이번 PR이 변경한 앱을 골라냅니다.
  2. deploy: 변경된 앱만 이미지를 빌드하고, 앱별 프리뷰 인프라를 순차적으로 생성하며 배포합니다. 변경이 없는 앱은 건너뜁니다.
  3. comment: 배포가 끝나면 PR에 프리뷰 환경 접속 URL을 코멘트로 남깁니다.

프리뷰 URL은 pr-{PR 번호}.preview.<도메인> 형태여서, PR 번호만 알면 누구나 바로 찾아 들어갈 수 있습니다.
이후 같은 브랜치에 커밋을 push하면 다시 빌드와 배포가 진행되어 프리뷰 환경이 최신 상태로 갱신됩니다.

변경된 앱만 배포하도록 만든 이유는 시간 때문입니다. 3개 앱을 매번 모두 빌드하면 배포가 오래 걸리기 때문에, 실제로 바뀐 부분만 다시 만들도록 했습니다.

또 앱별 배포는 순차로 진행하고, 중간에 실패하면 그 자리에서 멈추게 했습니다. 일부만 배포된 어중간한 상태에서 QA가 진행되는 것보다 빠르게 실패하는(fast-fail) 편이 낫다고 판단했고, 실패하면 Slack 알림으로 바로 대응할 수 있게 했습니다.

삭제

PR이 머지 또는 클로즈되거나 preview 라벨을 떼면, 워크플로가 destroy 단계를 실행합니다. 생성할 때와 같은 방식으로 앱별 프리뷰 인프라를 순차적으로 삭제합니다.

IaC 도입

이 과정에서 프리뷰 전용 인프라를 만들고 삭제해야 하기 때문에 Terraform과 Terragrunt를 도입했고, 워크플로 내에서 인프라 생성과 삭제를 독립적으로 수행할 수 있도록 했습니다.

아키텍처: 기존 dev 환경 인프라 위에 얹기

프리뷰 환경을 위해 인프라 전체를 복제할 필요는 없다고 봤습니다. 비용과 관리 복잡도만 늘어나기 때문인데요. 대신 이미 구성된 dev 환경 위에 필요한 것만 덧붙이는 방식으로 목표를 달성했습니다.


프리뷰 환경의 전체 구조는 다음과 같습니다.

새로 만든 것: 프리뷰 전용 진입점과 실행 환경

  • 프리뷰 전용 CloudFront
    • dev 환경과 진입점을 분리하기 위해 프리뷰 전용 CloudFront를 만들고, 와일드카드 서브도메인(*.preview.<도메인>)을 연결했습니다. 이로 인해 PR이 아무리 많아져도 DNS나 인증서를 PR마다 따로 만들 필요가 없습니다. CloudFront는 경로 기반 라우팅으로 요청을 각 웹 서비스에 보냅니다.
  • 프리뷰 전용 ECS 클러스터
    • 프리뷰 서비스는 dev 환경과 분리된 별도의 ECS 클러스터에서 실행됩니다. PR마다 3개의 웹 서비스가 각각 ECS 서비스로 뜨기 때문에, 프리뷰 환경마다 독립된 컴퓨팅 리소스가 할당됩니다.
  • 프리뷰 전용 이미지 리포지토리와 태스크 정의
    • 컨테이너 이미지와 태스크 정의도 프리뷰용으로 따로 관리합니다. 프리뷰 배포가 dev 배포 이력이나 이미지에 영향을 주지 않게 하기 위해서입니다.

재사용한 것: 로드밸런서와 일부 공용 리소스

  • dev 로드밸런서 공유
    • PR마다 로드밸런서를 새로 만들면 비용도 늘고 생성 시간도 길어집니다. 그래서 기존 dev 환경의 ALB를 그대로 공유하고, 호스트 기반 리스너 규칙으로 트래픽을 나눴습니다. 요청의 Host가 특정 PR의 프리뷰 도메인이면 그 PR의 프리뷰 서비스로, 그렇지 않으면 기존처럼 dev 서비스로 보냅니다. 프리뷰 환경 하나를 만들 때 필요한 건 ALB 전체가 아니라 리스너 규칙과 대상 그룹뿐입니다.
  • 보안 정책과 정적 리소스 공유
    • 보안 정책(WAF)과 정적 리소스(S3) 같은 일부 구성은 dev와 공유합니다. 프리뷰 환경의 복잡도를 낮추고, dev와 같은 조건에서 테스트할 수 있다는 장점이 있습니다.

접근 제어

프리뷰 환경은 dev 환경과 마찬가지로 외부에 노출되면 안 됩니다. 그래서 CloudFront 앞단의 AWS WAF에서 IP 허용 목록으로 접근을 제한해, 사내에서만 접속할 수 있도록 했습니다. 프리뷰 도메인 패턴이 단순한 만큼, 접근 제어는 도메인을 숨기는 방식이 아니라 네트워크 단에서 막는 방식을 택했습니다.

인프라는 코드와 모듈로

프리뷰 환경을 구성하는 리소스는 모두 Terraform과 Terragrunt로 관리합니다. 인프라 레포에 프리뷰 환경을 하나의 모듈로 정의해두고, GitHub Actions 워크플로가 PR 번호를 입력값으로 이 모듈을 apply 또는 destroy합니다.

덕분에 “PR 하나 = 프리뷰 스택 하나”라는 관계가 코드와 Terraform state에 명확하게 남습니다. 이 구조는 뒤에서 고아(Orphan) 환경 정리에서도 중요한 역할을 하게 됩니다.

운영: 비용과 정리

프리뷰 환경을 만들면서 신경 쓴 부분 중 하나는 비용이었습니다. 프리뷰 환경은 PR 수만큼 ECS 태스크가 늘어나는 구조입니다. 그대로 두면 비용이 계속 늘어나고, 정리되지 않은 환경이 계속 쌓이게 됩니다.

근무 시간 외 자동 셧다운

프리뷰 환경은 사람이 직접 확인할 때만 필요합니다. 아무도 보지 않는 밤 사이에 태스크가 계속 떠 있을 이유가 없죠.

그래서 EventBridge Scheduler와 Lambda로 시간 기반 셧다운을 구성했습니다.

  • 22:00 : 프리뷰 클러스터의 모든 ECS 서비스에서 목표 태스크 수(desiredCount)를 0으로 내립니다.
  • 07:55 : 출근 시간 전에 다시 태스크를 띄웁니다.

Lambda는 AWS SDK로 프리뷰 클러스터의 서비스 목록을 조회한 뒤 각 서비스의 desiredCount를 조정합니다. 서비스와 설정은 그대로 남겨 두고 태스크만 내렸다 올리는 방식이라, 아침에 출근하면 전날과 같은 프리뷰 환경을 그대로 쓸 수 있습니다. 이렇게 하면 하루 약 10시간 동안 프리뷰 태스크 비용이 발생하지 않습니다. 더불어 프리뷰 환경에는 큰 컴퓨팅 리소스가 필요하지 않아, 태스크 사양도 최소로 설정했습니다.

매일 자정, 고아(Orphan) 환경 정리

기본적으로 프리뷰 환경은 PR 머지, 클로즈, 라벨 제거 이벤트로 삭제됩니다. 하지만 실제로 운영해 보니 이벤트가 누락되거나 워크플로가 중간에 실패해서, PR은 닫혔는데 인프라는 남아 있는 경우가 생겼습니다. 이런 고아 환경은 눈에 잘 띄지 않기 때문에 조용히 비용만 쌓입니다.

그래서 매일 자정 EventBridge Scheduler가 CodeBuild를 실행해 고아 환경을 정리하도록 했습니다.

  1. GitHub API로 preview 라벨이 붙은 열린 PR 목록을 조회합니다.
  2. 실제로 떠 있는 프리뷰 환경 목록과 비교합니다. 열린 PR에 대응하지 않는 스택이 고아입니다.
  3. 고아 환경마다 Terragrunt로 destroy를 실행합니다.
  4. 실행 결과를 Slack으로 알립니다.

추가적으로 신경 쓴 부분은 다음과 같습니다.

  • 무엇을 지울지는 Terraform state로 결정합니다.
    • 고아를 정리할 때 AWS 리소스를 하나씩 찾아 지우지 않고, 생성할 때와 같은 Terraform 모듈로 destroy합니다. 어떤 리소스가 그 프리뷰 환경에 속하는지는 Terraform state가 가장 정확하게 알고 있기 때문입니다. 리소스를 빠뜨리거나, 반대로 지우면 안 되는 공유 리소스를 건드릴 위험이 줄어듭니다.
    • Terraform state는 S3를 백엔드로 지정해 관리합니다.
  • 정리할 게 없어도 알림을 보냅니다.
    • 고아가 0개인 날에도 “점검 완료” 알림을 보내도록 했는데요. 정리할 게 없었던 건지, 정리 작업 자체가 실패한 건지 구분하고자 했습니다.
  • 개인 토큰 대신 GitHub App을 씁니다.
    • GitHub API를 호출할 때 개인 액세스 토큰(PAT) 대신 GitHub App을 사용합니다. PAT는 특정 개인 계정에 묶여 있어서 계정 소유자가 퇴사하거나 만료기간이 지나면 자동화가 깨집니다. 공용 계정을 쓰거나 만료기간을 없애는 방법도 있지만, 보안상 좋은 방법은 아닙니다. GitHub App은 짧은 유효기간의 토큰을 발급받아 권한을 얻는 구조이므로 더 안전하다고 판단했고, 키는 AWS Parameter Store에 암호화해서 보관하고 실행할 때만 읽어오도록 했습니다.

앞으로의 계획: 백엔드까지 프리뷰로

현재 프리뷰 환경은 프론트엔드만 지원합니다. 프론트엔드 프리뷰는 백엔드 API로 dev 환경을 바라보기 때문에, 백엔드 변경이 함께 들어가는 기능은 여전히 프리뷰만으로 검증하기에 한계가 있습니다. 다음 목표는 PR 단위 프리뷰 환경을 백엔드까지 확장하는 것입니다.

백엔드 프리뷰에서 가장 어려운 부분은 애플리케이션이 아니라 데이터 계층입니다. 서버는 프론트엔드처럼 PR마다 띄울 수 있지만, DB와 캐시 등을 PR마다 물리적으로 새로 만들기는 비용과 운영 측면에서 현실적이지 않습니다. 따라서 다음과 같은 방향을 고려하고 있습니다.

  • DB: dev DB 안에서 논리적으로 분리하기
    • 현재 Flyway로 DB 스키마 형상 관리를 중앙화하는 작업을 함께 진행하고 있습니다. 스키마 변경 이력을 특정 레포나 ORM과 독립적으로 관리하면, 원하는 시점의 스키마를 언제든 재현할 수 있습니다. 이를 활용해 물리 DB 인스턴스를 늘리지 않고, dev DB 인스턴스 안에 PR별 논리 DB를 만들고 마이그레이션을 적용하는 방식을 생각하고 있습니다.
    • 다만 커넥션 고갈 및 컴퓨팅 리소스 관리, 초기 데이터 시딩, 가비지 컬렉션 등 고려할 점이 많습니다. 그래서 프리뷰 전용 DB를 따로 두거나 서버리스 DB를 쓰는 방안도 고려하고 있습니다.
  • Redis: prefix 격리 또는 논리 DB 분리하기
    • Redis도 PR마다 인스턴스를 늘리기는 어렵습니다. 그래서 하나의 Redis 안에서 prefix를 통해 키를 분리하거나, DB 번호(Logical database) 단위로 PR별 공간을 나누는 방법을 검토하고 있습니다.
    • prefix를 외부에서 주입하고 앱에서 받을 수 있는 형태로 변경이 필요할 수 있고, 마찬가지로 리소스 관리, 가비지 컬렉션을 통한 클린업 등도 필요합니다.
  • SQS: 프리뷰 환경별 큐로 물리적으로 분리하기
    • 메시지 처리에는 SQS를 쓰고 있습니다. SQS는 생성과 삭제가 빠르고, 유지 비용 없이 사용한 만큼만 과금됩니다. 그래서 프리뷰 환경별로 큐를 만들어 쉽게 격리할 수 있을 것으로 보고 있습니다.

마치며

처음에는 고민도 있었습니다. 스타트업이다 보니 개발할 것도, 해야 할 일도 많은데 “여기에 리소스와 시간을 써야 할까?” 싶었죠. 하지만 작업을 마치고 나니, 조직에는 개발 프로세스의 병목을 풀어야 하는 시점이 분명히 있다는 걸 체감했습니다.

도입 이후 프리뷰 환경은 한 달에 50개 정도 생성·삭제되고, 동시에 10~20개 정도가 유지되고 있습니다. 그런데도 AWS 월 비용은 도입 전과 거의 차이가 없습니다. 실무자분들의 피드백도 기대 이상이었습니다. 개발, 디자인, PM 팀 모두 숫자로는 다 담기 어려운 업무 효율 개선을 체감하고 있습니다.

기술적인 관점에서 얻은 교훈도 큽니다. 가장 큰 배움은 프리뷰 환경을 꼭 Kubernetes로 만들 필요는 없다는 것이었는데요. 기존 ECS 인프라에서 진입점(CloudFront)과 실행 환경(ECS)만 새로 만들고, 로드밸런서처럼 비용이 큰 리소스는 호스트 기반 라우팅으로 공유하는 것만으로도 PR 단위 격리 환경을 충분히 구축할 수 있었습니다. 만드는 것만큼 비용 효율적으로 운영하는 것도 중요하다는 점을 다시 한번 느꼈습니다.

인프라 전담 인력이 없거나 비슷한 상황에서 개발 병목을 고민하고 있다면, 작은 범위부터 프리뷰 환경을 시도해 보셔도 좋겠습니다.
이 글이 그 출발점에 작은 도움이 되었으면 합니다.

피클플러스에서 함께할 동료를 찾고 있습니다

피클플러스는 회사의 성장에 발맞춰 이런 문제들을 함께 풀어갈 동료를 찾고 있습니다. 저희 팀에 조금이라도 관심이 생기셨다면, 피클플러스 채용 홈페이지에서 편하게 문을 두드려 주세요.

주체적으로 서비스를 개선하며 등을 맞대고 함께 나아갈 동료를 기다립니다.

최화재

최화재

BlazeCTO

0 to 1과 1 to 10을 하는데 익숙하고, 보안 연구에서 출발해 극초기부터 시리즈B까지 다양한 스타트업에서 백엔드 시스템과 클라우드 인프라를 함께 설계, 구축, 운영해왔습니다. 현재는 피클플러스에서 CTO로서 훌륭한 개발자분들과 함께 멋진 제품과 팀을 만들기 위해 노력하고 있습니다.