GitHub Actions + AWS EC2 + Docker + Nginx 기반 Blue/Green 무중단 배포 구축(1)

동준·2024년 10월 29일
0

개발일지

목록 보기
8/9

1. 배경

1) 개요

한창 취준하면서 공부도 병행하던 와중, 예전 팀 프로젝트에서 아키텍처 팀이 무중단 배포를 구현했던 기억이 있다. 그 당시에는 워낙 프로젝트 데드라인에 쫓겨서 코드 리뷰가 미흡했는데 이제서야 해본 코드 리뷰 결과, 당시 팀원이 AWS Deploy를 기반으로 무중단 배포 구현에 성공했었다.

팀 회고에서, 도커를 적극적으로 활용하지 못하고 활용한 부분에서도 이해가 전제되지 않았던 게 아쉬웠던지라 사이드 프로젝트나 스터디 프로젝트에서 도커를 애용 겸 연습했는데 일단 잘 모르겠지만 무작정 스스로 무중단 배포를 구현하고 싶었다. 겸사겸사 진행 중인 MSA 마이그레이션 및 실시간 통신 성능 계측 프로젝트에서의 무중단 배포 구현에 대해 고민도 할 겸.

목표는 블루 그린 무중단 배포를 도커 중점으로 구현하는 것

2) 무중단 배포

보통 배포는 한 번 하고 끝나지 않는다. 성능 개선이나 기능 교체가 있으면 서비스 중인 곳에 배포를 새롭게 수행해야 한다. 이 과정을 순서로 표현하면...

  1. 기존 WAS가 배포돼서 서비스되고 있다
  2. 새로운 기능이 도입돼서 해당 내용을 추가한 새 버전 WAS 배포가 필요하다
  3. 기존 WAS를 중단하고 새 버전 WAS를 배포한다
  4. 중단한 시점에 트래픽이 몰리고 있었다면...?😱

저 중단된 시간이 설령 찰나라고 해도, 초당 몰리는 트래픽이 존재하는 경우라면 서비스 사용자 입장에서는 불편함을 유발할 수도 있고, 기업 입장에서는 수익적인 문제로도 이어질 수 있다. 이는 서비스 운영의 측면이고...

개발의 관점에서 봤을 때, 무중단 배포는 CI/CD(지속적 통합/지속적 배포) 파이프라인을 통해 자동화된다. 보통의 배포는 수동으로 관리함으로써 개발 비용이 소모되지만 이를 절감할 수 있게 된다. 뿐만 아니라, k8s 등의 도입을 통해 스케일링 최적화까지 이끌어낼 수 있으며 장애 대응책의 밑바탕으로써 활용될 수도 있다.

무중단 배포에는 순차적 업데이트를 통해 부하 분산을 기대할 수 있는 롤링 업데이트, 소규모 사용자 그룹에게 우선 배포해서 문제 확인 후에 점진적으로 확장하는 카나리 배포 등이 있으나 이번에 연습할 내용은 별개의 운영 환경(보통 블루, 그린으로 나뉨)을 유지하면서 트래픽을 전환하는 블루/그린 배포로 구축할 생각이다.

3) 사전 배경지식

  1. AWS EC2 인스턴스 생성
  2. 간단한 SpringBoot 기반 WAS 구현
  3. Docker 활용법 & Docker hub 계정 보유

2. AWS EC2 환경 설정

1) 탄력적 IP & 보안 그룹 규칙

나는 ubuntu를 기반으로 EC2 인스턴스를 띄웠는데 그 이유는 APT 패키지를 통해 쉽게 소프트웨어를 관리할 수 있어서다. EC2를 그냥 생성하면 외부 접근용 퍼블릭 IP가 계속 변동되기 때문에 만약 인스턴스를 셧다운시켰다가 다시 올리면 WAS 설정파일 및 기타 관련 내용에서 배포 IP로 접근하기 어려워지고 이는 무중단 배포에서 이뤄질 자동화 과정에서도 똑같이 적용된다. 그렇기 때문에 무중단 배포를 구축하려면 HTTPS 도입 혹은 최소한 탄력적 IP 도입은 필수다.

위처럼 AWS에서는 탄력적 IP 할당을 해둬서 접근 IP를 고정시킨다.


EC2의 방화벽 역할을 맡는 보안 그룹의 인바운드 규칙에서 WAS가 구동되기 위한 포트를 두 개 열어줘야 한다. 각 포트별로 구 버전과 신 버전이 동작하도록 번갈아 할당시키기 위함이다. 이를 통해 트래픽 스위칭을 수행하고 사용자가 느끼지 못하는 새에 배포가 이뤄진다.

AWS에서의 EC2 인스턴스 준비는 탄력적 IP 도입과 보안 그룹 세팅까지 하면 된다.

2) EC2 인스턴스 세팅 - Docker 설치

ubuntu 바탕 EC2 인스턴스에 익숙하다면 당연히 하겠지만, 나름의 학습 기록을 위해 추가한다. 일단 터미널로 EC2 인스턴스에 진입한다. 나는 mac 환경이어서 별도로 pem 파일을 저장해두고, 해당 키 파일을 통해 내가 띄운 EC2 인스턴스에 접속했다. 접속해서 항상 sudo 모드에서 모든 세팅을 진행했다.

루트 권한을 부여받아야 apt(Advanced Package Tool) 명령어를 통해 소프트웨어 패키지를 업데이트해서 최신화를 해야 도커 설치 환경을 갖출 수 있다. 근데 저 명령어 볼 때마다 아파트 생각나 이후, 도커 설치에 필요한 보조 툴들을 세팅한다.

# ubuntu 시스템 패키지 업데이트
apt-get update

# https 기반 서드파티 레포 및 툴 설치 
apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

도커 설치를 위한 추가 작업이 필요하다. 도커의 공식 GPG(GNU Privacy Guard) 키를 우분투에 설치할 수 있도록 도커 패키지 서명을 검증해야 한다. 또한, 도커 공식 패키지를 우분투 시스템에 설치할 수 있도록 공식 레포지토리에 추가한다.

# 도커 공식 GPG 키 추가
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# 도커 공식 apt 레포 추가
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

위의 과정까지 마무리하고 다시 한 번 더 apt-get update 명령을 내려주면, 시스템 패키지 업데이트를 하면서 도커 설치 준비가 완료된다.


이제 도커를 설치하자. 단순히 Docker만 설치하는 것이 아닌, Docker-Compose도 같이 설치한다. 그 이유는 번갈아 반복하는 배포 환경과 그로 인한 멀티 컨테이너의 관리를 통한 일관된 배포 처리를 위해서다.

# 도커 설치
apt-get install docker-ce docker-ce-cli containerd.io

# 도커 컴포즈 설치
curl \
    -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" \
    -o /usr/local/bin/docker-compose
    
# 도커 컴포즈 실행 권한 부여
chmod +x /usr/local/bin/docker-compose

위 단계까지 마무리하고 아래처럼 도커 및 도커 컴포즈의 버전 확인을 통해 확인이 되면 성공적으로 도커와 도커 컴포즈가 우분투에 세팅된 것이다.

3) Docker Hub 관련 세팅

도커 세팅과 더불어서 추가로 진행할 단계가 있다. 다음 포스팅에서 후술될 내용이지만, 업데이트된 WAS 내용을 EC2 인스턴스로 전달하기 위한 밑작업이 필요하다. 그 전달하는 방법은, 업데이트된 WAS를 도커 이미지화해서 나의 도커 허브로 Push한 다음, EC2 인스턴스에서 Pull을 받아 컨테이너로 구동시키는 것이다.

그렇기 때문에, EC2 인스턴스에서 나의 도커 허브에 접근하기 위한 세팅이 필요하다. 일단, 도커를 사용하면 당연히 도커 허브의 계정을 보유하고 있을 것이다. 해당 도커 허브의 username엑세스 토큰이 필요하다. 엑세스 토큰은 도커 허브의 개인 프로필에서 계정 설정으로 접근해서 Security 항목을 보면 Personnel Access Token을 생성할 수 있다.

도커 허브의 엑세스 토큰은 생성해둔 내용을 벗어나면 다시는 확인할 수 없기 때문에 별도의 메모장 등에 저장하는 것을 추천한다. 해당 내용은 EC2 인스턴스 외에도 깃허브의 시크릿 변수에서도 적용해야 하기 때문에 보관해둬야 한다.

엑세스 토큰까지 발급받았으면 다시 EC2 인스턴스로 돌아와서 터미널에 아래와 같은 명령어를 입력하고 Password에는 기억해둔 엑세스 토큰을 입력한다. 참고로 직접 입력하나 붙여넣기를 하나 아무 것도 안 뜰 텐데 원래 그렇다.

docker login -u <username>

정상적으로 입력이 완료되면 로그인이 성공했다는 메세지를 확인할 수 있다. 이제, EC2 인스턴스에서 나의 도커 허브로 접근이 가능해졌다.

3. Nginx 도입

1) Nginx의 역할

EC2 인스턴스에 도커를 설치한 이유는 세 가지다.

  1. WAS의 이미지 관리 및 그를 통한 컨테이너 구축 후 실행
  2. 무중단 배포 환경 구축에 필요한 툴(여기선 Nginx) 설치
  3. 무중단 배포 환경 관리

1번은 다음 포스팅에 작성할 GitHub Actions가 맡을 거고, 3번은 배포 완료 이후, 지속적인 배포에 있어서 도커가 맡을 역할이다. 2번을 설명하기에 앞서, 우리가 구현하려는 블루/그린 무중단 배포의 동작 원리를 다시 파악하자.

앞서 얘기했듯, 두 개의 포트(스프링부트 WAS일 경우 보통 8080과 8081)로 WAS를 번갈아 동작시키며 구 버전에서 신 버전으로 트래픽 스위칭을 하는 것이 블루/그린 무중단 배포의 핵심이다.

보편적인 웹 서비스의 구성은 클라이언트의 요청이 서버로 바로 넘어와서, 서버가 응답을 반환하는 방식이다. 여기서 서버에 띄운 앱 서비스의 버전 업데이트가 이뤄지면 구 버전의 앱을 중단하고 신 버전을 다시 띄워야 할 것이다. 그래서 구 버전의 WAS를 계속 동작시키면서 동시에 신 버전의 WAS를 띄우고, 신 버전 포트로 스위칭을 해야 하는데, 이 역할을 Nginx가 맡는다.

Nginx는 웹 서버이자, 리버스 프록시의 일종으로 덤으로 로드 밸런서의 역할도 수행할 수 있다. 다양한 기능을 제공하지만 현재 구축하려는 블루/그린 무중단 배포에서는 리버스 프록시로써 활용된다. 리버스 프록시는 클라이언트의 요청을 서버에게 보내고, 서버의 응답을 클라이언트에게 반환하는 중재자 역할을 맡는다. 이를 통해 로드 밸런싱, 캐싱, SSL 종료, 보안 등의 이점을 얻을 수 있는데 블루/그린 무중단 배포에서는 트래픽 라우팅 기능을 메인으로 해서 다양한 기능을 적용할 수도 있다.

이제 EC2에 Nginx를 설치하고 세팅해보자.

2) Nginx 설치 & 세팅

직접 ubuntu 인스턴스에 Nginx를 설치하는 것도 있지만, 미리 설치해둔 도커를 활용해서 더 간편하게 Nginx 설치가 가능하다. Nginx 도커 이미지를 도커 허브에서 Pull 받고, 컨테이너를 생성해서 실행한다.

# nginx image pull (tag: latest)
docker pull nginx

# nginx container create and run with detached mode
docker container run --name <container_name> -d -p 80:80 nginx

# check docker container health
docker ps

도커 기반으로 Nginx 컨테이너를 생성하고 실행하면 현재 EC2 인스턴스에 Nginx가 동작하고 있다. 이제 이 Nginx를 우리가 목표로 하는 블루/그린 무중단 배포에 맞춰 동작할 수 있도록 설정을 해줘야 한다.

일단 동작하고 있는 Nginx가 EC2 우분투 인스턴스에서 직접 작동시키는 게 아닌, 도커 컨테이너의 가상 환경에서 동작하고 있기 때문에 Nginx 컨테이너로 접속해야 한다.

# access docker nginx container bash
docker exec -it <container_name> bash

보통 Nginx의 설정 파일은 /etc/nginx/conf.d 경로에 default.conf 명칭으로 존재한다. 해당 설정 파일에 기본적인 내용들이 기재되어 있으므로 블루/그린 무중단 배포와 관련된 업스트림 세팅해당 경로에 inc 파일 작성이 필요하다. vim 작성 방법은 별도로 서술하지 않는다.

설정 파일에서는 블루 컨테이너와 그린 컨테이너에 대한 url 설정을 넣어둔다. 블루 컨테이너와 그린 컨테이너에 맞춰서 EC2 인스턴스의 private IP포트 번호를 부여해둔다. 참고로 일단 블루/그린 무중단 배포 구현이 우선이기 때문에 로드 밸런싱 설정은 처리하지 않았다.

inc 파일은 Nginx의 설정에서 업스트림된 컨테이너 url의 참조를 동적으로 변경한다. 즉, 설정 파일의 업스트림 설정이 블루 컨테이너와 그린 컨테이너의 url 표시 역할을 맡고, inc 파일이 컨테이너 url에 대해 참조하기 위한 명시 역할을 맡는 셈이다.

참고로 사진은 blue라고 뜨지만 초기에는 green으로 작성해야 한다. 사진이 blue로 되어있는 이유는 내가 이미 테스트를 하면서 변경을 확인했기 때문...ㅎ

작성한 inc 파일을 바탕으로 include 설정과 더불어 location에서의 proxy 세팅을 마무리하면 블루/그린 무중단 배포를 위한 Nginx 설정이 끝났다.

3) 나름의 트러블 슈팅

개인적으로 배포가 가장 어렵게 느껴지는 건, 이런 에러가 발생했을 때 앱 레벨에서의 에러 해결법보다 난해하고 과정을 디버깅하기 까다롭다고 생각해서다. 그래서 나중에 까먹지 않으려고 나름의 트러블 슈팅을 기록하고자 한다.

참고로 아래의 이슈는 직전까지 작성한 것들의 설치 및 세팅 등이 마무리된 시점에서 발생했다.

(1) 처음 배포 도메인으로 접근하면 에러가 발생

사실 기대했던 화면은 Welcome to Nginx!...라는 환영 인사였지만, 위와 같은 에러 발생 문구가 나를 반겼다. 설치와 세팅에는 나름 문제가 없었는데, Nginx 세팅이 잘못됐는지 확인하기 위해 실행 과정의 에러 로그를 분석하고자 했다.

(2) Docker 기반의 동작이므로, 에러 로그는 Docker를 통해 확인

에러 로그를 확인하려고 공식 문서를 뒤져보니, Nginx의 /var/log/nginx 경로에 error.log 파일에 에러 내용이 저장되어 있다고 한다. 접근해서 바로 확인을 하려고 했지만, 어째서인지 내용 확인이 안됐다. 기본적인 설정으로도 자동으로 에러 로그가 저장되는 것으로 알고 있었는데 혹시나 해서 설정 파일에도 error.log 파일 저장 및 저장 내용 레벨까지 명시했음에도 확인되지 않았다.

여러 소스들을 뒤져본 결과, 도커 환경에서의 Nginx는 stdout(표준 출력)으로 내보내기 때문에 cat 명령어 등으로 내용을 확인할 수 없다고 한다. 근데, 아직 default.conf 파일에서 해당 내용이 확인되지 않아서... 좀 더 알아봐야겠다. 결론은 도커 로그로 확인해야 한다.

docker logs <nginx_container_id>

도커 로그로 조회하니, EC2 인스턴스의 private IP에 할당된 포트 번호(8081)로의 연결이 거부된다고 한다. 그래서 해당 탄력적 IP로 접근했을 때 에러 페이지를 반환한 모양이다.

(3) WAS 배포가 이뤄져야 해결

현재 EC2 인스턴스가 점유하고 있는 포트는 다음과 같다.

도커 프록시와 관련해서 80번 포트를 점유하고 있고 연결이 거부된 8081 포트를 점유하고 있는 것은 없다. 그렇기 때문에 선점 문제는 아니라는 것을 확인했다. 나는 에러 로그에서 8081 포트 번호를 가리키는 것에 집중했다. 80 포트를 점유하고 있는 상황에서 왜 8081 포트로의 연결이 이뤄졌고 이것이 왜 거절당했는가?

다시 한 번 더 Nginx 설정을 확인해봤다.

8081 포트 번호를 담당하는 것은 그린 컨테이너와 관련된 업스트림이다. 그리고 설정 파일에서 동적으로 참조하는 변수는 green으로 초기화되어 있다. Nginx의 리버스 프록시로써의 역할은 클라이언트 요청을 업스트림, 즉 백엔드 서버로 라우팅시키는 것이라는 점을 고려해서 작업 플로우를 머릿속으로 그려봤다.

  1. 클라이언트의 요청이 Nginx의 리버스 프록시로 인해 80번 포트로 들어온다.
  2. Nginx는 이를 업스트림으로 라우팅한다.
  3. 현재 업스트림의 초기화는 green(포트 번호 8081)으로 되어있다.
  4. 하지만 현재 8081번 포트에는 띄워져 있는 내용이 아무 것도 없다.
  5. 그렇기 때문에 라우팅 처리가 됐으나 내용이 없어서 에러를 반환하는 것이다.

즉, Nginx 리버스 프록시를 통해 클라이언트에서 80번 포트로 들어오는 요청을 8081번 포트로 라우팅했으나, 현 시점에서는 8081번 포트로 띄운 WAS가 없기 때문에 에러를 반환시킨 것으로 잠정 결론을 내렸다.

GPT와의 토론(?)을 통해, GPT도 나와 같은 의견을 결론으로 내뱉긴 했지만... 사실 내 생각이 맞는지에 대한 5% 부족한 교차 검증 정도로만 활용했다.(사실 GPT 잘 안 믿거든)

여튼, WAS를 배포해서 다시 접근해서 확인해야 할 문제일 듯하다.


지금까지 진행한 내용은, EC2 세팅 + Docker 세팅 + Nginx 세팅이다.
다음 포스팅에서는 GitHub Actions의 빌드 및 배포의 자동화 스크립트를 작성해 CI/CD 파이프라인 구축을 마무리한다.

profile
scientia est potentia / 벨로그 이사 예정...

0개의 댓글