본문 바로가기

DevOps/CI, CD

NextJS - AWS EC2에 무중단 배포/자동화

이 글은 아래 링크의 블로그를 따라하며 작성했습니다.

https://velog.io/@_woogie/%EB%B0%B0%ED%8F%AC%EB%A5%BC-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%B4%EB%B3%B4%EC%9E%90-feat.-Next-js-pm2-Nginx

 

----

이 글의 목표

git pull > npm run build > npx pm2 reload all 로 이뤄지는 배포 프로세스 자동화

 

이 글에서 구현할 배포 자동화/무중단 배포 로드맵

  1. IDE에서 깃 레파지토리로 코드 push
  2. 깃 레파지토리가 업데이트 되면, 연동되어 있는 Travis CI에서 이를 감지
  3. Travis CI는 업데이트된 레파지토리를 빌드/테스트
  4. 빌드/테스트된 코드를 AWS S3에 업로드
  5. Travis CI는 EC2 인스턴스의 CodeDeploy에게 배포 이벤트 트리거
  6. CodeDeploy는 AWS S3에서 업로드된 파일을 가져옴
  7. 가져온 파일을 EC2 인스턴스에 저장
  8. 배포 스크립트(blue-green 무중단 배포)를 실행해서 배포 완료 --> NginX 로드밸런싱 처리
  9. 결론적으로 개발자는 레파지토리에 push만 하면, 빌드/테스트/배포까지 모두 자동화되어 동작한다.

 

개발 환경

  • MacOS
  • VSCode
  • AWS EC2 인스턴스: RedHat Enterprise Linux

 

1. 버킷 생성

프로젝트의 파일들을 업로드 할 버킷을 생성합니다.

 

2. 사용자 추가

외/내부에서 AWS 서비스를 이용할 수 있는 권한을 가진 사용자를 추가합니다.

  • https://us-east-1.console.aws.amazon.com/iamv2/home
  • 왼쪽 메뉴 > 엑세스 관리 > 사용자 > 사용자 추가 버튼 클릭
  • "액세스 허용 - 프로그래밍 방식 액세스" 체크 후 다음
  • 기존 정책 직접 연결 클릭
    • AmazonS3FullAccess 체크
    • CodeDeployFullAccess 체크
  • 태그는 건너뛰고 완료
  • .csv 파일 다운로드

 

3. AWS 역할 만들기

CodeDeploy 사용을 위해 EC2 인스턴스에 액세스할 수 있는 권한을 부여하기 위한 IAM 역할을 만듭니다.

IAM이란 AWS 리소스에 대한 액세스를 제어할 수 있는 서비스입니다.

 

4. EC2에 역할 적용하기

EC2 인스턴스에 3번에서 만든 역할을 적용합니다. 인스턴스가 없다면 생성해서 적용해주세요.

 

5. EC2에 CodeDeploy Agent 설치하기

CodeDeploy는 애플리케이션 배포를 자동화하는 AWS의 배포 서비스입니다.

CodeDeploy를 설치하려면 먼저, EC2 인스턴스에 터미널로 접속해서 awscli를 설치해야 합니다.

아래 예제는 레드햇 리눅스 환경에서 설치한 예제입니다.

$ sudo dnf install python3-pip
$ sudo pip3 install awscli
$ aws --version

 

설치가 완료되면 configure 명령어로 awscli를 설정해 주세요.

$ aws configure
  • Acess Key ID / Secret Acess Key : 다운받았던 .csv 파일 내용을 입력합니다.
  • Default region name : 요청을 전달할 서버의 지역을 입력합니다. ( 서울은 ap-northeast-2 )
  • Default output format : 기본 출력 포맷을 지정합니다. ( json으로 설정함 )

 

설정 후 CodeDeploy 설치 파일을 다운로드 합니다.

$ aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2

 

위 명령어를 입력하면 현재 경로에 "install"이라는 파일이 다운로드 되고, 이 파일로 CodeDeploy를 설치합니다.

설치 파일 실행에는 Ruby가 필요하기 때문에 Ruby를 먼저 설치해야 합니다.

# 실행 권한 부여
$ sudo chmod +x ./install

# Ruby 설치
$ sudo dnf install ruby

# CodeDeploy 설치
$ sudo ./install auto

 

CodeDeploy를 설치한 후 CodeDeploy를 실행합니다.

$ sudo service codedeploy-agent start
$ sudo service codedeploy-agent status

 

6. 도커 설치

애플리케이션을 도커 이미지로 배포할 예정이기 때문에 위해 도커를 설치했습니다.

# yum에 도커 레파지토리 추가
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# container-selinux 설치
$ sudo yum install -y http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.33-1.git86f33cd.el7.noarch.rpm

# docker-ce 설치
$ sudo yum install -y docker-ce --nobest

# docker 서비스 시작
$ sudo systemctl enable --now docker

# docker 서비스 상태 확인
$ sudo systemctl status docker

# 도커 버전 확인
$ docker -v

 

7. NginX 설치

리버스 프록시 서버로 사용할 NginX를 설치합니다.

프록시 서버에 대한 내용은 아래 링크의 글에서 정리했습니다.

https://sty110357.tistory.com/90

$ sudo dnf install nginx
$ sudo systemctl start nginx
$ sudo systemctl status nginx

 

리버스 프록시 예로써, 아래처럼 80번 포트로 요청이 오면 3000번 포트로 전달해주도록 설정할 수 있습니다.

기본 설정 파일은 nginx.conf이지만 sites-available, sites-enables 폴더를 만들어서 관리하는 것이 권장됩니다.

$ sudo vi /etc/nginx/nginx.conf
# /etc/nginx/nginx.conf

... (생략)

http {
  ... (생략)
  
  server {
    listen       80 default_server;
    listen       [::]:80 default_server;
    server_name  _;
    root         /usr/share/nginx/html;
    
    location / {
      proxy_pass http://127.0.0.1:3000;  
    }
    
    ... (생략)
  }
}
  • "location /" 블록에 proxy_pass를 사용하면, 요청을 내부의 다른 서버로 전달해줄 수 있습니다.

 

만약 리버스 프록시가 동작하지 않으면 SELinux 설정을 변경해 주세요.

https://sty110357.tistory.com/92

 

8. dockerfile

dockerfile은 docker 컨테이너의 빌드를 자동화해주는 파일입니다.

프로젝트 최상단에 dockerfile을 생성합니다.

FROM node:alpine

WORKDIR /usr/app

RUN npm install --global pm2

COPY ./package*.json ./

RUN npm install --production

COPY ./ ./

RUN npm run build

EXPOSE 3000

USER node

CMD [ "pm2-runtime", "start", "npm", "--", "start" ]
  • FROM : 컨테이너에 설치할 이미지 명시
  • WORKDIR : 경로 이동(= cd)
  • RUN : 도커 이미지를 빌드하는 동안 명령어 실행
  • COPY : 첫 번째 인자=dockerfile이 위치한 경로 기준의 복사할 파일, 두 번째 인자=복사된 파일을 저장할 컨테이너 상의 경로
  • EXPOSE : 외부에 공개할 컨테이너의 포트 번호 명시(명시 뿐이라서 실제론 docker run -p 를 사용해 됨)
  • CMD : 생성된 도커 이미지를 시작하는 동안 명령어 실행, 둘 이상의 CMD 명령이 있으면 마지막을 제외한 명령은 무시됨

 

9. Docker-Compose

Docker-Compose는 yaml 파일을 사용하여, 여러 개의 컨테이너로 이루어진 서비스를 구축하고, 실행하는 순서를 자동화하는 기능입니다.

Docker-Compose를 설치하는 명령어는 아래와 같습니다.

sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# .sh 파일이 docker-compose 명령어를 사용할 수 있도록 심볼릭 링크 생성
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

 

10. 무중단 배포

무중단 배포하는 방법으로 blue-green 방식을 사용했습니다. 코드가 수정되어 애플리케이션이 재배포되면, blue와 green이 번갈아가며 서버를 실행해주는 방법입니다.

 

docker-compose를 사용해서 dockerfile을 빌드해야 하기 때문에 프로젝트 최상단에 docker-compose.blue.yml, docker-compose.green.yml 파일을 생성해줍니다.

# docker-compose.blue.yml

version: '3'
services:
  nextjs: 
    build: ./
    restart: unless-stopped
    ports:
      - 3001:3000
# docker-compose.green.yml

version: '3'
services:
  nextjs: 
    build: ./
    restart: unless-stopped
    ports:
      - 3002:3000
  • blue는 3001번 포트, green은 3002번 포트에서 실행됩니다.

 

NginX에서 80번 포트로 들어온 요청을 3001, 3002 포트로 로드밸런싱 해주기 위해 nginx.conf 파일을 수정해줍니다.

# /etc/nginx/nginx.conf

... (생략)

http {
  upstream next-build-test {
    least_conn;
    server 127.0.0.1:3001 weight=5 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3002 weight=10 max_fails=3 fail_timeout=10s;
  }

  server {
    listen       80 default_server;
    listen       [::]:80 default_server;
    server_name  _;
    root         /usr/share/nginx/html;

    location / {
      proxy_pass http://next-build-test;
    }
  }
  
  ... (생략)
}

 

마지막으로, 무중단 배포를 실행할 deploy.sh 파일을 프로젝트 최상단에 만들어 줍니다.

#!/bin/bash

DOCKER_APP_NAME=node-koa-server

EXIST_BLUE=$(docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)

if [ -z "$EXIST_BLUE" ]; then
        echo "blue up"
        docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d

        sleep 10

        docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml down
else
        echo "green up"
        docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d

        sleep 10

        docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml down
fi
  • blue가 실행 중이면, green을 시작하고 blue를 종료합니다. 반대도 마찬가지입니다.

 

11. AWS CodeDeploy Application 생성하기

 

생성을 완료하고, 배포 그룹 탭의 배포 그룹 생성을 클릭합니다.

 

배포 그룹 생성 화면

  • 배포 그룹 이름: 적절한 배포 그룹 이름 입력
  • 서비스 역할 : 3번에서 만들었던 역할을 선택
  • 배포 유형 : 현재 위치
  • 환경 구성 : Amazon EC2 인스턴스
    • 태그 그룹 입력
  • 배포 설정 : CodeDeployDefault.AllAtOnce 선택
  • 배포 그룹 생성 클릭
  • 로드 밸런서 : 체크 해제

 

12. Travis CI 설정 파일에 deploy 설정하기

Travis CI는 깃 레파지토리와 연동하여 코드가 push 되면 이를 감지하여 빌드와 테스트를 해주는 CI(Continuous Integration) 툴입니다. CI 툴이기 때문에 직접 배포까진 하지 않고, CodeDeploy에게 이벤트를 트리거 합니다.

 

Travis CI를 사용하기 위한 사전 세팅

  • https://app.travis-ci.com/ 에서 회원가입
  • 이메일 인증
  • 깃 레파지토리를 연동
  • 계정에 사용 플랜 설정(체험판인 Trial 플랜으로 결제 카드를 등록하면 10,000 크레딧 제공)
  • Trial 플랜 설정 시 1,200원 상당의 비용이 발생

 

설정을 마치면, 프로젝트 최상단에 ".travis.yml" 파일을 만들어서 배포에 대한 설정을 해야 합니다.

language: node_js
node_js:
  - 14 #노드 버전
branches:
  only:
    - main
before_deploy:
  - rm -rf node_modules
  - zip -r next-deploy-test * #zip 파일 이름 명시
  - mkdir -p deploy #deploy폴더 생성
  - mv next-deploy-test.zip deploy/next-deploy-test.zip #만든 zip 파일을 deploy 폴더로 이동
deploy:
  - provider: s3
    access_key_id: $AWS_ACCESS_KEY #추후에 travis에서 설정
    secret_access_key: $AWS_SECRET_KEY #추후에 travis에서 설정
    bucket: next-deploy-test-jaynull #s3 버킷 이름
    region: ap-northeast-2
    skip_cleanup: true
    local_dir: deploy
    wait-until-deployed: true
    on:
      repo: sty1103/next-deploy-test #본인의 repository
      branch: main
  - provider: codedeploy
    access_key_id: $AWS_ACCESS_KEY
    secret_access_key: $AWS_SECRET_KEY
    bucket: next-deploy-test-jaynull #s3 버킷 이름
    key: next-deploy-test.zip
    bundle_type: zip
    application: code-deploy-application #CodeDeploy에서 생성한 애플리케이션 이름
    deployment_group: code-deploy-group #이전 단계에서 설정한 CodeDeploy 배포 그룹명
    region: ap-northeast-2
    wait-until-deployed: true
    on:
      repo: sty1103/next-deploy-test #본인의 repository
      branch: main
notifications:
  email:
    recipients:
      - sty110357@gmail.com #본인의 이메일

 

이제 레파지토리에 push하면, Travis가 S3에 소스를 업로드 하고, CodeDeploy에 이벤트를 트리거합니다.

이 때 CodeDeploy가 어떻게 해야할지 정의해야 하는데, 이는 appspec.yml로 정의할 수 있습니다.

이 파일도 마찬가지로 프로젝트 최상단에 만들어야 합니다.

version: 0.0
os: linux
files:
  - source:  /
    destination: /home/next-deploy-test #S3에서 가지고온 파일을 저장할 폴더
hooks:
  AfterInstall: #배포 후 실행할 명령
    - location: execute-deploy.sh
      timeout: 240

 

appspec.yml에서 설정했던, 배포 후 실행할 명령인 execute-deploy.sh을 프로젝트 최상단에 만들어 줍니다.

#!/bin/bash

cd /home/next-deploy-test #본인의 EC2 폴더 구조에 따라 변경
sudo sh ./deploy.sh

 

마지막으로, .travis.yml 파일에 필요했던 Travis 환경변수를 설정하면 됩니다.

  • AWS_ACCESS_KEY, AWS_SECRET_KEY를 .csv 파일의 내용으로 추가합니다.

 

13. 마무리

이제 코드를 push 하면 배포가 자동적으로 이뤄집니다. 단, push 하기 전에 아래 사항을 먼저 확인해주세요.

 

  • AWS EC2, S3의 region이 서울로 되어있는지 확인
    • .travis.yml 파일에서 region을 서울로 지정했기 때문
    • region을 잘못 만들었다면, 다음 링크의 글을 확인 : https://ndb796.tistory.com/257
  • EC2 인스턴스에서 codedeploy-agent 서비스 재시작 해주기($ sudo systemctl restart codedeploy-agent)
    • IAM이 변경되면, agent를 재시작해줘야 적용되기 때문
  • execute-deploy.sh 파일에서 deploy.sh 파일을 실행하는 명령어 확인

 

이제 코드를 push 해서 배포가 잘 되는지 확인해보죠!

 

Travis CI에서 빌드/테스트/배포 트리거 성공 확인

 

CodeDeploy에서 배포 성공 확인

 

잠시 후 EC2 인스턴스에서 dockerfile이 build되면, 3001/3002 포트가 열렸는지 확인(처음엔 3001번이 정상)

 

NginX의 로드밸런싱이 잘 적용됐는지 확인

14. 마치며..

블로그 글을 따라하며 같은 환경을 구축해봤지만, 제 인스턴스의 OS가 레드햇 기반이라 명령어가 일부 달랐고, 특히 EC2 인스턴스의 region을 미국 동부로 해놔서(...) 인스턴스를 서울로 이전하는 등의 삽질을 많이 했습니다. 삽질한 만큼 알게된 게 많았고, 새로 배운 것도 많았던 경험이었습니다.

'DevOps > CI, CD' 카테고리의 다른 글

Docker Ubuntu 22.04에 Jenkins 설치  (0) 2023.09.05
GKE에 GitLab CI/CD 적용하기  (0) 2023.01.11