와플 스튜디오 루키 과정 복습하기 7 - nginx configuration and CD
천천히 쓰면서 CD를 구축하던 중... 머리가 깨져가며 code deploy agent와 EC2 속 pm2가 협동해서 배포하도록 하는 방법을 배웠다.
dev prod 환경을 구분한 것도 아니고... 그냥 자동 배포만 구축하는데 왜이리 어려울까. 그리고 이렇게 시행착오를 거칠 줄 알았으면 push할 때 메시지라도 잘 적어놓을 걸 그랬다. 나는 그냥 하나 고치면 될 줄 알았음.
그래도 이제는 정말 aws에 대해 감은 잡은 거 같다. 간단한 걸 멍청하게 실수한 건 부끄럽지만 뭐... 컴린이인 건 사실이니까. 우선은 전반적인 과정은 블로그에 영어로 적어놓고, 어떤 시행착오를 했는지는 따로 한국어로 된 글을 작성할 계획!
Last time(복습하기 5), I deployed my project using AWS EC2.
While testing the image upload feature on the test page, I encountered an error:
POST --- 413 (Request Entity Too Large)
This error occurs because nginx limits the size of client requests to 1MB by default.
To resolve this issus, we need to modify the nginx configuration file to increase the request size limit.
sudo vim testsite.conf
server {
listen 80;
server_name <Public IPv4 address>;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
client_max_body_size 50M;
}
}
Add the 'client_max_body_size 50M' in the locaiton block to increase the allowed request size.
Test for syntax errors and reload nginx, and also app.
nginx -t
sudo systemctl reload nginx
pm2 restart <my_app>
After applying these changes, I successfully uploaded and retrieved my image without any issues.
+ Proceeding distribution, I made a high score of apple game!
What is the CI/CD?
Continuous Integration (CI)
- Automated tools build and test the code to ensure that new changes do not break the application.
Continuous Delivery/Deployment (CD)
- After passing CI, the code is automatically prepared for deployment to staging or production environments.
First, I'm going to construct automatic deployment(CD).
To automate the deployment of my Next.js app to an EC2 instance whenever I push code to Github, I will use Github Actions as the automation tool.
I will follow the steps outlined in this blog: https://velog.io/@hoooons/AWS-Github-Actions-S3-EC2-CodeDeploy-pm2%EB%A1%9C-Next.js-CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-0edyvs5n
The process is as follows:
1. When GitHub Actions is triggered, it builds and compresses the Next.js app and then uploads it to S3 bucket.
2. The GitHub Actions workflow triggers AWS CodeDeploy to fetch the file from S3 and deploy it to the EC2 instance based on the 'appspec.yml' configuration.
3. After Deployment, pm2 ensures that changes in the project are handled properly and manages the app using the 'ecosystem.config.js' settings.
Let's get started!
First, create an S3 bucket to upload the compressed Next.js app file. I named it 'rookie-review-ci-cd'.
Then create IAm roles for EC2 instance and CodeDeploy.
1. Role for EC2 instance
What are they? Let's look at one by one.
1) AmazonEC2RoleforAWSCodeDeploy
- This policy is required for the CodeDeployAgent running on the EC2 instance. It allows the agent to access S3 buckets and download files stored in them.
2) AmazonS3FullAccess
- I'm not entirely sure why this is needed yet, but it might be required later for interacting with S3.
Initially, some blog posts suggested attaching AWSCodeDeployFullAccess to the EC2 instance role. However, this policy is meant for CodeDeploy itself, not the EC2 instance. After removing it, everything worked as expected.
2. Role for CodeDeploy
Next, create a Code Deploy application and set up a deployment group.
This is a process to create deployment group
Set the Deployment configuration to AllAtOnce.
Next, create an IAM user for CI/CD workflow and issue access key.
Assigning roles:
The blog I following recommended assigning AmazonEC2FullAccess to the IAM user, but this is unnecessary. This user is only responsible for application deployment tasks, such as:
1. Uploading artifacts to S3
2. Triggering AWS CodeDeploy
Access to EC2 is only required by CodeDeploy, not the IAM user.
After creating the user, issue an access key and register it in your GitHub repository.
Next, install the CodeDeploy Agent at EC2 instance.
sudo apt update
sudo apt install ruby-full
sudo apt install wget
cd /home/ubuntu
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto > /tmp/logfile (20.04 버전)
Verify that the CodeDeploy Agent is installed successfully by running the following command:
sudo service codedeploy-agent status
If it working correctly, the status should display: 'active (running)'
With all preparations complete, let's move on to writing the YAML files.
.github/workflows/deploy-test.yml
name: deploy to S3
# main 브랜치로 push 할 때 workflow 실행
on:
push:
branches:
- main
workflow_dispatch:
# 환경 변수 설정
env:
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
CODE_DEPLOY_APPLICATION_NAME: ${{ secrets.CODE_DEPLOY_APPLICATION_NAME }}
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${{ secrets.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }}
jobs:
build:
runs-on: ubuntu-latest
steps:
# 레포지토리 clone
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.11.1'
# Bun 설치 및 관련 환경 변수 설정
- name: Setup Bun
run: |
curl -fsSL https://bun.sh/install | bash
echo "BUN_INSTALL=$HOME/.bun" >> $GITHUB_ENV
echo "PATH=$HOME/.bun/bin:$PATH" >> $GITHUB_ENV
# 프로젝트 의존성 설치
- name: Install dependencies
run: bun install
# Next.js 앱 빌드
- name: Build next app
run: bun run build
# 빌드한 파일과 프로젝트 소스를 압축해 .zip 파일로 만들기
- name: Make zip file
run: zip -qq -r ./rookie_review.zip . -x ".git/*"
# -qq: quit 모드로 실행 (에러나 경고메세지만 출력하도록 함)
# -r: 지정된 디렉토리를 재귀적으로 압축 (하위 디렉토리와 파일들 모두 압축)
# Github Action에서 AWS의 권한 자격을 얻어오는 단계
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
# 압축된 파일을 S3에 업로드
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./rookie_review.zip s3://${{ env.S3_BUCKET_NAME }}/rookie_review.zip
# aws s3 cp: AWS CLI 명령어로 파일 복사
# --region ap-northeast-2: 업로드 대상 리전 설정
# 파일을 S3 버킷의 루트 디렉토리에 업로드.
# S3에 업로드 된 빌드 파일을 이용해 CodeDeploy가 정의된 동작을 하도록 트리거
- name: Code Deploy
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=rookie_review.zip
GitHub Actions execute workflow based on this file.
appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/waffle_rookie_review
overwrite: yes
permissions:
- object: /home/ubuntu/waffle_rookie_review
owner: ubuntu
group: ubuntu
mode: 755
hooks:
BeforeInstall:
- location: scripts/before-install.sh
timeout: 300
runas: ubuntu
AfterInstall:
- location: scripts/after-install.sh
timeout: 300
runas: ubuntu
script/after-install.sh
REPOSITORY=/home/ubuntu/waffle_rookie_review
BUN_PATH=/home/ubuntu/.bun/bin/bun
if [ -f "/home/ubuntu/env-backup/.env.local" ]; then
echo "> Restoring .env.local file..."
cp /home/ubuntu/env-backup/.env.local /home/ubuntu/waffle_rookie_review/.env.local || {
echo "Error: Failed to restore .env.local file."
exit 1
}
else
echo "Warning: No backup .env.local file found. Skipping restoration."
fi
cd $REPOSITORY || {
echo "Error: Failed to navigate to project directory."
exit 1
}
$BUN_PATH run deploy
script/before-install.sh
REPOSITORY=/home/ubuntu/waffle_rookie_review
# 모든 파일과 숨김 파일 삭제
find $REPOSITORY -mindepth 1 -delete
echo "Cleanup completed."
appspec.yml is a script executed by Code Deploy Agent.
ecosystem.config.js
module.exports = {
apps: [
{
name: "test-server", // 앱의 이름
script: "./node_modules/next/dist/bin/next", // Next.js 스크립트 경로
args: "start", // Next.js 앱을 시작할 때 사용할 인수
exec_mode: "cluster", // 실행 모드: cluster 또는 fork 중 선택
instances: "2", // 클러스터 모드에서 실행할 인스턴스 수 (CPU 코어 수만큼)
autorestart: true, // 프로세스 자동 재시작 활성화
watch: true, // 파일 변경 감지 활성화 (개발 중에만 활용)
max_memory_restart: "1G", // 1GB 이상 메모리 사용 시 재시작
env: {
NODE_ENV: "production", // Node.js 환경 설정
},
},
],
};
And add script to package.json as:
"deploy": "pm2 start ecosystem.config.js --env production"
Done! :D
Let's do test.
Before:
After:
Coooooooool!