본문 바로가기
Project/StyleLab

StyleLab의 세 번째 노트: Github Actions CI/CD 구축

by 규난 2023. 12. 17.
728x90

개발자는 프로젝트를 진행하면서 테스트 코드를 실행하고 빌드 하여 배포하는 과정을 반복하게 됩니다.

하지만 위와 같은 과정이 반복되면 생산성이 떨어지게 되는데, 이러한 문제를 해결하기 위해 프로젝트를 진행하기 전에 빌드와 테스트 그리고 배포를 자동화할 수 있도록 CI/CD를 구축하도록 하겠습니다.

 

목차

  1. GitHub Actions를 선택한 이유
  2. GitHub Actions의 작업 구성 요성
  3. GitHub Actions로 CI 구축하기
  4. GitHub Actions로 CD 구축하기
  5. reference

 

1. GitHub Actions를 선택한 이유

CI/CD를 구축할 수 있는 제가 아는 대표적인 도구로는 Jenkins와 GitHub Actions가 있습니다.

저는 이번 프로젝트에서 GitHub Actions를 사용하기로 결정하였습니다. 이유는 다음과 같습니다.

 

비용

Jenkin로 CI/CD를 구축하려면 Jenkins 서버가 별도로 필요합니다. AWS EC2 free tier 기준으로 메모리 스왑을 해주지 않으면 사용할 수 가 없습니다.

GitHub Actions는 별도의 서버가 필요하지 않으며 free 버전 사용 시 2,000 Actions minutes/month를 사용할 수 있기 때문에 비용적인 측면에서 별도의 서버가 필요하지 않은 GitHub Actions를 선택하였습니다.

 

Jenkins Plugin vs GitHub Marketplace

Jenkins의 기능을 확장하기 위한 Jenkins의 plugin과 GitHub의 workflow를 확장하기 위한 GitHub Marketplace 중 GitHub Marketplace가 사용하기가 쉽고 문서화가 잘 되어있다 판단하여 GitHub Actions를 선택하였습니다.

 

GitHub free로 사용할 수 있는 plan

 

2. GitHub Actions의 작업 구성 요소

Workflows

.github/workflows 디렉터리에 yaml 파일로 정의되며, 하나 이상의 job을 구성하고 실행하는 자동화된 프로세스입니다. 각 역할(CI, CD)에 맞는 wokrflow를 만들 수 있으며 수동, event 또는 특정 시간대에 trigger 시킬 수 있습니다.

 

Event

pull request, issue, push 등 workflow를 실행시키는 trigger를 의미합니다.

 

Step과 Jobs

step은 실행될 shell script나 action을 의미하며, 순서대로 실행되고 각 step은 동일한 runner에서 실행되기 때문에 step들 간의 데이터를 공유할 수 있습니다.

 

job은 동일한 runner에서 실행되는 workflow step의 집합입니다.

기본적으로 workflow 내의 job들은 병렬적으로 실행되지만, 필요에 따라 의존 관계를 설정하여 순서를 지정해 줄 수 있습니다.

 

Actions

action은 step을 결합하여 만든 재사용 가능한 workflow에서 가장 작은 단위 블록입니다.

custom action을 만들거나 marketplace에서 필요한 action을 불러와 workflow에서 사용할 수 있습니다.

 

Runners

runner는 workflow가 어떠한 event로 인해 실행이 될 때 workflow를 실행하는 서버입니다.

각 runner는 한 번에 하나의 job을 실행할 수 있습니다. workflow를 실행하는 서버는 ubuntu linux, ms windows, mac os가 있으며 각 workflow는 새로운 가상머신에서 실행됩니다.

 

3. GitHub Actions로 CI 구축하기

GitHub Actions를 사용하여 CI를 구축하기 위한 workflow를 생성하는 방법은 다음과 같습니다.

  • github repository > Actions > set up a workflow yourself 순으로 접근하여 workflow 생성하기
  • 직접 .github/workflows 디렉터리를 만들어 workflow 생성하기

위 과정을 진행하게 되면 다음과 같이 workfile을 구성할 수 있는 코드 에디터가 나오게 됩니다.

workflow editor

 

이제 workflow에 CI를 하기 위한 코드를 작성해 보도록 하겠습니다. 코드는 다음과 같습니다.

CI는 develop 브랜치에 pull request trigger가 발생 시 workflow가 실행되도록 하였습니다.

CI workflow 코드

 

여기서 주의할 점은 위에서 GitHub free 버전 사용 시 2,000 Actions minutes/month를 사용할 수 있다고 언급하였습니다. runner가 실행되는 환경을 정의하는 runs-on에서 ubuntu linux가 아닌 ms windows나 mac os를 사용하게 되면 ubuntu linux에서 실행되는 작업보다 2~10배 빠른 속도로 시간을 소비하기 때문에 무료로 사용할 수 있는 시간을 빨리 소모하게 되므로 ms windows나 mac os 환경에서 실행되어야 하는 코드가 아니라면 ubuntu linux를 사용하시는 게 비용적인 측면에서 더 효율적일 수 있습니다.

 

코드를 작성 후 another branch에서 develop branch로 pull request 요청 보내게 되면 GitHub Actions workflows에 정의한 CI workflow가 실행되는 것을 볼 수 있습니다.

CI workflow 실행

 

이렇게 실행된 workflow가 성공적으로 마치게 되면 다음 사진과 같이 successful check 문구를 보실 수 있으며 자세한 workflow 실행 과정은 Actions 탭에서 보실 수 있습니다. 또한 CI workflow가 실패 시 pull request를 close 할 수 있는 step도 추가할 수 있는데 이 부분은 추후에 다루도록 하겠습니다.

CI workflow 실행 완료

 

4. GitHub Actions로 CD 구축하기

GitHub Actions를 사용하여 CD를 구축하기 위해 workflow를 하나 더 생성하기 전에 workflow runner에서 ssh 원격 접속을 위해 rsa 키를 하나 생성해 주도록 하겠습니다. 밑의 명령어를 사용하여 키 생성과 원격 서버에 키를 등록해 주시면 됩니다.

# local pc에서 private key와 public key 생성
$ ssh-keygen -t rsa -b 4096 -f "키 이름"

#local pc에서 생성한 public key를 배포할 원격 서버의 SSH authorized_keys 파일에 등록
$ cat "키 이름.pub" | ssh <SSH_USER>@<SSH_HOST> "cat >> ~/.ssh/authorized_keys"

 

등록이 잘 되었는지 등록한 키를 사용하여 원격 접속을 해보도록 하겠습니다.

등록한 키를 사용하여 ssh 접속

 

만약 접속이 되지 않는다면 원격 서버에 ~/.ssh 디렉터리에 authorized_keys에 local pc에서 등록한 public key가 잘 등록되어 있는지 확인을 해주셔야 합니다.

 

접속이 잘 되셨다면 github repository > Settings > Secrets and variables > Actions 탭으로 이동을 하신 후 new repository secret 버튼을 눌러 workflow의 runner가 ssh를 사용하여 프로젝트를 배포할 원격 서버에 접속할 수 있게 생성한 private key와 원격 서버의 username과 ip를 actions secret에 등록을 하겠습니다.

actions secret

 

다음으로 public repository를 사용는 경우 application.yml의 DB 접속 정보, jwt secret key 등 중요 정보들에 대한 외부 노출을 막기 위해 secret에 배포용 yml을 등록해 보도록 하겠습니다. secret에 yml에 등록 시 base64로 encoding 해서 올려야 workflow 실행 시 실패하지 않습니다. 

application-dev.yml 정보를 secret로 등록

 

글을 쓰다 보니 이 작업도 yml의 정보가 바뀔 때마다 등록된 secret을 업데이트를 해주어야 하며 업데이트를 하지 않을 경우 애플리케이션이 제대로 실행되지 않는 문제가 있을 거 같다는 생각이 드네요... 이 부분은 더 나은 방법을 찾게 된다면 추후에 업데이트하도록 하겠습니다.

 

위 설정들을 다 하셨다면 workflow를 생성하여 CD를 구축해 보도록 하겠습니다. CD를 구축하기 위한 코드는 다음과 같습니다.

CD는 releases-** 브랜치에 push trigger가 발생 시 workflow가 실행되도록 하였습니다.

name: cd
on:
  push:
    branches: [ releases-** ]

jobs:
  cd-test: 
    runs-on: ubuntu-latest 
    steps: 
    - name: Check out git repository
      uses: actions/checkout@v4  
      
    - name: Set up JDK 11 
      uses: actions/setup-java@v4
      with:
        java-version: 11 
        distribution: 'temurin'
        
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
      
    - name: Test with Gradle
      run: ./gradlew test
      
    # 위 코드는 CI 때와 동일한 코드입니다.

    # secrect에 등록한 yml을 decoding하여 생성하는 코드
    - name: Make application-dev.yml
      run: |
        cd src/main/resources
        touch ./application-dev.yml
        echo "${{ secrets.APPLICATION_DEV_YAML }}" | base64 --decode > ./application-dev.yml

    # 프로젝트의 jar 생성
    - name: Make jar
      run: ./gradlew bootJar

    # secret에 등록한 원격 서버 정보를 사용하여 scp-action으로 원격 서버에 jar 전송
    - name: Copy jar file to remote
      uses: appleboy/scp-action@v0.1.4
      with:
        username: ${{ secrets.DEPLOY_DEV_USER }}
        host: ${{ secrets.DEPLOY_DEV_IP }}
        key: ${{ secrets.DEPLOY_DEV_SSH_PRIVATE_KEY }}
        source: "./build/libs/*.jar"
        target: "/stylelab_app/upload"
        strip_components: 2

    # secret에 등록한 원격 서버 정보를 사용하여 ssh-action으로 원격 서버에 ssh 접속
    - name: Executing remote ssh commands using password
      uses: appleboy/ssh-action@v1.0.0
      with:
        username: ${{ secrets.DEPLOY_DEV_USER }}
        host: ${{ secrets.DEPLOY_DEV_IP }}
        key: ${{ secrets.DEPLOY_DEV_SSH_PRIVATE_KEY }}
        script: |
          cd /stylelab_app
          ./deploy.sh

 

 

원격 서버의 deploy.sh의 shell script는 다음과 같습니다.

#!/bin/bash

# today
today=$(date "+%Y%m%d")
echo "today is ${today}"

# copy to backup
# 기존에 실행 중인 jar를 backup 디렉터리에 복사
echo "Copying to a running JAR backup directory"
cp stylelab.jar ./backup/stylelab-${today}.jar

# running pid
current_pid=$(pgrep -f "stylelab.jar")
echo "Determine the running StyleLab application PID: ${current_pid}"


# 실행 중인 application을 종료
# -z는 조건문에서 사용되는 테스트 옵션 중 하나로, 주어진 문자열이 비어있으면 참(True)을 반환합니다
if [ -z ${current_pid} ]; then
        echo "There is no StyleLab application running"
else
        echo "Exit the running PID ${currnet_pid} StyleLab application"
        systemctl stop stylelab-app.service
        sleep 15
fi

# start stylelab application
# workflow에서 업로드한 새로운 jar 파일을 최상위 디렉터리에 복사
echo "Copy the jar that exists in the upload directory"
cp upload/stylelab.jar ./stylelab.jar

# systemctl를 사용하여 application을 실행
echo "start stylelab application"
systemctl start stylelab-app.service
sleep 10
systemctl status stylelab-app.service

 

 

한 번에 성공한 거 같지만 사실 정말 많은 시도 끝에 CD 구축을 성공하였습니다..

간단한 CI/CD 구축마저 쉽지 않았네요..ㅎㅎ 드디어 이번 프로젝트에서 사용할 GitHub Actions CI/CD pipeline이 구축되었습니다.

다음 블로깅은 pinpoint-apm으로 찾아뵙도록 하겠습니다. 그럼 전 다시 삽질하러..

 

CD workflow 실행 완료

 

5. reference

https://docs.github.com/ko/actions

https://docs.github.com/ko/apps/github-marketplace/github-marketplace-overview/about-github-marketplace-for-apps

728x90