배포를 자동화해보자! (feat. Next js, pm2, Nginx)

개발자 왜?전·2020년 12월 29일
26

매번 sudo git pull > sudo npm run build > sudo npx pm2 reload all 매우 귀찮은걸?

왜?

AWS로 배포를 진행하면서 Local에서 테스트한 결과와 배포한 어플리케이션과는 꽤나 큰 차이가 있었다. 그러면서 매번 같은 코드를 쳐가며 확인하고 테스트했다. 이러한 귀찮음과 더불어 무중단 배포의 욕심에 Travis-CI와 docker, Nginx 그리고 AWS를 이용해 배포자동화 환경을 구축하기로 했다.

이 글은 그 과정을 적은 포스트이다.

AWS EC2를 통해 웹애플리케이션이 배포중인 상태입니다.
또한 Travis CI를 통해 Jest 테스트하였습니다.
Nginx설정이나 Dockerfile등 각각의 프로젝트에 맞게 수정하시면 됩니다.

구조

앞으로 진행할 구조는 다음과 같다.

  1. 먼저 IDE에서 작업한 결과를 git push한다.
  2. travis-ci에서 commit을 감지한다.
  3. travis-ci가 미리 작성한 테스트를 Jest와 Enzyme을 통해 검사(생략가능) 후 빌드한다.
  4. travis는 스스로 배포할 수 없기에 빌드한 파일은 S3버킷으로 보낸다.
  5. 배포과정은 Code deploy가 작업하며 과정에 대해 스크립트 또한 travis에서 정의하여 처리한다.
  6. Code deploy가 S3의 파일을 해당 인스턴스에 보내고, 인스턴스 내부에 정의된 배포 스크립트를 실행한다.
  7. 스크립트를 통해 Dockerize 배포된다. 이 과정은 Nginx 로드밸런싱을 통해 무중단으로 진행된다.

설정하기

travis연결은 생략하였습니다.

빌드한 코드를 저장할 S3 만들기

S3 페이지에 접속하면 버킷 console이 뜬다. 버킷은 S3에 저장되는 데이터의 컨테이너로 쉽게말해 저장공간이다.
해당 페이지에서 주황색 버튼인 버킷 만들기를 클릭하면 된다.

이후 버킷 이름을 설정하고 버킷만들기를 클릭하면 된다. 다른 설정은 건드리지 않아도 된다.

배포할 AWS사용자 추가하기

추가할 AWS사용자는 추후에 Travis CI에서 배포할 수 있도록 사용된다.

IAM console에서 액세스관리 > 사용자에 들어가면 사용자 추가라는 파란색 버튼이 있다.

사용자 추가를 클릭 후 사용자 이름을 적고 다음:권한 버튼을 클릭한다.

이후 기존 정책 직접 연결을 클릭한 뒤 AmazonS3FullAccessCodeDeployFullAccess를 검색한 후 해당 항목을 체크하고 다음 버튼을 누른다.

필요한 태그가 있다면 설정한 후 검토에 사용자 이름을 제외한 나머지 항목이 같으면 된다.

사용자 만들기를 하면 아래와 같은 화면이 나오는데, 이 때 .csv 다운로드를 클릭하여 csv 파일을 다운받아야한다. 해당 파일이 있어야만 증명을 통해 해당 사용자를 사용할 수 있다.

AWS 역할 생성하기

IAM역할은 다음과 같다.다시말해서 배포에 사용할 EC2와 CodeDeploy에게 올바른 역할로 권한을 부여하여 배포할 수 있다.

EC2와 CodeDeploy, 각각의 서비스에 맞는 역할 2개를 설정해야 한다. 먼저 EC2이다.

IAM역할만들기를 클릭하면 다음과 같은 화면이 나온다. 사용사례는 아래와 같이 EC2를 클릭한 후 권한을 넘어간다.

권한에서는 AmazonEC2RoleforAWSCodeDeploy를 검색 후 해당 권한을 클릭하면 된다. 정책이름에서 알 수 있듯이 codedeploy를 위한 EC2에 대한 접근을 허용하는 내용이다.

이후 태그는 건너뛰고 이름설정에 본인이 이해할 수 있도록 설정 후 완료하면 된다.

이어서 Codedeploy완 관련된 역할을 설정해줘야한다. 이것도 EC2 역할설정과 비슷하다.
똑같이 역할 만들기에 들어간 후 사용사례 선택에서 CodeDeploy를 선택한다.

이어서 다음:권한을 클릭하면 이전과 같이 정책이 뜨는데 정책이 하나(AWSCodeDeployRole)뿐이라 해당 정책을 클릭 후 넘어가면 된다. 해당 역할의 이름을 잘 설정하면 역할 생성이 마무리된다.

EC2에 이전단계의 역할 적용하기

EC2콘솔에서 인스턴스에 이전에 만들었던(첫번째, 사용사례: EC2) 역할을 설정해줘야 한다.

ec2콘솔에서 원하는 인스턴스 우클릭 후 > 보안 > IAM역할 수정에서 할 수 있다.

역할 수정 페이지에서는 아까 만들어두었던 역할을 설정하면 된다.

EC2에 CodeDeploy Agent설치하기

모든 설정이 끝났으면 이전단계에서 역할을 설정한 인스턴스를 터미널로 접속한다.
접속 후 먼저 awscli를 설치한다. 설치방법은 아래와 같다.

sudo apt-get install awscli

설치를 시작하면 설치과정이 나오고 도중에 y를 눌러 continue를 하면 설치가 완료된다.

설치 후 sudo aws configure명령어를 터미널에 입력한다. 입력하면 아래와 같은 화면이 보인다. 이 때 이전에 받았던 .csv파일에는 id와 key값이 적혀있는데 터미널에 해당 값을 입력해주면 된다.

2가지 값을 입력하면 Default region name [None]이 뜨는데 해당 리전에 맞게 입력하면 된다. (서울은 ap-northeast-2) 이어서 Default output format [None]이 뜨는데 json으로 입력하면 된다.

설정 후 codeDeploy에 관한 파일을 받아야 하는데 이는 aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2 명령어를 터미널에 입력하면 된다. 그러면 해당 파일이 다운받아져 있는 것을 확인할 수 있다.

위의 캡쳐화면에서 알 수 있듯이 x권한이 빠져있는 것을 볼 수 있다. 따라서 chmod +x ./install명령어를 통해 권한을 부여하면 된다. 또한 CodeDeploy Agent를 설치하기 위해서는 ruby가 필요하기 때문에(없는 경우에 설치하면 /usr/bin/env: 'ruby': No such file or directory가 뜬다) sudo apt-get install ruby를 통해 ruby를 설치한다. 설치 때 y를 눌러 continue하면 된다.

Ruby설치가 완료되었으면 sudo ./install auto명령어를 통해 CodeDeploy Agent설치하면된다.

설치완료 후 sudo service codedeploy-agent start를 통해 CodeDeploy Agent를 실행하고 sudo service codedeploy-agent status를 통해 현재 실행되고 있는지 확인할 수 있다. 정상적으로 완료되면 아래와 같은 화면을 볼 수 있다.

Docker 설치

먼저 리눅스에 Docker를 설치하고 도커이미지 파일을 만들어 배포할 예정이다. 그 첫번째 Docker를 EC2에서 Docker를 설치한다. 명령어는 다음과 같다.
curl -fsSL https://get.docker.com/ | sudo sh
설치 이후 docker -v를 명령어를 치면 현재 설치된 버전이 나온다.

Dockerfile만들기

예시로 쓰이는 futchall프로젝트Next js프레임워크를 사용하였습니다. 특히 Nginx를 활용하여 https를 구현하였고 애플리케이션을 pm2를 통해 백그라운드 실행하였습니다.

Docker Image는 해당 프로젝트에 맞게 설정하시면 됩니다.

지금의 자동배포화 이전에 Nginx와 Let's encrypt를 통해 https통신을 하고 있었다. 이에 nginx 또한 Docker로 컨테이너로 만들어 운영하려 했다. 그러나 추후에 진행할 무중단 배포에서 nginx컨테이너의 설정값을 next app컨테이너에 따라 유동적으로 변경하는데에 어려움을 느꼈다. docker-gen을 통해 blue-green deployment를 구현할 수 있을 것 같지만, 시간 제약이 있었다. 따라서 nginx는 컨테이너화 하지 않기로 했다.

Next의 구조는 다음과 같다. 그리고 괄호친 항목이 앞으로 만들 파일이다.

-- app/
    |-- .next/
    |-- pages/
        ㄴ-- index.js
    |-- node_modules/
    |-- package.json
    |-- docker-compose.yml(앞으로 만들 파일)
    ㄴ-- dockerfile(앞으로 만들 파일)

nginx설치와 https 같은 경우는 이전에 작성한 을 통해 확인할 수 있다.

nginx설정은 Web app컨테이너와 통신하기 위해 리버스 프록시 설정만 해주면 된다. 아래 코드는 Web app컨테이너를 ports 3000:3000으로 열어둘 것이기 때문에 3000포트를 통해 리버스 프록시하는 코드 예시이다. 해당 코드는 server블럭내에 넣으면 된다.

location / {
		proxy_set_header HOST$host;
		proxy_pass http://127.0.0.1:3000;
		proxy_redirect off;
	}

dockerfile은 next build 후 port 3000으로 listen하고 pm2를 통해 백그라운드 실행에 관한 설정파일이다.

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, copy와 같은 기본적인 문법은 별로 어렵지 않다.

이제 docker-compose파일을 만들어줘야 하는데 그전에 먼저 docker-compose를 설치하자

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

이후 아래와 같이 docker-compose.yml파일을 만든다.

version: '3'
services:
  nextjs: 
      build: ./
      restart: unless-stopped
      ports:
      	   - 3000:3000

next앱의 루트폴더에 docker-compose.yml파일이 있다. 또한 도커이미지를 만들기위한 Dockerfile이 루트폴더에 있기 때문에 build의 value를 "./"와 같이 설정했다. 본인의 dockerfile에 따라 혹은 사용하는 도커 이미지에 따라 설정을 바꾸면 된다.

이후 docker-compose up 명령어를 입력하면 만든 이미지가 실행된다. 필자는 .dockerignore파일을 만들어서 node_modules를 등록해야 용량부족 없이 실행되었다.
참고로 docker-compose up -d를 통해 백그라운드에서 실행할 수 있다.

무중단 배포

무중단 배포에는 주로 사용되는 방식이 Rolling방식과 Blue-green방식이 있다. 전자는 2대의 서버를 작동시키면서 하나씩 수정된 사항을 배포하는 방식이다. 후자는 수정 사항을 새로운 서버를 통해 배포하고 신규서버가 완료되면 기존의 서버를 제거하는 방식이다. 이 글에서는 후자의 방식인 Blue-green방식으로 무중단 배포를 한다.

-- app/
    |-- .next/
    |-- pages/
        ㄴ-- index.js
    |-- node_modules/
    |-- package.json
    |-- docker-compose.green.yml(앞으로 만들 파일)
    |-- docker-compose.blue.yml(앞으로 만들 파일)
  	|-- blue-green.sh(앞으로 만들 파일)
    ㄴ-- dockerfile

먼저 기존에 사용하던 docker-compose.yml파일을 삭제하고 docker-compose.blue.ymldocker-compose.green.yml파일을 작성한다. 내용은 아래와 같다.

#green
version: '3'
services:
  nextjs: 
      build: ./
      restart: unless-stopped
      ports:
      	   - 3002:3000
#blue
version: '3'
services:
  nextjs: 
      build: ./
      restart: unless-stopped
      ports:
      	   - 3001:3000

두 파일의 차이점은 port설정이다. 3001포트로 서버가 실행되다가 aws code deploy로 변경사항이 deploy되면 3002포트로 서버를 실행하고 3001포트를 중지하는 방법이다. 이러한 방식은 Nginx의 로드밸런싱 기능을 통해 nginx가 3001포트와 3002포트를 로드밸런싱해야 한다.

이전 nginx설정에 아래와 같이 넣어주면 된다.

upstream 원하는 이름 {
                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 {
        server_name 서버의 도메인이나 IP를 넣어주세요;
        location / {
                proxy_set_header HOST $host;
                proxy_pass http://원하는 이름;
       }
}

upstream에 설정한 이름 그대로 server 블록 내의 location에서 proxy_pass에서 설정해주면 된다.

이제 현재 어떠한 blue, green 컨테이너가 실행중인지 확인하고 이에따라 빌드를 새로하고 컨테이너를 새로 up하고 기존의 컨테이너를 down하는 shell(blue-green.sh)을 루트 폴더에작성하면 된다.

DOCKER_APP_NAME은 여러 테스트 결과 해당폴더의 이름과 docker-compose에서 작성한 sevice이름이 underscore( _ )로 묶인다.

#!/bin/bash

DOCKER_APP_NAME=본인의 docker service이름

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.sh를 터미널에서 실행하면 된다. 혹시 기존의 도커 이미지나 현재 실행중인 컨테이너를 삭제하려면 docker rm 컨테이너 ID, docker rmi 도커이미지 ID를 하면 된다.

AWS CodeDeploy application생성하기

먼저 CodeDeploy application탭에서 애플리케이션 생성을 클릭한다.

다음 애플리케이션 이름을 설정하고 컴퓨팅 플랫폼을 EC2/온프레미스를 설정해준 후 애플리케이션 생성을 누른다.

생성을 누르면 바로 생성한 해당 애플리케이션 세부사항이 뜬다. 혹시라도 안뜬다면 생성한 애플리케이션을 클릭 후 배포그룹탭을 누르면 된다. 이제 배포 그룹생성을 클릭하면 된다.

먼저 배포그룹 이름을 설정해준다. 이후 서비스역할을 설정해줘야하는데 이전에 AWS 역할생성하기에서 만든 역할을 선택해주면 된다.

다음으로는 배포유형은 현재위치, 환경구성은 Amazon EC2 인스턴스를 선택한다. 또한 배포설정은 CodeDeployDefault.AllAtOnce이다.

travis파일에 deploy설정하기

Travis는 스스로 배포할 수 없고 이러한 기능을 지원해준다. 그리고 이러한 배포할 때 필요한 설정은 .travis.yml파일에서 가능하다. 설정은 다음과 같다.

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

Travis는 코드를 S3에 업로드하고 CodeDeploy 이벤트를 실행한다. 그리고 이 때 CodeDeploy가 어떻게 해야할지 정의해야 하는데 이는 appspec.yml로 정의한다. 해당 파일을 루트폴더에 작성한다.

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

또한 배포 후 실행할 명령인 execute-deploy.sh파일을 루트폴더에 만들어 준다.

#!/bin/bash

cd /home/ubuntu/futchall #본인의 EC2 폴더 구조에 따라 변경
sudo ./deploy.sh > /dev/null 2> /dev/null < /dev/null &

Travis 환경변수 설정하기

먼저 환경변수 설정을 위해 해당 repository의 설정에 들어간다.

설정의 Environment Variables에서 Name에는 AWS_ACCESS_KEY, AWS_SECRET_KEY를 Value에는 이전에 .csv파일로 받은 ACCESS_KEY와 SECRET_KEY를 각각 넣어주면 된다.

마무리

이제 vscode에서 push를 진행해보자.
travis에서 test가 진행되고 빌드가 완료되면
이후 AWS의 CodeDeploy에 접속해 이전에 만든 배포그룹에 들어가면 아래와 같은 화면을 만날 수 있다.



이렇게 약 1주간 진행한 배포자동화 및 무중단 배포가 마무리되었다. 도커에 대한 이해와 사용법부터 AWS의 IAM이나 정책에 관한 사용, 더 나아가 Nginx의 이점에 대해 체감했다.

순서나 구조를 이해하면 생각만큼 두려운 과정은 아니었다. 단지 Mac에서 mysql을 사용하기 위해 docker를 얼핏 알았다면 이제는 dockerfile syntax나 docker-compose를 이해하며, 도커의 편리함을 느꼈다. 더불어 AWS 기능의 다양한 사용과 여전히 사용하지 못한 수많은 기능을 보며 나 자신을 다잡았다.

이러한 환경을 설정 후 이전과는 비교할 수 없는 효율을 얻었다. 갈수록 빨라지는 개발, 배포의 상황에서 DevOps에 대한 고민과 기초를 맛 볼 수 있었다. 더 나아가 오픈소스나 프로그램의 사용법 뿐만 아니라 하드웨어나 아키텍쳐에 관한 좀 더 심도있는 영역 또한 웹 개발자로서의 필요충분조건이라 생각했다.

이러한 공부도 게을리 말아야겠다.

profile
하고 싶어 개발하는, 능동개발자

0개의 댓글