Github Actions, Code Deploy, Nginx를 이용한 무중단 배포 및 SSL 적용 (3)

유제·2022년 7월 31일
0

저번 포스팅에서는 AWS Service(EC2, CodeDeploy, S3)를 세팅하고, 빌드 결과물을 압축한 후 S3에 올리는 작업까지 했습니다.

이번 포스팅에서는 S3에 올린 zip 파일을 EC2로 가져온 후 docker로 띄우는 작업을 하겠습니다. 아래 파란 부분에서는 docker 컨테이너가 두 개지만 일단 1개만 띄우겠습니다.

8. EC2에 CodeDeploy Agent 설치하기

Github Actions에서 Code Deploy deployment를 생성하는 과정이 제일 아래에 있었습니다.

 - name: Code Deploy
   run: aws deploy create-deployment --application-name $AWS_CODEDEPLOY_APPLICATION_NAME --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name $AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$GITHUB_SHA.zip

EC2에서 deployment를 감지하기 위해서는 CodeDeploy Agent가 설치되어 있어야합니다.
설치 방법은 CLI를 사용하여 CodeDeploy Agent를 설치하기를 참고하였습니다.

Ubuntu 20.04

  1. sudo apt update
  2. sudo apt install ruby-full
  3. sudo apt install wget
  4. cd /home/ubuntu
  5. wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
  6. chmod +x ./install
  7. sudo ./install auto > /tmp/logfile
  8. sudo service codedeploy-agent status

실행된 것까지 확인이 되었다면 다음 단계로 넘어가봅시다!

9. EC2에 도커 설치하기

  1. sudo apt-get update
  2. sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
  3. curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
  4. sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
  5. sudo apt-get update
  6. sudo apt-get install docker-ce
  7. sudo systemctl status docker
  8. sudo usermod -aG docker ${USER}
  9. 터미널 종료 후 SSH로 다시 접속하기
  10. docker

Docker-compose 설치하기

2022.07.31 기준 docker-compose의 최신 버전은 2.8.0이므로 해당 버전을 설치해주도록 하겠습니다. 최신 버전은 여기서 확인해주세요.
1. sudo curl -L "https://github.com/docker/compose/releases/download/v2.8.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
2. sudo chmod +x /usr/local/bin/docker-compose
3. docker-compose -v

10. appspec.yml 작성하기

애플리케이션 사양 파일(appspec.yml)은 CodeDeploy에서 배포를 관리하는 데 사용하는 파일입니다. 프로젝트 루트 경로에 생성하시면 됩니다.

appspec.yml을 작성해봅시다.

version: 0.0
os: linux # Amazon Linux, RHEL, Ubuntu Server일 경우 "linux", Windows Server일 경우 "windows"
files:
  - source: / # 인스턴스에 복사할 S3 파일의 경로입니다. / 로 설정하면S3_BUCKET_NAME/PROJECT_NAME/GITHUB_SHA.zip을 가져옵니다.
    destination: /home/ubuntu/my-nest-app # S3에서 가져온 파일을 저장할 위치입니다.
    overwrite: yes # 덮어쓰기를 허용하는 옵션입니다.

# files에서 가져온 파일들에게 권한을 어떻게 적용해야하는지 지정하는 곳입니다.
permissions:
  - object: / 
    pattern: '**'
    owner: ubuntu
    group: ubuntu

한 번 푸쉬를 하고 EC2에서는 어떤 일이 일어나는 지 확인해보세요! destination에서 지정한 경로에 빌드 결과물이 있습니다! 이제 스크립트를 추가해서 애플리케이션을 실행해봅시다. 스크립트를 추가하기 전에 Dockerfile을 먼저 작성해봅시다.

11. Dockerfile 작성하기

Nest 애플리케이션을 위한 Dockerfile을 작성해주겠습니다.

FROM node:alpine

# Docker container 안의 기본 workdir를 /usr/src/app으로 설정하였습니다.
WORKDIR /usr/src/app

# 현재 프로젝트의 package.json, package-lock.json을 docker container의 /usr/src/app로 복사합니다.
COPY package*.json ./

# 이미지 빌드시 실행되는 명령어입니다.
# 프로덕션을 위한 코드를 빌드하는 경우
RUN npm ci --only=production

# .dockerignore에 지정되어있는 파일 제외한 모든 파일을 docker container의 /usr/src/app로 복사합니다.
COPY . .

# docker container의 3000번 포트를 엽니다.
# EC2 내부에서는 해당 이미지를 사용하는 docker container의 3000번 포트에 접근할 수 있습니다.
EXPOSE 3000

# 이미지가 실행되어 docker container가 되는 시점에 실행될 명령어입니다.
CMD ["npm", "run", "start:prod"]

12-1. execute.sh 작성하기

저는 스크립트를 scripts/execute.sh에 넣어두었습니다.
위의 9번에서 나온 이미지를 보면 확인할 수 있습니다. 이렇게 하는 경우, 이미지 빌드시 스크립트에 대한 Dockerfile의 상대 경로를 지정해주어야합니다.

#!/bin/bash

DOCKER_IMAGE_NAME=my-nest-app

DOCKER_CONTAINER_NAME=nest-app

docker build -t ${DOCKER_IMAGE_NAME} ../ # <-- 여기서 Dockerfile의 경로를 제대로 지정해주어야합니다.

docker run -d -p 3001:3000 --name ${DOCKER_CONTAINER_NAME} ${DOCKER_IMAGE_NAME}

파일 경로와 관련하여..

위와 같이 상대경로를 지정할 수도 있지만 경로를 지정할 때 실수할 가능성이 있습니다. 그래서 스크립트 파일을 하나 더 만들어서 경로를 지정할 때 실수를 줄일 수 있도록 해보겠습니다. 필요성을 못느끼신다면 넘어가셔도 됩니다. 위에서 만든 execute.sh에는 아래와 같은 내용으로 대체합니다.

#!/bin/bash

# appspec.yml의 files.destination에 지정한 경로입니다.
# 실행 위치를 프로젝트 루트 경로로 수정해줍니다. 그리고 실제 docker 컨테이너를 컨트롤하는 스크립트를 실행합니다.
cd /home/ubuntu/my-nest-app
sh docker-script.sh

위와 같은 스크립트를 만들면 docker-script.sh 를 Dockerfile의 경로를 프로젝트 루트 경로에 대한 상대경로로 작성할 수 있습니다.

#!/bin/bash

DOCKER_IMAGE_NAME=my-nest-app

DOCKER_CONTAINER_NAME=nest-app

docker build -t ${DOCKER_IMAGE_NAME} . # <--- 프로젝트 루트 경로에 대한 상대경로

docker run -d -p 3001:3000 --name ${DOCKER_CONTAINER_NAME} ${DOCKER_IMAGE_NAME}

쉘 스크립트는 실행 위치가 영향을 준다.

쉘 스크립트가 아래와 같은 구조 안에 존재한다고 합시다.

home
└── parent
    └── child
 	    └── test.sh

tesh.sh의 내용은 아래와 같습니다.

#!/bin/sh

pwd

test.sh

  • /home/parent에서 실행하는 경우 Output : /home/parent
  • /home/parent/child에서 실행하는 경우 Output : /home/parent/child

위와 같은 차이점이 있어서 execute.sh를 통해서 쉘 스크립트의 실행 위치를 프로젝트 루트 경로로 이동시켰습니다.

정리

두 개의 쉘 스크립트 파일을 작성하면 됩니다.

  • execute.sh
#!/bin/sh

cd /home/ubuntu/my-nest-app
sh scripts/docker-script.sh
  • docker-script.sh
#!/bin/sh

DOCKER_IMAGE_NAME=my-nest-app

DOCKER_CONTAINER_NAME=nest-app

docker build -t ${DOCKER_IMAGE_NAME} . # <--- 프로젝트 루트 경로에 대한 상대경로

docker run -d -p 3001:3000 --name ${DOCKER_CONTAINER_NAME} ${DOCKER_IMAGE_NAME}

12-2. appspec.yml에 hooks 추가하기

CodeDeploy의 LifeCycle Event Hook의 순서는 아래와 같습니다.

조금 더 정확히 말하자면, 인 플레이스(in-place) 배포 상황에서 로드 밸런서 없는 배포 그룹의 경우 위와 같은 순서로 Hook이 진행됩니다. 공식 문서에서 더 자세하게 확인할 수 있습니다.

회색 표시된 hook은 스크립트를 추가할 수 없고, 그 외 hook에서 스크립트를 추가해야합니다.
저는 CodeDeploy Agent가 S3에서 zip 파일을 가져와서 EC2 인스턴스에 설치했을 때 스크립트를 실행하도록 구현을 했기 때문에 AfterInstall hook만 사용했습니다. 제가 작성하는 방식은 best practice가 아니므로 여러 시도를 한 번 해보세요!!

appspec.yml에 hook을 추가해보도록 하겠습니다.

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ubuntu/my-nest-app
    overwrite: yes

permissions:
  - object: /
    pattern: '**'
    owner: ubuntu
    group: ubuntu
    
hooks:
  AfterInstall: # LifeCycle Event Hook의 이름을 의미합니다.
    - location: scripts/execute.sh # 스크립트의 위치입니다. files.destination에 정의한 경로에 대한 상대경로롤 작성해주세요.
      timeout: 600 # 스크립트 실행시, 여기에 지정된 시간을 초과하면 안됩니다. 최대 3600초입니다.
      runas: ubuntu # 스크립트 실행 시 가장하는 사용자입니다.
  

appspec.yml에 hooks까지 추가했다면 푸쉬를 날리고 배포가 완료될 때까지 기다려주세요. 그 다음 EC2의 3001포트로 접속하면 정상적으로 응답이 옵니다.

0개의 댓글