Profile picture

[배포 가이드] Github Actions를 사용하여 배포 자동화하기 (팀 단위)

JaehyoJJAng2023년 11월 22일

📋 Workflow 구성도

image


◾️ 배포 준비

1. 브랜치 생성

이전에 [Deploy] 가상서버(Virtual Machine)에 node API 배포하기 - AWS Lightsail 프로젝트에서 진행했었던 express 앱을 그대로 사용할 것이다.


우선 프로젝트 경로로 이동한 후 "dev" 브랜치를 생성하자.

$ git checkout -b dev # dev 브랜치 생성 후 dev 브랜치로 checkout

$ git branch # branch 목록 및 현재 checkout 되어있는 브랜치 확인
* dev
  main

그리고 main 브랜치에서 모든 소스코드를 관리했었는데, 이제는 dev 브랜치에서 코드 수정/추가 등을 할 것이다.

app/index.ts 파일에 아래의 console 문을 추가하고 main 브랜치로 PR을 요청하자.

# app/index.ts

...
const startServer = async () => {
  console.log("trying to start server"); # 추가된 부분
  const client = redis.createClient({ url: REDIS_URL });
  await client.connect();
  ...
};
startServer();

코드를 수정했으면 변경 사항을 커밋하고 github에 dev 브랜치로 push하자.

$ git add .
$ git commit -m "[UPD] added starting messages"
$ git push -u origin dev

프로젝트의 깃허브 레포지토리로 이동 후, dev 브랜치가 새로 생겼는지 확인하자.
image
dev 브랜치가 새로 생긴 것을 확인할 수 있다.


그리고나서 dev 브랜치의 수정/추가된 코드들을 main 브랜치로 Pull Request(PR)을 요청해야한다.
'Pull Request' 탭에 들어간 후 'New Pull Request'를 클릭하자.
image


아래처럼 PR 기록이 뜨고 이번 PR에서 어떤 코드들이 변경됐는지 세세하게 알 수가 있다. compare인 브랜치를 'dev'로 잡아주고 base 브랜치를 'main'으로 잡아주자. Pull Request를 요청함으로써 dev 브랜치의 수정사항 또는 추가된 코드들이 main 브랜치에 적용될 수 있는지 원본 레포지토리 주인이 판단하는 것이다.
image
"create pull request"를 클릭하자.


PR 메시지는 사용자가 무얼 수정하고 추가했는지 알 수 있는 메시지로 작성하면 된다.
image


로컬 터미널로 들어가서 dev 브랜치에서 main 브랜치 내용을 새롭게 pull(code update) 해준 후 push 하자.

$ git pull origin main
$ git push -u origin dev

2. Workflow 생성

정상적으로 PR이 됐고, Merge Conflict가 발생하지 않은 상황에서의 github actions workflow를 작성해보자. image


2-1. workflow 작성

현재 프로젝트 경로에 .github/workflows/test.yaml 파일을 만들어주도록 하자.

$ mkdir -p .github/workflows
$ touch .github/workflows/test.yaml

테스트용 workflow를 작성해보자.

name: test
on: pull_request

jobs:
    test_job:
      runs-on: ubuntu-22.04
      steps:
          - name: "1. Checkout repository"
            uses: actions/checkout@v3

          - name: "2. Node.js Setup"
            uses: actions/setup-node@v3
            with:
                node-version: "18"
          
          - name: "3. Install npm packages"
            run: npm ci
          
          - name: "4. Install and run redis-server"
            run: |
                sudo apt-get update -y
                sudo apt-get install -y redis-server
                redis-server --daemonize yes --requirepass test_env --port 6380

          - name: "5. Run test"
            run: npm run test

          - name: "6. test build"
            run: npm run build

📌 github actions 문법은 다음 게시글에서 확인 가능하다.
👉 https://jaehyojjang.dev/categories/github-action-basic


2-2. workflow 배포

workflow 작성이 다 끝났으면 dev 브랜치에서 commit & push 하면 된다.

$ git add .
$ git commit -m "added test github actions workflow"
$ git push -u origin dev

main 브랜치로 PR 요청
image


main 브랜치로 PR을 요청하니 위에서 작성했던 workflow가 실행되는 것을 볼 수 있다.
image


2-3. 배포 중 에러

현재 PR로 인해 실행된 github actions trigger가 몇 분째 노란색 동그라미에서 넘어가지 않고 있다.
이는 테스트 코드에서 일부러 의도한 것이다. 만약 trigger가 fail이 아닌 진행중인 상태로 계속 남아있는다면 어떻게 될 것인지에 대한 테스트였다. gitbub actions에서 자체적으로 긴 시간이 소요되는 경우 타임아웃이 발생하여 해당 trigger가 종료된다.
image
현재 PR에서 실행된 trigger가 정상적으로 완료되지 않았음을 알 수 있음.


2-4. 배포 에러 해결

먼저 pacakge.json에 아래의 커맨드를 추가하자.

  "scripts": {
    ...
    "test:ci": "jest"
  }

image


workflows의 5번 job을 아래처럼 수정하자.

# vim .github/workflows/test.yaml

...
          - name: "5. Run test"
            run: npm run test:ci
...

다시 푸시해보자.

$ git add .
$ git commit -m "fixed test npm script"
$ git push -u origin dev

다시 PR을 요청해보고, github actions trigger가 정상적으로 진행되는지 github에서 확인해보자!
image
trigger가 정상적으로 실행되어 Merge가 가능해진 것을 확인할 수 있다.


workflow가 어떻게 진행됐는지 확인해보자.
image


이제 dev 브랜치는 쓸모 없어졌으니 Merge 후 삭제하도록 하자.
image
"Delete branch" 클릭.


물론 터미널 상에서도 dev 브랜치 삭제가 가능하다.

$ git push --delete origin dev

💿 배포

image


1. Github Secrets

Github Actions VM에서 Lightsail 인스턴스에 접근할 수 있도록 로컬에서 만든 github_id_rsa 개인 키를 Github 프로젝트 레포지토리의 Secrets 환경 변수로 등록해줄 것이다.

이렇게 함으로써 Github Actions Workflows에서 생성된 가상머신이 Lightsail 인스턴스에 접근할 수 있게된다.

github_id_rsa ssh 키 생성은 아래 링크를 참고하여 생성하면 된다. 이번 포스팅에서는 생략하였는데 간단히 설명하자면 로컬 머신에 github_id_rsa 라는 개인 키를 생성하였고 같이 생성된 public 키는 lightsail 인스턴스의 ~/.ssh/authorized_keys에 등록하였다.

📌 SSH 터널링으로 Lightsail 인스턴스 접속하기 -> https://jaehyojjang.dev/linux/ssh-tunneling-to-lightsail/


로컬의 ~/.ssh/gitub_id_rsa 개인 키를 cat 명령어로 출력해보자.

$ cat ~/.ssh/github_id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbXXXXXXXX
.............
-----END OPENSSH PRIVATE KEY-----

위 키를 복사하고 github 프로젝트 레포지토리 - Settings - Secrets and variables 탭으로 이동한 후 "New repository secret"을 클릭하여 필요한 환경 변수들을 아래처럼 만들어주자.
image

  • SSH_KNOWN_HOST의 경우 로컬에서 ssh-keyscan 서버_PUBLIC_IP_주소 명령을 실행하여 서버의 공개 키를 수집하여 출력한 후 해당 공개 키들을 환경 변수로 넣은 것이다.
    • ssh-keyscan example.com : ssh-keyscan은 SSH 서버의 공개 키(public key)를 검색하고 수집하는 유틸리티입니다. SSH 서버는 클라이언트가 연결할 때 공개 키를 제공하여 서버 식별 및 인증을 수행합니다. ssh-keyscan은 지정된 호스트 또는 호스트 목록에서 SSH 서버의 공개 키를 검색하여 해당 호스트의 신뢰할 수 있는 키들을 수집합니다.
    • 일반적으로 ssh-keyscan은 보안 및 인증을 목적으로 서버의 공개 키를 수집하고, 이를 클라이언트의 known_hosts 파일에 추가하여 해당 서버에 대한 신뢰성을 제공합니다. 이를 통해 서버가 변경되었거나 중간자 공격(man-in-the-middle attack)에 취약해지는 것을 방지할 수 있습니다.

2. workflow 작성

  • 배포를 위한 workflow를 작성해 볼 것이다.

그전에, 위에서 test workflow 배포 당시 배포하고나서 깃허브 레포지토리에 있던 dev 브랜치를 삭제했었다.
로컬의 프로젝트 경로에서도 dev 브랜치를 삭제하고 다시 생성해야한다.

먼저 main 브랜치에서 현재 최신 상태의 코드를 pull(update) 받도록 하자.

git checkout main
git pull origin main

dev 브랜치를 삭제하자.

git branch -D dev

다시 dev 브랜치를 생성하고 거기서 아래 deploy.yaml을 작성하면 된다.

git checkout -b dev

.github/workflows/deploy.yaml

name: deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-22.04
    steps:
      - name: "1. Set up SSH"
        run: |
          mkdir -p ~/.ssh/
          echo "${{ secrets.SSH_PRIVATE_KEY }}" | tee ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
      
      - name: "2. Set up Known hosts"
        run: |
          echo {% raw %} "${{ secrets.SSH_KNOWN_HOST }}" {% endraw %} | tee -a ~/.ssh/known_hosts
          chmod 644 ~/.ssh/known_hosts

      - name: "3. SSH and Deploy"
        run: |
          ssh -i ~/.ssh/id_rsa {% raw %} ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_PUBLIC_IP }} {% endraw %}" 
            cd express
            sudo bash scripts/kill-app.sh
            git pull
            npm install
            npm run build
            sudo nohup npm run start 1>/dev/null 2>&1 & npx wait-on http://localhost:4000
            "

2-1. workflow 배포

workflow 작성이 다 끝났으면 dev 브랜치에서 commit & push 하면된다.

git add .
git commit -m "Created deploy.yaml github actions"
git push -u origin dev

main 브랜치로 PR 요청
image
테스트 trigger가 정상적으로 통과되었음.


dev 브랜치 코드를 merge 한 뒤에 Actions 탭에서 정상적으로 deploy trigger가 동작 하는지 확인


2-2 workflow 배포 에러

deploy trigger 동작 중 "3. SSH and Deploy" job이 계속 실패하였음. image


SSH 접속이 막힌 걸로 추측이 되는데, deploy.yaml 파일 코드에는 아무 문제가 없어보임.

한참을 생각하다가 '아차' 소리가 나왔는데 Lightsail Networking에서 22번 포트(SSH) 접속 대역을 내 PC 공인 IP 외에는 모두 못들어도록 막아놨었음!
image


모든 IP 대역 허용으로 바꿔주니 다음과 같이 정상적으로 배포됨!
image


2-3. main 브랜치 보호하기

위에서 실습을 진행할 때 PR에서 test trigger 동작 중에 test trigger가 완료되지 않았는데도 merge가 가능하도록 되어있었다.

이는 테스트 과정에서 잘못된 코드가 있었음에도 deploy가 가능해진다는 애기이므로 프로덕션의 심각한 문제를 야기할 수도 있는 큰 문제이다.

그러므로 main 브랜치를 보호하기 위한 방법을 알아보려고 한다.


먼저 깃허브 프로젝트 레포지토리 - Settings - Branches로 들어가서 아래 'Require a pull request before merging'을 체크해주도록 하자. 배포 시 PR이 필수이며 강제로 push를 못하도록 막아놓는 것이다.
image


그리고 두번째로는 'Require status checks to pass before merging'을 체크한 후, test_job을 넣어주도록 하자.
test_job 워크플로우가 통과하기 전까지는 merge를 할 수 없도록 만드는 것이다.
image


Loading script...