Git Actions - AWS EC2 CI/CD 자동화

손찬호·2024년 7월 20일

모래시계 플래너

목록 보기
2/3

팀 프로젝트를 진행하면서 프론트엔드의 경우 자잘한 UI나 로직 수정이 많았는데
이게 실제 배포환경에서도 잘 돌아가는지 확인하고 싶은데 자잘하게 수정할 때마다
직접 EC2에 들어가 수정하고 배포하고 하는 과정이 상당히 번거로웠다.
그래도 팀 프로젝트 우선순위 상으로 기능 구현보다는 CI/CD 자동화하는 일이
우선순위가 떨어져서 계속 미뤄왔는데
목표한 기능 구현을 거의 다 했기에 쉬어가는 의미에서(?)
Git Actions를 사용해서 CI/CD 자동화를 해보기로 했다.

구현

Git Actions를 사용하면 GitHub 특정 레포지토리의
특정 브랜치에 커밋하면 그걸 인식해서 자동으로 EC2의 인스턴스에 SSH로 접속해
특정 명령어를 순차적으로 실행하도록 설정한다.
다시 말해 커밋 이벤트가 발생하면 EC2에 접속해서 CI/CD를 자동화할 수 있다.

CI/CD가 뭐지?

CI

CI는 “Continuous Integration”의 약자로, “지속적 통합”을 의미한다.
여기서 통합은 팀 개발자들이 작업해서 수정한 코드를 레포지토리에 반영하는 걸 행위를 말한다.
자주 코드를 통합하면 테스트를 자주할 수 있어 코드 변경 사항이 문제가 있는지 빨리 알 수 있다.
문제가 발생했을 때 빨리 발견하고 해결할 수 있다는 장점이 있다.

CD

CD는 “Continuous Delivery” 또는 “Continuous Deployment”의 약자로
코드가 중앙 레포지토리에 통합된 후 자동으로 배포되는 걸 의미합니다.
두 약자는 의미가 비슷해보이지만 미묘한 차이가 있다.

  • 지속적 전달 (Continuous Delivery):
    새로운 코드를 배포할 준비가 항상 되어 있는 상태를 유지하기.
    배포 준비만 자동화하고 실제 배포는 개발자가 수동으로 진행하는 방식.
  • 지속적 배포 (Continuous Deployment):
    새로운 코드가 통합되면, 자동으로 사용자에게 배포.

이번에는 배포까지 자동으로 한 '지속적 배포'방식으로 진행할 예정이다.
왜냐하면 하루에도 팀원들의 수 많은 배포 요청이 오기 때문이다.

자동화하면 좋은 점

여러 장점이 있지만 핵심은 빠른 피드백과 안정적인 배포를 할 수 있다는 점이다.
새로운 기능 추가나 버그 수정이 빠르게 이루어지고
개발자가 수동으로 배포하는 과정에서 발생하는 오류를 방지한다.

과정

보안이 필요한 값 등록

레포지토리의 경우 public으로 공개설정을 해두면 다른 사람도 볼 수 있는데
거기에 내 인스턴스의 도메인이나 키값을 올려두면 보안적으로 위험하다.
그래서 GitHub에서는 보안을 위해 따로 등록을 해두고
해당하는 변수값을 둘러오는 방식으로 보안을 챙길 수 있다.

자동화를 할 깃허브 레포지토리에 들어가서
Settings -> Security -> Secrets and variables -> Actions
-> New repository secret을 클릭
사용할 변수명과 값을 등록하면 된다.

변수명은 원하는 대로 사용해도 좋지만 가능한 알아보기 쉬운 의도가 명확한 이름으로
작성하자. 나중에 헷갈릴 수 있다.

EC2 인스턴스에 자동화를 한다면 크게 2가지를 등록하면 된다.

  • HOST: 퍼블릭 IPv4 DNS
  • SSH_PRIVATE_KEY: 시작 시 할당된 키 페어

yml파일 작성

자동으로 실행할 명령어를 적어놓은 파일인 yml파일을 작성해보자.
이때 중요한 게 크게 2가지가 있다.

  1. yml파일은 .github/workflows 디렉토리 안에 있어야한다.
  2. 특정 브랜치에 커밋을 하면 자동으로 Git Actions를 실행하도록 한다면
    그 브랜치에 .github/workflows 디렉토리에 yml파일이 있어야한다.

깃 레포지토리에 Actions라는 탭을 누르면
Choose a workflow라는 제목의 페이지가 나오는데 이때 EC2를 검색하거나
아래의 네모 칸을 찾아 Configure 버튼을 눌러준다.

그러면 아래에 저 디렉토리에 yml파일을 작성하는 페이지가 나오게 되는데
이래저래 길게 적혀있지만 우리는 필요한 부분만 작성하면 된다.

작성한 yml파일 해설

name: Deploy to Amazon ECS

on:
  push:
    branches: [ "aws" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
    - name: Checkout
      uses: actions/checkout@v4

    - name: Set up SSH
      uses: webfactory/ssh-agent@v0.5.3
      with:
        ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

    - name: Deploy to EC2
      run: |
        ssh -o StrictHostKeyChecking=no ubuntu@${{ secrets.HOST }} << 'EOF'
          #!/bin/bash

          # 포트를 사용 중인 프로세스를 종료합니다.
          PORT=3000
          PID=$(lsof -t -i:$PORT)
          if [ ! -z "$PID" ]; then
            kill -9 $PID
            echo "Process on port $PORT stopped."
          else
            echo "No process running on port $PORT."
          fi

          # 새로운 배포를 위해 디렉토리로 이동합니다.
          cd /home/ubuntu/testspring/HourglassPlannerFront 

          # Git 명령어를 실행합니다.
          git pull
          git checkout origin/aws
          
          # npm 명령어를 실행합니다.
          npm install
          npm run build
          npm run server > npm.log 2>&1 &
          exit
        EOF

이래저래 길게 적혀있지만 해야하는 것만 적어놨다. 하나씩 알아보도록 하자.

특정 브랜치에 커밋하면 실행

on:
  push:
    branches: [ "aws" ]

-> aws라는 브랜치에 커밋을 하면 실행하도록 설정했다.

ssh로 접속

    - name: Set up SSH
      uses: webfactory/ssh-agent@v0.5.3
      with:
        ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

AWS EC2 인스턴스 시 생성한 pem파일로 ssh로 접속해서 터미널 명령어를
실행하기 위해서 필요한 설정이 적혀있다.
참고로 이 작업을 하기 위해선 해당 인스턴스의 ssh 통신을 위한 tcp 22번 포트가
열려있어야한다.
secrets.SSH_PRIVATE_KEY
아까 등록한 Repository Secret의 변수명에 해당하는 값을 가져온다.

어떤 HOST에 접속할지 설정

    - name: Deploy to EC2
      run: |
        ssh -o StrictHostKeyChecking=no ubuntu@${{ secrets.HOST }} << 'EOF'
          #!/bin/bash

-> AWS EC2 인스턴스 -> 연결 -> SSH 클라이언트를 참고하면 이해하기 편한데
살짝 형식은 다르지만 Git Actions를 실행할 때 EC2에 SSH로 접속하기 위해
사용하는 명령어라고 보면 된다.

특정 포트의 프로세스 종료

EC2에 자동배포를 한다면 특정 포트에 Next.js든 Spring Boot든 백그라운드로 실행을
하고 있을텐데 새로운 커밋을 반영한 프로그램을 실행하려면 기존 포트에 실행 중이던 프로그램을
중단하고 다시 배포해야한다.
이때 특정 포트에 실행 중이던 프로그램의 프로세스 ID인 PID를 알아내고 종료하는 걸 자동화하는 명령어이다.

# 포트를 사용 중인 프로세스를 종료합니다.
PORT=3000
PID=$(lsof -t -i:$PORT)
if [ ! -z "$PID" ]; then
kill -9 $PID
echo "Process on port $PORT stopped."
else
echo "No process running on port $PORT."
fi

배포 자동화

여기까지 왔다면 우리가 실제로 EC2 터미널에 들어왔을 때 실행할 명령어를
순서대로 적어주면 된다.

 # 새로운 배포를 위해 디렉토리로 이동합니다.
cd {Git Clone한 레포지토리 경로}

# Git 명령어를 실행합니다.
git pull
git checkout origin/aws
          
# npm 명령어를 실행합니다.
npm install
npm run build
npm run server > npm.log 2>&1 &
exit

테스트

aws에 브랜치에 커밋을 하면 정말 실행되는지 테스트를 해보자.

실행 중이면 이런 식으로 노란 버튼이 나오고
완료되면 초록색으로 버튼이 나온다.

배포 상태 깃 레포지토리 ReadMe에 올리기

배포가 되었는지 상태를 깃 레포지토리 메인 페이지에서 ReadMe.md 파일에
특정 링크를 등록하면 알 수 있다.

Action 탭에서 Workflow를 클릭하고 우측 상단의 ... 버튼을 클릭해 create status badge를
클릭한다.

특정 브랜치에 커밋하면 자동으로 배포하도록 설정했다면
반드시 그 브랜치를 선택해주고
Event의 경우 이미 yml에 커밋할 때 동작하도록 설정했으므로 'Default'로 선택해준다.
이후 Copy status badge Markdown을 클릭해서
ReadMe.md에 붙여넣기 해주면 된다.

profile
매일 1%씩 성장하려는 주니어 개발자입니다.

0개의 댓글