프로젝트를 본격적으로 수행하기 전에 CI/CD를 미리 구축해 놓기로 하였다. 🙌
CI/CD
CI/CD는 지속적 통합/배포 환경을 뜻한다. 여러 명의 개발자가 함께 작업하는 과정에서 꼭 필요한 작업이다.
CI를 통해 코드를 지속적으로 통합(PR 후 merge 과정으로 이해해도 좋음)할 때 이를 자동으로 테스트하여 충돌이나 버그를 최소화할 수 있으며, CD를 통해 지속적으로 통합된 코드를 자동으로 프로덕션 환경에 배포할 수 있다.
무중단 배포
CD 과정에서 무중단 배포가 존재하지 않는다면 이 세상 서비스들은 모두 업데이트 시 사용자를 blocking해야 하는 불상사가 일어날 것이다!! 이를 막기 위해 무중단 배포가 필요하다.
간단하게 무중단 배포 방식을 알아보자.
- Rolling 배포
구버전에서 신버전으로 점진적으로 트래픽을 전환한다. 이 때 발생할 수 있는 단점은 호환성 문제가 발생 가능하다는 점이다. 한 사람에게 구버전과 신버전의 어플리케이션이 동시에 서비스될 수 있다.이미 존재하는 것들을 서서히 바꿈 - 블루/그린 배포
트래픽을 한 번에 구버전에서 신버전으로 옮기는 방법이다. 현재 운영중인 것이 BLUE, 새롭게 배포할 환경이 GREEN이다. Blue와 Green을 나란히 구성해둔 상태로 로드밸런서가 트래픽을 Blue에서 Green으로 일제히 전환시킨다.
한 번에 한 개의 버전만 게시되고, 라우팅을 순간적으로 전환하는 방식이다. 이 때 단점은 자원이 두 배로 필요하다는 점이 있지만, 호환성 문제가 발생하지 않는다.새롭게 만들어두고 일제히 전부 전환 - 카나리 배포
카나리 = 가스에 예민한 카나리아 새를 활용해 누출을 감지했다는 썰에서 가져온 단어이다.
구버전에서 신버전으로 조금씩 트래픽을 넘긴다. 예상대로 이 방식도 Rolling 배포와 마찬가지로 호환성 문제가 발생할 수 있다. 이 때 특정 사용자들에게만 새로운 버전으로 접근되도록 라우팅 처리하여 극복할 수 있다.
이 또한 자원이 두 배 필요하다는 단점이 있다. 장점으로는 카나리 배포를 통해 A/B 테스팅을 해볼 수도 있고, 특정 서버에만 먼저 흘러보내 오류를 빠르게 감지하고, 이 때 기존 서버로 빠르게 롤백할 수 있다는 점이 있다.새롭게 만들어두고 서서히 바꿈
AWS Elastic Beanstalk
AWS를 사용하여 여러 방식으로 무중단 배포를 해볼 수 있으나, 이번에는 elastic beanstalk을 활용해보기로 하였다.
본래 EC2 + S3 + CodeDeploy + Nginx 아키텍처를 생각하고 구현 중에 있었으나 갓 AWS 멘토님께서 Elastic Beanstalk 쓰면 복잡한 과정이 생략되고 간단하게 다 된다는 말을 듣고 바로 구현 방향을 틀었다 🤣
Elastic Beanstalk(줄여서 EB)은 처음 들으면 거부감이 올 수 있는데, 전혀 그렇지 않다 !
EB는 AWS 에서 제공하는 오케스트레이션 서비스이다. 필요한 AWS 리소스를 수동으로 실행할 필요 없이, 앱 인프라 스트럭처의 배포, 스케일링, 관리를 자동화해준다.
아래에서 자세히 알아볼테지만, 로드밸런싱 처리, Nginx 설정, 오토 스케일링, EC2 생성을 정말 간편하게 해결할 수 있다!
EB 아키텍처 알아보기
EB 공식문서에 의하면 웹 서버 환경에서 EB는 아래와 같이 작동한다.
간단히 접근 로직만 살펴보면 ELB ----> Auto Scaling Group ---> EC2 형태를 띄고 있다.
EB에서 할당해주는 CNAME(URL)이 로드밸런서를 가리킨다. 도메인명으로 EC2와 비슷하게 (**.ap-northeast-2**.com) 할당된다. 이 로드밸런서는 Auto Scaling 앞에 위치하여 EC2 인스턴스를 자동으로 시작시켜주고, 이를 통해 애플리케이션의 증가하는 Load를 처리해준다. 애플리케이션의 Load가 감소하면 더 이상 너무 많은 인스턴스는 필요없다고 판단하여 인스턴스를 중지하지만 그래도 항상 최소 1개의 인스턴스는 실행 상태로 둔다.
EC2 자동생성 및 AutoScaling
EB Deploy 과정을 github action 로그로 살펴보자. 아래와 같이 EC2 인스턴스가 자동으로 생성되는 것을 볼 수 있다.
AWS EC2 인스턴스 화면에 들어가게 되면, 나는 아무 손대지 않았지만 아래와 같이 ! 자동으로 인스턴스가 EC2에 짠-하고 생성되어 있는 것을 볼 수 있다.
이 때 Docker image를 통해 배포하므로 버전 관리에 용이하며 Autoscaling 작업시 확장성과 일관성을 보장할 수 있다. EB 는 Docker와의 연동성도 훌륭하다.
LoadBalancing(+Nginx)
EB 구성 > 인스턴스 트래픽 및 크기 조정 구성 을 확인해보면 리스너 포트 80을 애플리케이션의 프로세스 포트 8080에 연결하는 과정을 볼 수 있다. 공식 문서를 살펴보면 EB는 Nginx를 리버스 프록시(Web Server)로 사용하여 애플리케이션과 인스턴스를 로드밸런서(ELB) 형태로 매핑해주는 것을 알 수 있다.
이미 Nginx 를 리버스 프록시로 사용하나 추가적으로 특정 요구 사항이나 사용자 정의 설정이 필요한 경우, .platform 디렉토리 하위에 .platform/nginx/nginx.conf 경로에 Nginx 웹 서버의 설정 파일을 작성하면 된다.
webserver로서의 nginx가 가지는 로그 처리, 캐싱 뿐만 아니라 proxy 서버로서의 프록시 버전, 위치 등을 표기할 수 있다. 아래는 예시이다. 여기 에서 설정에 대한 자세한 설명을 확인할 수 있었다.
user nginx;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 33282;
events {
use epoll;
worker_connections 1024;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf;
map $http_upgrade $connection_upgrade {
default "upgrade";
}
upstream springboot {
server 127.0.0.1:8080;
keepalive 1024;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
proxy_pass http://springboot;
proxy_http_version 1.1;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
access_log /var/log/nginx/access.log main;
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip off;
gzip_comp_level 4;
include conf.d/elasticbeanstalk/healthd.conf;
}
}
EB 단점
많은 장점을 가진 EB이지만, 단점 중 하나는 신규 인프라 구성 및 커스터마이징에 제한적이라는 것이다. 이를 커버하기 위해 aws에서는 커스텀을 위한 옵션을 제공하는데, .ebextensions/ 디렉토리 하위에 옵션을 작성하는 형태로 제공한다. .ebextensions 는 애플리케이션이 배포될때 최초에 실행되는 커맨드로, 우리는 Spring 애플리케이션을 배포하기 전에 수행해야 하는 초기화 작업 코드를 넣어주면 된다.
구성 파일은 .ebextensions 하위에 .config 확장명을 사용하며 따로 배포 전에 jar 파일과 함께 하나의 zip 파일로 묶어서 배포해야 제대로 적용된다. (참고 - AWS , 우아한 기술 블로그)
.ebextenstions/00-makeFiles.config
files:
"/sbin/appstart":
mode: "000755"
owner: webapp
group: webapp
content: |
#!/usr/bin/env bash
JAR_PATH=/var/app/current/application.jar
killall java || true
java -Dfile.encoding=UTF-8 -Dspring.profiles.active=prod -jar $JAR_PATH
이렇게 EB 환경설정을 모두 완료하고 github actions 의 배포 yml 파일을 잘 작성해주면 아래와 같이 CD가 완료된다 !
AWS 배포 정책은 아래와 같다. 우리 서비스는 이중에서 우선 RollingWithAdditionalBatch 배포 방식을 선택하였다. 아직은 서비스가 작고 트래픽이 많지 않으므로 RollingWithAdditionalBatch 가 적합하다고 판단하였으나 서비스가 커지면 Immutable/TrafficSplitting 또한 고려해보아야 할 사항이다.
AllAtOnce – 새 버전을 모든 인스턴스에 동시에 배포합니다. 배포가 수행되는 동안 환경에 있는 모든 인스턴스가 잠시 서비스 중지됩니다.
Rolling – 새 버전을 배치로 배포합니다. 각 배치는 배포 단계 동안 서비스에서 제외되므로 배치에 있는 인스턴스의 수만큼 환경의 용량이 감소합니다.
RollingWithAdditionalBatch – 새 버전을 배치로 배포하지만, 먼저 새로운 배치의 인스턴스를 시작하여 배포 프로세스 중에 모든 용량이 유지되도록 합니다. (롤링배포의 일종으로, 롤링배포는 10개의 인스턴스가 있다고 가정하면 1개씩 인스턴스를 최신 형태로 바꾸는 방법이고 RollingWithAdditionalBatch의 경우에는 한꺼번에 2~3개의 인스턴스를 업데이트한다.)
Immutable – 새로운 오토 스케일링 그룹을 만들어 단일 인스턴스를 배치합니다. 원래 있던 오토 스케일링 그룹과 같이 트래픽을 처리하며 새로 배치된 인스턴스의 상태가 양호하면 원래 있던 인스턴스의 수만큼 추가하여 배포합니다.
TrafficSplitting – 카나리 배포 방식입니다. 새로운 오토 스케일링 그룹을 만들고 로드밸런서에서 트래픽의 일정 부분을 보내 상태가 양호하다면 모든 트래픽을 새로 업로드한 인스턴스로 처리합니다.
업데이트_
1주 간 사용해본 결과, EB를 이용하면 IAM이나 S3 설정, 그리고 yml 파일 환경변수 설정 등을 매우 편리하게 할 수 있다. springboot 의 yml 파일에 들어가는 환경변수들을 기재해주지 않고 EB 속성 값으로 할당해주면 바로 들어가게 되어 안전하다는 장점이 있다.
추가로 RDS와 관련된 값들 또한 지정된 변수값(RDS_DB_NAME, RDS_HOSTNAME 등)으로 할당해주면 되어 편리하다.
그러나 ElasticBeanstalk 특성 상 이벤트(github actions CD)가 발생하면 EC2가 삭제되었다가 새롭게 생성이 되기 때문에 EC2 서버 내에 docker 형태로 넣고, 볼륨 마운트 하는 것이 쉽지 않다. 그리고,,,, Elastic Beanstalk 트러블 슈팅에 대한 문서화가 잘 되어있지 않다. (해당 블로그 글에 계속됩니다...)
'BackEnd > DEVOPS' 카테고리의 다른 글
[ELK] 텍스트를 이용해 키워드 추출 (0) | 2024.01.09 |
---|---|
[CD|Cloud RUN] Google Cloud Run으로 지속적 배포 구현하기 (2) | 2024.01.03 |
[Serverless|Cloud RUN] Google Cloud Run으로 JAVA WAR 파일 배포하기 (2) | 2024.01.02 |
[DEVOPS] Linux 환경에서 ELK metricbeat를 사용한 시스템 모니터링 (0) | 2023.09.02 |
[Kafka] Kafka 이론 및 실습(Window10 Local 환경) (0) | 2023.08.11 |