본문 바로가기

연재작/DevOps

AWS 무중단 배포 도입기 feat. CodeDeploy

문제 상황 )

실제 서비스를 제공하면서 느낀 것은 

"기획에서 요구했던 내용과 실제 사용자들이 사용하면서 필요하다고 느끼는 것이 매우 다르다"는 것이다.

어떤 부분은 핫픽스를 요구할 정도로 빠른 업데이트를 요한다.

 

요청이 올때마다 즉각적으로 수정을 하고 업데이트를 할 수는 있다. 하지만 그것의 배포는 다른 문제다.

현재 구축된 서비스 환경은 "중단 배포"를 사용하기 때문이다.

또한, 양해를 구하고 중간에 업데이트 공지를 한다고 하더라도

수십초의 다운 타임이 존재하게 되기에 이를 공지해주어야 하는 번거로움이 있다. 

이 때문에 우리는 실 사용자들이 잘 사용하지 않는 시간인 밤에 업데이트를 하게 되었다. 

(CDN을 사용하는 프론트엔드측이 이럴때는 부럽다.

캐시가 있고, S3를 사용하는 방식 자체가 서버가 중단되지 않아 다운 타임이 없다.)

굳이 운영 환경이 아니더라도, 개발 환경에서도 위와 같은 애로사항이 존재한다.

BE 서버 관리자들은 FE 및 다른 사용자들에게 위와 같이 언질을 미리 해주어야 한다.

여러모로 불편하다...

 

 

문제 원인 )

풀어서 설명하자면, 현재는 EC2 단일 인스턴스 위에 Docker Container에서 WAS를 사용하고 있다.

그렇기 때문에 서버를 업데이트하려면 다음과 같이 CI/CD 프로세스를 따라야 한다.

  1. Docker Container 종료
  2. Docker Container 삭제
  3. Docker Image 삭제
  4. 최신 Docker Image를 ECR로부터 pull
  5. Docker Container 실행

그렇기 때문에 업데이트 상황에서 서비스가 잠시 중단되는 것은 어쩔수가 없었고,

무중단 배포를 사용한 배포 프로세스의 개선을 생각해볼 수 밖에 없었다.

 

 

현재 인프라의 리소스 파악 + 선택지 리서치 )

일단 우리는 ELB를 사용하고 있다. 그 중 L7 로드밸런서인 ALB를 사용하고 있기 때문에

두 개의 EC2를 동시에 띄우고 인스턴스별로 로드밸런싱을 하는게 가능하다.

이러한 전제 조건 하에서 다음과 같은 무중단 배포 방식을 찾을 수 있었다.

  1. AWS Elastic Beanstalk 도입과 ALB의 조합
  2. CodeDeploy (단일 EC2 인스턴스 내에 컨테이너를 교체하는 방식)
  3. CodeDeploy (일시적으로 다중 EC2 인스턴스 ; 기존 인스턴스와 새로운 인스턴스가 존재.
    새 인스턴스가 활성화 된 이후 기존 인스턴스를 삭제)
  4. 단일 EC2 인스턴스에서 docker-compose에 3개의 컨테이너를 띄운다.
    Nginx를 리버스 프록시로, old, new-Spring-Boot Container 두 개를 두어서 Nginx가 로드밸런싱을 한다.

 

여기서 추가적인 제약사항으로 현재 EC2에는 CloudWatch Agent가 설치되어 있고,

Docker Volume에 쌓여져있는 로그를 CloudWatch Agent가 CloudWatch Log로 가져오는 방식이다.

로그의 중앙화를 Cloud Watch로 하고 있는데 이 방식이 너무 편하다..!

 

 

추가적으로 1번 Elastic Beanstalk의 경우, 만든 PaaS라서 "애플리케이션 배포"용으로는 상당히 간편한 측면이 있다.

하지만 세부적인 설정의 경우에는 조율이 좀 필요하다. + 기존 Docker Volume을 통한 사용은 번거로워 보였다.

그렇기 때문에 2,3 번. 그 중 OOM과 같은 것을 미연에 방지하기 위해서 (EC2 t2.micro는 위와 같은 현상이 꽤 자주 보이는 것 같다.

당장 구글에 t2.micro의 OOM 관련 블로그 포스팅만 수페이지가 나온다.)

EC2를 업데이트할 때 마다 주기적으로 내리는 과정이 필요할 것이라고 생각해 3번 방식을 채택했다.

 

 

 

실제 적용 과정

 

참조한 방식은 아래와 유사하다. 하지만 나는 여기에 cloudwatch agent의 설정이 추가되어야 한다.

https://twosky.tistory.com/61

 

[AWS] AutoScaling CodeDeploy Blue/Green CI/CD

Github Action, ECR, S3, CodeDeploy를 활용해 Blue/Green 방식의 무중단 배포를 해보려 합니다. 아래는 모든 과정이 끝난 후의 아키텍처입니다. 배포 시나리오 1. Github에 코드 push하면 Github Action 동작 2. ECR에

twosky.tistory.com

 

 

과정은 크게 다음과 같다.

 

  1. EC2에 code deploy를 위한 IAM 작업
    1. code deploy를 위한 Role (기존에 cloudwatch 정책이 EC2에 부여되어 있으므로, 이 정책에 추가) 수정
    2. 해당 IAM Role을 EC2에 권한 부여
  2. Auto Scaling Group 설정
    1. 현재 운영되는 EC2 인스턴스를 private AMI로 만들고
    2. 인스턴스의 모든 설정까지 시작 템플릿으로 템플릿화
    3. Auto Scaling Group을 새로 만들고, 이 시작 템플릿으로 인스턴스를 구성
    4. ALB의 대상 그룹을 Auto Scaling Group으로 변경 
    5. 기존에 있던 EC2 인스턴스를 ASG에 등록해야함 (안 할 경우, 현재 인스턴스가 없는 것으로 간주하고 계속해서 생성한다.)
  3. github에서 github action의 배포 스크립트를 다음의 내용이 들어가도록 수정한다.
    1. spring boot의 bootJar 빌드 및 Docker Image build 후 ECR에 push
    2. github actions에 저장된 secret 변수들을 저장한 파일을 .env에 입력해 S3에 업로드
    3. EC2에서 code deploy를 실행하도록 수정
  4. 추가로, appspec 폴더를 새로 만들고 여기에 appspec.yml 파일에
    - .env와 같은 설정 파일 복붙
    - 파일 권한 부여
    - install 전, 후 실행해야 할 명령어나 hook 이 있다면 이것을 추가해주어야 한다.

Auto Scaling Group의 설정은 위와 같이 해두면 된다.

 

더보기

나중을 위한 메모 & minor trouble shooting?

현재 개발 환경과 운영 환경에서 설치 및 설정되어 있는 점이 크게 두 가지가 달랐다.

 

1. 개발 환경은 위에 설명한 블로그의 방식과 유사하게 적용해도 되나,
추가적인 차이점이 있다면, 개발 환경은 ec2에 route 53으로 dev서버용 도메인이 연결되어 있고,

80포트 (HTTP TCP)로 들어오는 요청은 8080포트로 포트포워딩이 되어있는 상태이다.

콘솔에서 다음의 명령어로 이를 바꿀 수 있다.

sudo iptables -t nat -A PREROUTING -i enX0 -p tcp --dport 80 -j REDIRECT --to-port 8080

 

=> 이는 로드밸런서로 도메인을 옮기고, 보안 그룹을 다룰 때 염두에 두어야 할 것 같다.

=> 그리고 이런 방식의 포트포워딩은 ALB를 사용할 경우에는 사용할 필요가 없어 보이기 때문에 수정할 필요가 있었다.

이유 : enX0라는 값은 고정값이 아닐 뿐더러, EC2의 AMI가 달라(정확히는 OS가 다를 경우)
ENI가 달라 enX0 가 아니라 eth0와 같은 다른 값을 사용할 수 있기 때문에 항상 확인하고 사용해야 한다. 

=> 80으로 접근할 시 서버의 8080포트로 접근하도록 ALB에서 설정해주는 식으로 하자.

 

2. 반면, 운영환경은 로드밸런서가 이미 만들어져있는 상태이며, 443 이면 서버의 80으로,

80이면 ALB 도메인의 443포트로 리다이렉트 되는 식이다.

나중에 대상 그룹에서 타겟이 인스턴스가 아니라 auto scaling group으로 바꾸는 작업이 필요하다.

 

Minor Trouble Shooting

 

ALB와 ASG의 사용에 있어서, 헬스 체크는 정말로 중요하다.

헬스 체크를 통해서 현재 가용한 인스턴스의 개수를 판단하고, 헬스체크가 안되면 인스턴스를 늘리기 때문이다.

만약 헬스체크 설정이 잘못되서 계속해서 인스턴스가 생성된다면, 인스턴스가 무한히 증식하는 모습을 볼 수 있다.

(한 번 보고 식겁함)

나도 마찬가지로 계속해서 헬스 체크가 되지 않아 그러한 상황이 발생했는데,

이는 헬스체크를 통한 API를 생성해뒀지만, 이 API의 URI에 대해 JWT 인증을 제외하지 않아

계속해서 401이 뜨고, 200을 받지 않아 문제가 생기는 것이었다.

(하필 JWT 추가 적용과 겹치는 와중이라서 이런 문제가 생겼었다.)

 

해결은 다음의 두 가지 방식이 가능하다.

- JWT를 통한 API 인증이 진행 될 경우, 헬스 체크를 위한 API 호출의 URI는 인증 URI에서 제외해야 한다.

- 다른 방법으로는, 401을 헬스 체크 시 http status 코드에 추가 시켜서 적용하는 방식도 존재한다.

 

 

아래와 같이 인스턴스 단위의 블루/그린 배포가 가능해졌다!

 

 

 

 

결론 및 고찰, 보완 

1. 이제 운영서버에서 블루/그린 무중단 배포를 통해 배포시 고가용성을 확보할 수 있게 되었다.

다만, 여전히 평상시 서버 자체는 한 개의 인스턴스만 이루어져 있기 때문에 실질적인 고가용성이라고 볼 수는 없다.

가용성을 위해서는 물리적 이중화 / failover / 로드 밸런싱이 이루어져야 하는데

지금은 물리적 이중화도 되어있지 않고, 그렇기 때문에 failover도 없고, 로드 밸런싱도 없기 때문이다.

(ALB가 달려는 있으나 어디까지나 ACM의 인증서 발급 및 HTTPS 리다이렉팅을 위해 사용되는 것이므로
가용성 적인 측면에서 기여는 없다.)

 

2. 그러나 이를 개발 환경에서 적용하기에는 한계가 있다. 실제로 이 방식을 통해 배포해보니

기존에 있던 인스턴스가 완전히 제거되기까지 약 15분의 시간이 소요된다.

개발 환경은 수 많은 소규모 커밋들이 존재하고, 이에 대해서 매번마다 15분간의 무중단 배포를 진행한다면

가용성은 몰라도 개발 효율성 측면에서는 엄청나게 문제가 생길 것이다.

그렇기 때문에 개발 환경에서는 Nginx와 도커 컴포즈를 통한 컨테이너 단위의 blue/green 무중단 배포를 통해서 

개발 효율성을 확보하는 것이 좋아보인다.

 

3. 현재 상황에서 가용성을 위해 추가로 고려해야할 사항에는 다음이 있다.

Graceful Shutdown : 요청이 다 들어온 것을 마저 처리하고 docker가 내려가야 진짜 무중단 배포가 될 것이다.

server.shutdown=grateful 등의 spring boot 설정을 통해서 들어온 프로세스를 마지막까지 잘 처리하도록 해야 한다.

Connection Draining : ALB 설정에서 새 요청은 새 인스턴스로, 기존 인스턴스에 들어온 요청은 마저 처리하도록 설정해야 한다.

 

4. 추가적으로 고려해야 할 사항들

  • 만약 지금처럼 JWT를 통해 Cookie가 아닌 Session으로 관리하고 있다면, 세션 관리 전략을 따로 구상해주어야 한다. 
    • Redis를 통한 세션 공유를 통해서 해결하는 방식이 좋아 보인다.
  • 지금은 S3에 GitHub Actions의 Secret을 전부 다 입력하는 방식을 진행하는데, 이에 대한 보안적인 문제가 있을 수 있다.
    • S3에 업로드 되는 해당 .env파일들은 접근하지 못하도록 먼저 설계를 하자.
    • Secret을 이런식으로 두 군데에 저장하는 방식보다는, AWS SSM의 파라미터 스토어나 Secret Manager를 사용해보자.

 

 

====================== 아래는 참고한 레퍼런스들 ======================================

https://velog.io/@mminjg/Github-Actions-CodeDeploy%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-EC2-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94#%EB%B0%B0%ED%8F%AC-%EA%B7%B8%EB%A3%B9-%EC%83%9D%EC%84%B1

 

Github Actions, CodeDeploy를 이용한 EC2 무중단 배포 자동화

Github main 브랜치에 PushGithub Actions에서 AWS S3에 빌드 파일 및 Dockerfile, deploy.sh 등 업로드Github Actions이 AWS CodeDeploy에 배포 요청CodeDeploy가 배포 실행도커 빌드 및 실행소스코드를

velog.io

https://hotechstory.tistory.com/144#%EB%B0%B1%EC%97%94%EB%93%9C%20%EB%B0%B0%ED%8F%AC-1

 

[SSM_프로젝트] - GitHub Actions와 AWS Code Deploy를 활용한 CI/CD 적용 - (2)

AWS CodeDeploy이전 글을 통해서 GitHub Actions를 하는 방법과 현재 프로젝트에 어떻게 적용했는지 알아보았다. [SSM_프로젝트] - GitHub Actions와 AWS Code Deploy를 활용한 CI/CD 적용 - (1)기존의 젠킨스 구성최

hotechstory.tistory.com

https://cabi.oopy.io/archive/spring-codedeploy

 

CodeDeploy로 Spring 배포 과정

CodeDeploy란?

cabi.oopy.io

https://devlog-wjdrbs96.tistory.com/305

 

[AWS] Auto-Scaling CodeDeploy Blue/Green 자동화 배포

Auto Scaling, CodeDeploy로 Blue/Green 자동화 배포하기 저번 글 에서는 아래와 같은 아키텍쳐로 진행되었습니다.(저번 글을 한번 가볍게 보고 오는 것을 추천드립니다. AWS 서비스를 생성하는 과정에서

devlog-wjdrbs96.tistory.com