이전에는 Koyeb와 같은 무료 호스팅 플랫폼을 사용하여 자동 배포가 필요하지 않았으나
최근 AWS EC2로 이전하면서 배포가 필요할 때마다(주 1회 정도) 수동으로 코드를 가져오고 PM2를 재시작하여 배포를 진행해야 했다.
또한, 추후 프론트 서버도 EC2로 옮길 예정이므로 배포 관리가 복잡해질 것으로 예상되어 CI/CD가 필요하다고 판단하게 돼서 구축하게 되었다.
CI (Continuous Integration)
"CI"는 Continuous Integration(지속적인 통합)의 약자로, 빌드 및 테스트 자동화 과정을 의미한다.
CD (Continuous Delivery 및 Continuous Deployment)
"CD"는 Continuous Delivery 및 Continuous Deployment(지속적인 제공 및 지속적인 배포)의 약자로,
배포 자동화 과정을 의미한다.
name: build
on:
push: // 실행 조건 설정
branches: main // 브랜치 설정
pull_request:
branches: main
jobs:
build-check:
runs-on: ubuntu-latest // 깃허브에서 제공하는 가상환경 설정
strategy:
matrix:
node-version: [16.15.0] //노드 버전 설정
steps:
- name: source code.
uses: actions/checkout@v3
- name: ${{ matrix.node-version }} 버전의 노드로 세팅
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: 환경변수 설정
working-directory: ./
run: |
pwd
touch .env
echo DATABASE_URL=${{ secrets.DATABASE_URL }} >> .env
cat .env
- name: 패키지 설치
working-directory: ./
run: npm i
- name: 빌드 과정 시작
working-directory: ./
run: npm run build
- name: 빌드한 코드 압축
run: zip -r surfe.zip ./dist ./scripts ./appspec.yml ./.env ./package.json
- name: AWS 접속
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: S3에 압축된 서버 코드를 업로드
run: aws s3 cp --region ap-northeast-2 ./surfe.zip ${{ secrets.S3 }}
- name: AWS codeDeploy로 배포
시작
run: aws deploy create-deployment
--application-name test-codeDeploy
--deployment-config-name CodeDeployDefault.OneAtATime
--deployment-group-name test-deploy-group
--s3-location bucket=${{ secrets.S3BUCKET }},bundleType=zip,key=deploy/surfe.zip
위의 YAML 파일은 워크플로우를 설정한 것이다.
이 파일은 루트 폴더에서 .github/workflows 폴더 하위에 위치해야 합니다.
"main" 브랜치로 푸시 되면 GitHub 리포지토리의 Actions 탭에서 작업을 확인할 수 있습니다.
EC2에서 S3와 CodeDeploy를 사용하기 위한 권한 설정이 필요하다.
이 부분은 따로 사진을 못 찍어서 다른 블로그를 참고하면 자세히 나온다
sudo yum update
sudo yum install ruby
sudo yum install wget
cd /home/ec2-user
wget https://bucket-name.s3.region-identifier.amazonaws.com/latest/install
위 명령어는 Amazon Linux 2를 기준으로 작성된 것이고
Ubuntu와 같은 다른 운영 체제에서는 해당 OS에 맞는 방법을 찾아 설치해야 합니다.
appspec.yml 파일은 Codedeploy가 배포를 관리하는 애플리케이션 사양 파일이다.
배포 후 EC2에서 수행될 작업을 정의해 준다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/build // 빌드에서 산출된 것들을 EC2의 /home/ec2-user/build로 옮긴다.
overwrite: yes
permissions: // 권한설정
- object: /home/ec2-user
pattern: "**"
owner: ec2-user
group: ec2-user
hooks:
BeforeInstall:
- location: scripts/before-deploy.sh
timeout: 300
runas: ec2-user
AfterInstall:
- location: scripts/after-deploy.sh
timeout: 300
runas: ec2-user
루트 경로에 scripts 폴더를 만들고 그 안에 before-deploy.sh, after-deploy.sh 파일 추가
배포되기 전과 후에 실행시키고 싶은 명령어들이다.
// before-deploy.sh
#!/bin/bash
REPOSITORY=/home/ec2-user/build
cd $REPOSITORY
pm2 delete all
rm -rf build
// after-deploy.sh
#!/bin/bash
REPOSITORY=/home/ec2-user/build
cd $REPOSITORY
sudo npm i
pm2 start dist/bundle.js
aws에서 아래 처럼 상태확인이 가능하다.
기존에는 빌드 없이 배포를 진행하였는데 이번 과정에서는 빌드가 필요해서 node build 하는 방법을 찾아봤고
webpack으로 빌드 하게 되었다.
npm install -D webpack webpack-cli
npm install -D webpack-node-externals
//webpack.config.js
const nodeExternals = require("webpack-node-externals");
const path = require("path");
module.exports = {
mode: "production", // development 와 production 둘 중 선택
entry: { // bundle화할 때 시작하는 파일을 지정한다.
bundle: path.resolve(__dirname, "index.js"),
},
output: { // boutput 은 bundle 화 된 파일의 위치와 이름을 지정한다.
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
exclude: /node_modules/,
},
],
},
target: "node",
externalsPresets: {
node: true,
},
externals: [nodeExternals()],
};
이렇게 wecpack을 설정해 주고
npx webpack
output에 설정한 위치로 빌드 된 파일이 생성된다.
모든 과정을 다 하고 배포를 시도했는데 codeDeploy에서 에러가 발생했고
실패한 에러는
view events를 누르면 자세히 볼 수 있다.
ScriptFailed를 누르면 더 자세히 볼 수 있고
Script - scripts/after-deploy.sh 해당 파일에서 에러가 났다고 파악해서 자세히 보니까
프로젝트에서 scripts/after-deploy.sh에 오타가 있었고 수정해서 에러를 해결하였다.
위 과정까지 다 했는데 api call을 하면 fail이 났고 원인을 찾기 위해서 pm2로 그도 봤는데도 잘 작동되고 있었다.
그래서 혹시 몰라 ip로 접근을 했을 때는 잘 나왔고 도메인을 검색하면 안 나오는 걸 파악했고
그렇다면 nginx가 문제인 거 같아서 nginx 상태를 확인했더니 꺼져있었고 다시 키니까 잘 작동하였다.