지인의 선물로 받은 영화추천 웹서비스로 배우는 풀스택 을 훑어보던 중, Gitlab 배포를 자동화하는 부분을 보고, 이번 기회에 동일한 과정을 내가 익숙한 Github으로 따라해보고 Github Actions에 대해서도 찍먹 해보기로 했다.

 

0. 준비 사항

  1. 서버

    • API 를 배포할 AWS EC2 인스턴스
    • EC2 인스턴스에 연결할 때 사용될 키페어 (.pem 파일) - RSA 유형으로 생성하여 다운로드 받자 (참고링크)
  2. GitHub 계정과 프로젝트를 관리할 Github Repository

    • 기본적으로 생성되는 main 브랜치와 별개로 deploy 브랜치를 생성해놓는다.
    • deploy 브랜치에는 서버에 배포할 코드(deploy-ready)만 올라가게 된다.
  3. API & Docker

    • 해당 실습에서는 FastAPI 기반의 API 서버를 작성하였다. Docker로 컨테이너화 하여 실행한다.
    • FastAPI 기반의 API 어플리케이션
    • AWS EC2 인스턴스에는 Docker가 설치해놓는다.

 

1. Workflow 파일 작성하기

  • Actions에서 자동화하고자 하는 workflow는 아래와 같은 경로에 yaml 형식으로 작성한다.
.github/workflow/{파일 이름}.yaml
  • 변수 등록하기 ({{ secrets.변수명 }})
    • 직접 보안과 관련된 정보 또는 hard-coding으로 간주되는 변수는 {{secrets.변수명}} 으로 입력한다.

    • repository 우측 상단 Settings -> Secrets and variables -> Actions 탭 에서 등록하면, 추후 Github Actions workflow가 실행될 때 등록된 변수에 접근이 가능하다.

    • 이번 실습에서는 다음과 같은 변수들을 등록했다. (스크린샷 참조)

      • DEPLOY_HOST : 배포할 원격 서버의 주소
      • DEPLOY_USERNAME : 원격 서버 계정
      • DEPLOY_SSH_KEY : 원격 서버에 접속할 때 사용할 SSH 인증키의 값
      • DEPLOY_SOURCE_DIR : repository의 소스코드를 동기화할 원격 서버의 경로
      • DEPLOY_IMAGE_NAME : 원격 서버에서 빌드할 이미지 이름
      • DEPLOY_CONTAINER_NAME : 원격 서버에서 실행할 컨테이너 이름

 

1.1. 작성된 Workflows 파일

# .github/workflows/deploy.yaml

name: Push to EC2  # workflow의 이름

on: # workflow 가 실행되게 하는 event
  push: # deploy 브랜치가 push (또는 merge) 될 경우 아래의 job이 실행되게 한다.
    branches: deploy

jobs:
  deploy: # job의 이름
    runs-on: ubuntu-latest # job이 실행되는 runner 명시
    
    steps:
    # step - repository의 소스코드 내려 받기
    - uses: actions/checkout@v2 # 아래 actions 설명 참조

    # step - 내려 받은 소스코드를 원격 서버에 동기화 시키기
    - name: rsync deployments 
      uses: burnett01/rsync-deployments@5.2.1 # 아래 actions 설명 참조
      with: 
        switches: -avzr --delete
        path: ./
        remote_host: ${{ secrets.DEPLOY_HOST }}
        remote_user: ${{ secrets.DEPLOY_USERNAME }}
        remote_key: ${{ secrets.DEPLOY_SSH_KEY }}
        remote_path: ${{ secrets.DEPLOY_SOURCE_DIR }} # 동기화 시키고자 하는 원격 서버 내 경로

    # step - 동기화된 소스코드를 가지고 
    # [도커 이미지 빌드 -> 기존 도커 컨테이너 중단 -> 새 도커 컨테이너 실행]을 수행   
    - name: Build, run uploaded Docker container
      uses: appleboy/ssh-action@master # 아래 actions 설명 참조
      with: 
        host: ${{ secrets.DEPLOY_HOST }}
        username: ${{ secrets.DEPLOY_USERNAME }}
        key: ${{ secrets.DEPLOY_SSH_KEY }}
        script: |
          cd ~
          cd ${{ secrets.SOURCE_DIR }}
          docker build . -t ${{ secrets.DEPLOY_IMAGE_NAME }}
          if [ $(docker inspect -f '{{.State.Running}}' ${{ secrets.DEPLOY_CONTAINER_NAME }}) = "true" ]; then echo "Stopping current running docker container ..." && docker stop ${{ secrets.DEPLOY_CONTAINER_NAME }}; fi
          echo "Running docker container based on newly built image"
          docker run --name ${{ secrets.DEPLOY_CONTAINER_NAME }} --rm -d -p 8000:8000 ${{ secrets.DEPLOY_IMAGE_NAME }}          

 

1.2. 사용된 Actions

1.2.1. checkout actions

1.2.2. rsync actions

1.2.3. ssh-action actions

 

2. Actions 실행

2.1. 변경 사항 Push 하기

  • 소스코드의 변경사항을 main 브랜치에 커밋해준다.
    git add .
    git commit -m "{커밋 메세지}"
    git push origin main
    

2.2. main --> deploy merge 하는 PR

  • 코드를 배포하기 위해 변경된 코드를 deploy 브랜치에 머지하는 PR을 작성하고 merge를 수행한다.
    main 브랜치에서 deploy 브랜치로 merge하는 PR 작성

    main 브랜치에서 deploy 브랜치로 merge하는 PR 작성

     
    merge 실행하기

    merge 실행하기

2.3. Actions 실행 확인하기

  • deploy 브랜치로의 merge가 수행되면 동시에 workflow 파일에서 정의한 push trigger가 동작되어 아래와 같이 Actions의 실행으로 이어진다. (Repository 상단의 “Actions” 탭에서 확인해보자)

    (workflow가 실행되었다!)

    (workflow가 실행되었다!)

     
    (workflow가 실행되는 중이다!)

    (workflow가 실행되는 중이다!)

     

    • 앞서 작성한 workflows 파일(deploy.yaml)에서 정의한 actions들이 사용되는 것을 확인할 수 있다.

       

    • workflow 파일에서 정의한 job의 step이 순차적으로 실행되었고, 해당 Actions workflow가 성공적으로 수행되었음을 확인할 수 있다.

       

    • 배포된 코드 기반으로 실행 중인 API도 확인해보았다.

 

마무리하며

업무에서는 Github Actions를 활용한 CI/CD를 하고 있지 않기 때문에, 점심시간에 따로 경험이 있는 동료에게 물어보기도 하고 적용해서 삽질을 하기도 하면서 이런 생각이 들었다.

  • 이번 찍먹 에서는 배포에만 사용해봤지만, 각 브랜치에 push 하기 이전에 진행하는 unit-test를 workflows에 포함시키면 되겠다. (이미 그렇게들 많이 쓰는 것 같다)
    • 사실 Actions 의 예시가 여러가지가 있었지만, 배포하는 과정의 자동화에 적용하는게 내 입장에서 가장 실용적으로 다가와서 이렇게 해보긴 했다.
  • [새로운 이미지를 빌드 -> 실행 중인 컨테이너 중단 -> 새로운 이미지로 컨테이너 실행]의 프로세스가 이게 맞나...? 하는 생각이 들었다.
    • 이 방식의 단점이 있다면 어떤 점이 있을까?
    • 매번 이렇게 이미지를 빌드해야 하나 … ? 싶기도 하고 그렇다.
    • Dockerfile을 보고 이미지를 빌드하는 Actions가 있지는 않을까? 바로 컨테이너만 실행할 수는 없을까? (다 하고나니 가지처럼 뻗어나가는 의문점 … ;;; )
    • 비슷한 다른 예시를 찾아보면 AWS S3와 AWS Code Deploy를 엮어서 사용하는 예시도 있던데, 비용이 살짝 부담되었다.
    • 어느 방식이 “Best practice"일까?
  • 적절한 Actions를 marketplace 에서 찾고 적용하는 것이 핵심이겠다는 생각이 든다.

     

Reference