해당 주제로 AWS Community Day 2021 에서 발표를 진행했습니다.
발표가 궁금하신 분들은 여기를 참고해주세요!
사각사각을 개발하며 아키텍처를 점진적으로 개선한 경험을 포스팅하려고 합니다.
사이드 프로젝트의 아키텍처는 총 세 단계가 존재합니다.
그리고 각 단계들의 문제가 무엇이었고, 왜 개선을 결심하게 되었는지 시간 순서대로 작성해보겠습니다.
아키텍처를 간단하게 설명해보자면 Github Action을 활용한 CI 파이프라인과 Code Deploy를 활용한 CD 파이프라인으로 나눌 수 있을 것 같습니다.
Ver 1.
의 문제점은 두 가지 였습니다.
각 문제점들에 조금 더 설명해보겠습니다.
Code Deploy를 활용해서 배포 자동화 파이프라인은 구축 했지만 배포할 때 마다 SpringBoot 애플리케이션이 재실행 되는 시간동안은 서버가 죽어있었습니다.
서버가 동작하지 않는 시간은 28초 정도로 짧다고 할 수도 있지만 저에게는 길다고 느껴졌습니다.
심지어 유저도 전혀 없었고 개발 단계였음에도 불구하고 무중단 배포를 구성하고 싶었습니다.
어쩌면 오버 엔지니어링이 아니었을까
하는 생각도 듭니다.
만약 EC2를 여러개 운영하고 싶다고 하면 개발자가 직접 EC2에 접속해서 JDK를 설치하고 Nginx를 설치하는 등 사전 작업이 필요했습니다. 현재 사용하는 호스트의 OS는 ubuntu였는데, 나중에 다른 OS로 변경하고 싶을 때는 어떤 사이드 이펙트가 있을지도 미지수였습니다.
그래서 어떤 호스트에서도 언제든지 실행 가능한 아키텍처
를 만들고 싶었습니다.
Ver 1
과 차이점을 먼저 살펴보겠습니다.
일단 백엔드의 CI 파이프라인을 살펴보면 Docker image build
과정과 Regist docker image on docker hub
과정이 추가 되었습니다.
또, CD 파이프라인에서 EC2 내부를 살펴보면 SpringBoot 애플리케이션과 Nginx 가 컨테이너로 동작하고 있는 것을 볼 수 있습니다.
이제 Ver 2
에서는 어떤 점이 개선 되었는지 살펴 보겠습니다.
Code Deploy에서 사용하는 쉘 스크립트를 고도화해서 무중단 배포가 가능해졌습니다.
하지만 동시에 많은 문제점을 낳습니다. 곧 다뤄보겠습니다.
또, Nginx와 서버 애플리케이션이 Docker Container로 동작하기 때문에 이제 더 이상 EC2에 접속해서 JDK를 설치하거나 Nginx를 설치할 필요가 없어졌습니다. 이제 Docker가 설치되어 있는 환경이라면 언제나 사각사각의 서버를 실행할 수 있습니다.
분명히 개선된 사항도 있지만 또 다른 문제점이 발생했습니다. 이번엔 Ver 2
의 문제점을 알아보겠습니다.
Ver 2
에는 Docker-Compose 파일도 있고, Nginx 설정 파일도 있고, Bash Shell Script도 있었습니다.
그리고 이 녀석들은 서로 강력하게 의존하며 유지보수성을 저해하고 있었습니다.
Ver 2
는 Nginx와 SpringBoot 애플리케이션이 도커 컨테이너로 실행되고 있습니다. 그리고 EC2에서 실행되는 모든 컨테이너들은 동일한 Docker Network 에서 실행됩니다.
Nginx 컨테이너는 HTTP 포트와 HTTPS 포트에서 실행되며 모든 요청에 따라 SpringBoot 컨테이너로 리버스 프록시 하거나 EC2내부에 배포되어 있는 프론트엔드(리액트)의 정적파일을 리턴합니다.
그리고 Nginx는 SpringBoot 컨테이너로 리버스 프록시할 때 Docker Compose에 정의되어 있는 서비스 네임을 기준으로 라우팅합니다. 그래서 Nginx 설정 파일에는 Docker DNS 주소가 반드시 Resolver
로 등록되어 있어야 했습니다. 이렇게요.
nginx.conf
파일의 일부를 가져왔습니다. docker dns
주소가 상수로 정의되어 있는 것을 볼 수 있습니다. 그런데 만약 Docker를 Kubernetes로 대체한다고 하면 resolver
를 개발자가 직접 바꿔줘야하는 상황입니다. 상수로 선언되어 있었기 때문입니다. 개발자가 기술스택을 kubernetes로 변경했지만 깜빡하고 nginx의 설정파일 수정은 깜빡 했다면? 휴먼에러가 발생하게 됩니다.
의존성에 의해서 유지보수성이 감소하는 순간입니다.
쉘 스크립트와 Docker Compose에서는 절대 경로가 사용되고 있었습니다.
이렇게 절대경로가 사용되고 있고 절대경로의 일부는 ubuntu
라는 호스트의 OS에 종속적인 경로도 포함되고 있습니다. 절대 경로도 마찬가지로 프로젝트의 디렉토리 구조가 변경되면 반드시 개발자가 수동으로 변경해줘야하는 상황이었고 에러가 발생하기 좋은 구조였습니다.
이 또한 의존성에 의해서 유지보수성이 감소하는 순간입니다.
위에서 설명 했지만 Nginx가 프론트엔드의 요청도 처리하고 있었습니다. 프론트엔드의 요청 뿐만 아니라 HTTPS 요청을 검증하는 역할도 했습니다. 로컬에 존재하는 SSL private key를 사용해서 요청을 검증까지 하고 있었습니다. 즉, 로컬에 반드시 SSL private key가 존재해야 했습니다.
Ver 1
에서 발생했던 문제가 똑같이 발생했습니다. Ver 1
에서는 JDK, Nginx를 설치해야 했다면 Ver 2
에서는 EC2를 생성할 때 마다 개발자가 EC2 인스턴스에 접속해서 명령어들을 직접 타이핑하며 SSL 인증서를 설정해둬야 했습니다. 저는 언제 어디서든지 애플리케이션을 실행 가능한 상태를 만들고 싶었습니다.
그래서 이번엔 AWS로의 이관을 결심하게 되었습니다.
CI 파이프라인은 전혀 변하지 않았습니다. CD 파이프라인에 많은 변화가 있었습니다.
천천히 살펴보겠습니다.
이제 개선점을 알아보겠습니다.
EB를 사용하며 자연스럽게 ELB, ACM, CF를 사용하게 되었고 Code Deploy, Docker Compose, Nginx를 사용할 필요가 없어졌습니다. 자연스럽게 절대경로, DNS Resolver 등록 등을 할 필요가 없어졌습니다. 그래서 모든 의존성이 사라지며 유지보수성이 크게 증가했습니다.
각 기술들을(Docker Compose, Nginx, Code Deploy) 설정하느라 250라인 정도의 텍스트를 작성 했다면 이제는 Elastic Beanstalk를 위한 설정 15줄만 작성하면 끝입니다.
그리고 모든 설정을 AWS Console에서 UI와 함께 설정할 수 있습니다. 이는 Bash 쉘 스크립트 문법, Docker-Compose 문법, nginx 설정 문법 등을 학습할 필요가 없어지면 개발자는 코드만 짜면 되는 환경으로 바꼈습니다.
SpringBoot 애플리케이션은 여전히 Docker가 설치되어 있는 환경에서는 언제든지 동작합니다. 또, SSL 인증서 관리를 ACM에게 위임하며 요청의 검증은 ELB 레벨에서 수행하기 때문에 EC2 내부에는 더 이상 SSL 인증서 세팅이 필요없게 되었습니다. 이제 더 이상 호스트에 의존하지 않습니다.
EB를 사용하면 Cloud Watch를 자동으로 연동해줍니다. 때문에 EB에서 사용중인 AWS Resousrce들을 시각적으로 모니터링할 수 있습니다. 헬스 체크, CPU 점유율, 요청의 수 등 많은 부분을 쉽게 모니터링하며 서비스의 상태를 더 수월하게 관리할 수 있게 되었습니다.
지금 당장! AWS Elastic Beanstalk를 도입합시다!
사이드 프로젝트에서 Nginx를 도커로 구성하면서 Ver2의 문제점을 겪었는데.... 갓 AWS