본문 바로가기
개발 프로세스 기록

Next.js App 배포하기 - 3. 지속 배포(CD) 구축

by 강물둘기 2025. 1. 25.

Github Action을 통하여 main브랜치에 PR이 merge 되면 자동으로 빌드 후 배포를 진행하도록 구축하였습니다.

 

main 브랜치에 merge가 되면 매번 ec2 instance에 들어가서 git pull -> npm run ->  build pm2 start까지 하는 작업을 줄여보고자 했습니다.

 

Github Action에서 제공하는 탬플릿을 사용하여 간편하게 CD를 구축할 수도 있지만, 이번에는 .yml 파일을 사용하여 스크립트 형식으로 CD를 구축해 보았습니다.

 

EC2 인스턴스에 인바운드 규칙으로 ip를 제한해 놓았기 때문에 github action이 실행될 ip를 추가하고 제거하는 작업이 포함되어 있습니다.

 

구축 방법

간단합니다. 프로젝트 root에 .github/workflows 폴더 내부에 .yml 파일을 작성하면 됩니다.

(물론 .yml 파일 작성이 간단하지는 않습니다.)

 

.yml 파일 작성

on

on 키는 해당 workflow가 언제 실행될지를 설정합니다.

on:
  push:
    branches:
      - main

 

저는 main 브랜치에 push 될 때 (PR포함) action을 실행하도록 지정하였습니다.

 

jobs

jobs에서 실행할 작업들을 설정합니다. 먼저 실행환경을 지정합니다. 저는 ubuntu를 지정해 주었습니다.

deploy-production-web: # 작업의 이름
    runs-on: ubuntu-latest

 

steps에서 실제 작업들을 설정합니다.

그냥 배포작업 설정만 해도 되지만, EC2 인스턴스에 보안그룹의 인바운드 규칙이 github action의 접근을 허용하지 않기 때문에 작업과정에서 입시로 github action의 접근을 허용하는 작업이 추가되어야 합니다.

 

1. 먼저 github action의 ip를 받아옵니다. haythem이라는 라이브러리를 사용합니다.

- name: Get Github action IP
        id: ip
        uses: haythem/public-ip@v1.2

 

 

2. github env의 환경변수를 설정합니다. 저는 region과 보안그룹이름을 설정하였습니다.

- name: Setting environment variables
        run: |
          echo "AWS_DEFAULT_REGION=ap-northeast-2" >> $GITHUB_ENV
          echo "AWS_SG_NAME=[보안그룹 이름]" >> $GITHUB_ENV

 

 

3. AWS 자격을 증명합니다. 프로젝트의 secret에서 지정한 여러 key들을 불러옵니다.

- 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: ap-northeast-2

 

 

4. 1번에서 받은 ip가 보안그룹에 지정되어있는지 확인하고, 지정되어 있지 않다면 지정합니다. 해당 작업에 aws credential이 필요하므로 env로 지정해줍니다.

 - name: Add Github Actions IP to Security group
        run: |
          CURRENT_RULE=$(aws ec2 describe-security-groups --group-name ${{ env.AWS_SG_NAME }} --query "SecurityGroups[0].IpPermissions[?FromPort==\`22\`].IpRanges[?CidrIp==\`{{ steps.ip.outputs.ipv4 }}/32\`]" --output text)
          if [ -z "$CURRENT_RULE" ]; then
            echo "IP not found, adding IP to security group."
            aws ec2 authorize-security-group-ingress --group-name ${{ env.AWS_SG_NAME }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
          else
            echo "IP is already in the security group."
          fi
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-northeast-2

 

 

5. repository의 코드를 불러옵니다. 기본적으로 이 액션은 main branch의 최신 commit의 코드를 가져옵니다.

- name: Checkout code
        uses: actions/checkout@v2

 

 

6. SCP(Secure Copy Protocol)를 사용하여 EC2 인스턴스에 파일을 복사합니다. appleboy/scp-action 라이브러리를 사용합니다.

- name: Copy files via SSH
        uses: appleboy/scp-action@v0.1.3
        with:
          host: ${{ secrets.EC2_HOST_PRODUCTION }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_KEY }}
          source: "./"
          target: "~/[프로젝트 이름]/"

 

** appleboy/scp-action

https://github.com/appleboy/scp-action

 

7. 배포에 필요한 스크립트를 실행합니다. 의존성 설치, 빌드, node 환경설정, pm2 start와 같은 작업들입니다.

- name: Deploy to EC2 via SSH and run PM2 start script
        uses: appleboy/ssh-action@v0.1.3
        with:
          host: ${{ secrets.EC2_HOST_PRODUCTION }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_KEY }}
          script: |
            cd ~/[프로젝트 이름]/
            git pull origin main
            pm2 stop [지정한 프로젝트 이름]
            export PATH=$PATH:/home/ubuntu/.nvm/versions/node/v22.11.0/bin
            npm install
            npm run build
            pm2 start [지정한 프로젝트 이름]

 

 

8. 마무리로 임시로 보안그룹에 추가했던 github action의 ip를 지우는 작업을 합니다.

- name: Remove Github Actions IP from security group
        run: |
          aws ec2 revoke-security-group-ingress --group-name ${{ env.AWS_SG_NAME }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-northeast-2

 

 

전체 yml 파일

name: Deploy main Branch to AWS EC2 Instance

on:
  push:
    branches:
      - main
env:
  GITHUB_REPOSITORY: ${{ github.server_url }}/${{ github.repository }}
  ACTION_URI: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
jobs:
  deploy-production-web:
    runs-on: ubuntu-latest

    steps:
      - name: Get Github action IP
        id: ip
        uses: haythem/public-ip@v1.2

      - name: Setting environment variables
        run: |
          echo "AWS_DEFAULT_REGION=ap-northeast-2" >> $GITHUB_ENV
          echo "AWS_SG_NAME=[보안그룹 이름]" >> $GITHUB_ENV
      - 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: ap-northeast-2

      - name: Add Github Actions IP to Security group
        run: |
          CURRENT_RULE=$(aws ec2 describe-security-groups --group-name ${{ env.AWS_SG_NAME }} --query "SecurityGroups[0].IpPermissions[?FromPort==\`22\`].IpRanges[?CidrIp==\`{{ steps.ip.outputs.ipv4 }}/32\`]" --output text)
          if [ -z "$CURRENT_RULE" ]; then
            echo "IP not found, adding IP to security group."
            aws ec2 authorize-security-group-ingress --group-name ${{ env.AWS_SG_NAME }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
          else
            echo "IP is already in the security group."
          fi
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-northeast-2
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Copy files via SSH
        uses: appleboy/scp-action@v0.1.3
        with:
          host: ${{ secrets.EC2_HOST_PRODUCTION }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_KEY }}
          source: "./"
          target: "~/[프로젝트 이름]/"

      - name: Deploy to EC2 via SSH and run PM2 start script
        uses: appleboy/ssh-action@v0.1.3
        with:
          host: ${{ secrets.EC2_HOST_PRODUCTION }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_KEY }}
          script: |
            cd ~/[프로젝트 이름]/
            git pull origin main
            pm2 stop [지정한 프로젝트 이름]
            export PATH=$PATH:/home/ubuntu/.nvm/versions/node/v22.11.0/bin
            npm install
            npm run build
            pm2 start [지정한 프로젝트 이름]
      - name: Remove Github Actions IP from security group
        run: |
          aws ec2 revoke-security-group-ingress --group-name ${{ env.AWS_SG_NAME }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-northeast-2

댓글