사용할 EC2는 EC2 기본 권한에 더해서 S3에도 접근 가능한 권한이 필요하다. 그러니 IAM에 가서 Roles를 만들자. entity type은 EC2에 부여할 거니까 AWS Service로 하고, Common Use Case인 EC2를 선택하고 다음으로 넘어가자. 그리고 s3를 검색해서 FullAccess 권한을 추가해주면 된다. 이름은 chalkak-ec2(필자 프로젝트명이 chalkak) 정도로 하자.
이제 EC2를 만들어줄 때 role을 chalkak-ec2로 주자. 만약 이미 만들어진 EC2에 줘야한다면 적용한 뒤에 한 번 재부팅이 필요하다.
나는 우분투를 사용하였고, 미리 사용해야하는 명령어는 다음과 같다.
# codedeploy-agent 설치
sudo apt update
sudo apt install ruby-full
sudo apt install wget
cd /home/ubuntu
# 내 경우에는 서울 리전이어서 이렇게 된다.
# 원래는 https://<storage name>.s3.<region>.amazonaws.com/latest/install 사용
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto > /tmp/logfile
sudo service codedeploy-agent status
# nodejs 설치(18.x)
sudo apt install curl
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install nodejs
# pm2 설치
sudo npm i -g pm2
필자가 만들 때에는 Deployment configurations를 먼저 건드렸지만, 어떻게 배포할건지 설정할 때 기존에 미리 준비해둔 설정(전부 다 새로운 배포환경으로 or 적어도 하나는 서비스 살아있게 무중단 배포 등) 중에 선택한다면 굳이 필요없다.
우리가 배포할 서비스를 애플리케이션에 등록하자.
이렇게 만들어주면 된다. 간단하다. 다음은 배포 그룹을 만들면 된다.
여기서 중요한 부분은 IAM에서 role에서 codeDeploy commonuse를 골라 만들어서 서비스의 권한을 설정하는 것과 어떤 방식으로 배포를 할지 정한다.
일단 단일 인스턴스로 배포를 원하는 경우에는 in-place, Amazon EC2 instances, CodeDeployDefault.AllAtOnce로 하고 그보다 아래에 있는 로드밸런서 설정은 풀어주자.
Amazon EC2 instances를 선택하게 되면 Tag로 어떤 인스턴스를 대상으로 배포할건지 지정할 수 있는데, Name Tag로 미리 만들어준 EC2의 이름을 적으면 된다.
그냥 이미지 저장에 쓰는 S3 만들듯이 하나 새로 만들어주면 된다.
권한을 EC2, S3, CodeDeploy에 접근할 수 있을 정도로 만들어주자.
그 다음에 유저에 들어가 Create access key를 눌러 생성하자.
얻어낸 키들은 나중에 깃허브 액션에서 쓸 수 있도록 리포지토리의 secret에 저장해놓자.
AWS_REGION은 서울의 경우에는 ap-northeast-2와 같이 저장해놓으면 된다.
name: Deploy NestJS Application
on:
push:
branches:
- dev # 임시로 dev, 원래는 main이 되어야함.
env:
S3_BUCKET_NAME: chalkak-s3 # 실행에 필요한 파일들이 담길 s3 버킷 이름
PROJECT_NAME: chalkak # 파일들이 들어갈 폴더 이름
AWS_CODEDEPLOY_APPLICATION_NAME: chalkak-app # 아까 codeDeploy에서 application 만들 때 쓴 이름
AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME: chalkak-deployment # delevelopment group
jobs:
deploy:
name: test and deploy nodejs app to S3 bucket
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-verison: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
# 여기까지는 CI 테스트. 코드에 문제없는지 확인.
# zip file로 저장하기 위해 압축 작업을 합니다. 크기를 줄이기 위해 필요없는 파일들은 정리합니다.
- name: Make a zip File
run: zip -r ./$GITHUB_SHA.zip . -x "node_modules/*" "coverage/*" "src/*" "test/*" "README.md" ".env*" "*.git*"
shell: bash
# AWS에 인증정보를 설정합니다.
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
# 만들어진 압축파일을 S3에 집어넣습니다.
- name: Upload to S3
run: aws s3 cp --region ${{ secrets.AWS_REGION }} ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip
# CodeDeploy에게 s3위치를 알려주며 해당 위치에 있는 zip 파일을 가지고 development group의 전략에 따라 배포하라고 하기.
- name: Request Deployment
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
이 과정에서 압축 파일의 이름을 동일하게 하고 싶다면 $GITHUB_SHA.zip 대신에 chalkak.zip이라고 쓰고 S3를 만들때 Versioning을 하면 될 수도 있겠으나 저는 $GITHUB_SHA.zip로 했습니다.
이제 dev 브랜치에 push를 하게 되면 자동으로 EC2에 배포가 되게 됩니다.
하지만 아직 끝이 아닙니다. 어느 EC2에 배포를 할 건지는 정했지만 배포의 동작은 정하지 않았습니다.
어떻게 앱이 배포될 것인지를 적은 appspec.yml을 봅시다.
version: 0.0
os: linux
# 아래 files는 압축이 풀린 뒤에 /로 압축된 모든 것을 사용하겠고,
# 그 위치는 /home/ubuntu/chalkak/에 두겠다고 선언합니다.
# 만약 존재하면 덧씌웁니다.(overwrite: yes)
files:
- source: /
destination: /home/ubuntu/chalkak/
overwrite: yes
# 파일의 모든 권한을 ubuntu로 설정합니다.
permissions:
- object: /
pattern: '**'
owner: ubuntu
group: ubuntu
# CodeDeploy의 배포과정에서 이벤트 훅이 여러개가 있습니다.
# BeforeInstall은 files의 destination에 파일을 넣기 전에 호출되는 훅이며,
# AfterInstall은 파일이 설치된 후에 호출됩니다.
# runas는 sudo 권한이라고 합니다.
hooks:
BeforeInstall:
- location: scripts/before_deploy.sh
runas: root
AfterInstall:
- location: scripts/after_deploy.sh
runas: root
각 이벤트 단계에서 미리 만들어둔 스크립트 파일로 명령어를 실행할 수 있다.
# scripts/before_deploy.sh
#!/bin/bash
REPOSITORY=/home/ubuntu
cd $REPOSITORY
sudo pm2 kill # pm2를 사용할 경우 pm2를 죽임
sudo rm -rf chalkak
# scripts/after_deploy.sh
#!/bin/bash
REPOSITORY=/home/ubuntu/chalkak
cd $REPOSITORY
cp ../.env* ./ # 비밀 env는 home/ubuntu에 만들어놓고 배포할 때마다 복사해서 씀.
sudo npm i
sudo pm2 start dist/main.js