장고 프로젝트 github action으로 배포 자동화 하기 feat. github action, github package(ghcr.io), docker-compose

     

 

 

 

🧑‍💻 들어가면서

이전 포스팅 중에 nginx와 django 앱을 github action을 이용해 배포하는 것을 다루었었다. 
https://cholol.tistory.com/586 

 

이때는 컨테이너 환경을 사용하지 않고 EC2에 직접 nginx와 django, uwsgi를 사용해서 서버를 실행했기 때문에 배포할 때 리모트로 들어가서 서버를 껐다 켜는(?) 명령어를 실행했다. 

 

이번에는 docker를 이용한 환경에서 github action을 통해 자동으로 docker image를 만들고, github package에 이미지를 업로드하고, 해당 ec2에서 만들어진 이미지를 자동으로 pull 받아서 배포가 되는 환경을 구성하려고 한다. 

 

왜?

 

사이드 프로젝트를 진행하는데 변경사항이 빈번하게 일어나고 메인 푸시, 서버 배포, 메인 푸시, 서버 배포 이런 식으로 진행하다 보니 자동화가 너무 급했다.. ㅋㅋ github action 안 쓰고 서버에 직접 들어가서 docker-compose build, up -d를 수동으로 하고 있잖아 현타가 와서...

 

 

🏃‍♂️ Github Action 만들기

🧱 docker image build and push action

github action은 이전 CI/CD포스팅에서 사용한 것과 거의 유사하게 사용한다.

먼저 main이 push되었을 때 django 앱 초기화 및 이미지를 빌드하고, github package (ghcr)에 이미지를 업로드한다. 그다음 EC2에 리모트로 접속해서 업로드한 docker image를 pull 하고 container를 재실행한다. 

 

docker image를 저장하기 위한 저장소로 github package를 사용한 것은 단순하다. github action 쓰니까.. ㅋㅋ github package 말고 도커 이미지를 저장해서 사용할 수 있는 서비스는 많다. 어느 정도까지는 공짜이기 때문에 개인이 사용한다면 크게 무리 없이 쓸 수 있다. 가장 큰 곳은 아마 도커 허브가 아닐까? 

 

github package 가격 정보

 

도커 패키지도 public repository에 대해서는 무료라고 한다. private나 organization에서 private한 repo를 사용할 때는 위처럼 용량 제한이 있다. 엔터프라이즈 요금도 스토리지가 생각보다 크지가 않다 ;; 

 

일단 나는 github enterprise에 가입된 organization이라 50GB는 쓸 수 있는 것 같다. 😌

 

먼저 main이 push 되었을 때 도커 이미지를 만들고 ghcr에 push하는 action코드를 보자.

 

name: Build and Push Docker Image

on:
  push:
    branches:
      - main

jobs:
  build-and-push:
    runs-on: ubuntu-latest

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

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Get current timestamp
      id: timestamp
      run: echo "::set-output name=TIME::$(date +'%Y%m%d%H%M%S')"

    - name: Log in to GitHub Container Registry
      uses: docker/login-action@v1
      with:
        registry: ghcr.io
        username: ${{ github.repository_owner }}
        password: ${{ secrets.PERSONAL_TOKEN }}

    - name: Build and push Docker image
      uses: docker/build-push-action@v6
      with:
        context: .
        file: ./Dockerfile
        push: true
        tags: |
          ghcr.io/${{ github.repository_owner }}/app-django:latest
          ghcr.io/${{ github.repository_owner }}/app-django:${{ steps.timestamp.outputs.TIME }}

 

on: 에 적혀있는 내용은 언제이 action이 수행되는지에 대한 내용이다. main이 push 되었을 때 자동으로 실행하도록 적혀있다.

jobs: 에 적혀있는 것이 실제로 이 action이 수행하는 작업이다. 바로 애라 steps:가 일련의 작업을 절차적으로 적어놓은 코드다. 

 

  • name: 해당 step의 명칭
  • uses: 사용할 라이브러리. github action도 누군가가 만들어놓은 코드를 가져다가 쓸 수 있다. github action marketplace라는 곳에서 검색해서 자신이 원하는 것을 사용할 수 있다. 
  • with: 작업을 위한 command가 들어간다. 실제 작업되는 코드라고 보면 된다.

핵심이 되는 step은 맨 마지막 Build and push Docker image이다. 여기서 실제로 docker를 빌드하고 ghcr.io에 푸시하게 된다. dockerfile의 경로는 해당 repository를 root로 생각하고 적으면 된다. 나는 django 프로젝트 root경로에 Dockerfile을 만들어 놨기 때문에./Dockerfile로 지정했다.

 

ghcr.io에 푸시할 때 사용되는 경로는 github 계정이름인데, repository_owner를 입력하면 된다. 그 뒤에는 이미지 이름과 버전 tag를 적게 되는데 이건 원하는 값을 적으면 된다. 나는 latest와 timestamp를 이용해 태그를 달았다.

 

ghcr.io에 로그인하는 부분은 중간에 있는 step Log in in Github Container Repository인데 깃헙에서 제공하는 Personal Access Token을 사용해서 접속 가능하다. github에 setting에서 secret메뉴에 보면 PAT를 생성하는 메뉴가 있다. 생성할 때 패키지 접근 권한을 체크하고 토큰을 생성하면 해당 PAT로 ghcr에 접속 가능하다. 

 

action에서 보면 secrets.XXX라는 변수를 사용하는데, 이건 github에서 제공하는 secrets에서 값을 가져오겠다는 뜻이다. github repo에서 setting에 가면 secret value를 설정할 수 있다.

 

Secret 설정

 

Secret 값을 한번 설정하면 설정한 사람도 볼 수 없기 때문에 처음에 잘 설정해야 한다. (만약 값이 바뀌거나 잃어버리면 다시 생성하는 게 방법)

 

이렇게 만든 github action 파일은 repo에 .github/workflows/xxx-github-action.yaml에 만들어놓으면 github이 자동으로 인식한다. 

 

github action 메뉴

실제로 위 yaml을 추가하고 main push 하면 자동으로 github repo 페이지 action메뉴에서 job이 실행되는 것을 확인할 수 있다.

 

 

⬆️ deploy action

deploy 액션은 build보다 간단하다. 서버에 접속해서 이전 단계에서 만들어진 docker image를 pull 하고, docker를 재시작하면 된다.

 

name: Deploy Sandbox Django

on:
  workflow_run:
    workflows: ["Build and Push Docker Image"]
    types:
      - completed

jobs:
  deploy:
    runs-on: ubuntu-latest

    if: ${{ github.event.workflow_run.conclusion == 'success' }}

    steps:
    - name: remote ssh and deploy
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.SANDBOX_SERVER_IP }}
        username: ${{ secrets.SANDBOX_SERVER_USERNAME }}
        key: ${{ secrets.SANDBOX_SERVER_SSH_KEY }}
        port: 22
        script: |
          docker login ghcr.io -u {{ github.repository_owner }} -p ${{ secrets.PERSONAL_TOKEN }}
          cd /home/ubuntu/havit-backend-django
          docker-compose pull
          docker-compose build
          docker image prune -f
          docker-compose up -d

 

 

지금은 서버가 1대만 띄워져 있기 때문에 한 곳에만 접속해서 명령어를 실행하지만, 만약 서버가 여러대라면 순차적으로 접속해서 명령어를 실행하도록 하면 된다. 하지만 서버를 여러 대 띄울 정도라면 다른 배포방식을 선택했겠지 ㅎㅎ 아직은 한 대니까 그냥 쓰자

 

이전에 github action을 통해서 ghcr.io를 push할 때랑 다른 점은 docker login을 통해 로그인한다는 점이다. 이전엔 github action 인스턴스에서 로그인했기 때문에 docker/login_action이라는 패키지의 도움을 받아서 로그인했지만, ec2에 remote로 접근했기 때문에 ec2인스턴스에서 ghcr에 로그인해야 한다. 이럴 때는 docker login명령어를 쓰면 로그인 가능하다.

 

docker-compose.yaml파일에 이미 이미지 경로를 적어놨기 때문에 그냥 단순히 docker-compose pull만 하면 이미지 다운로드가 가능하다. 참고로 이전에 올린 이미지 경로는 github package 메뉴에서 확인 가능하다. package메뉴는 repo 쪽에 있는 게 아니라 더 상위에 있다는 것을 참고하자.

 

packages 메뉴에서 이미지 확인 가능

 

docker-compose pull 후 docker-compose up 만 하면 되지만 나는 docker-compose build와 docker image prune 이라는 명령어를 추가했다. docker-compose에 django-app만 있는 게 아니라 nginx도 있기 때문에 nginx는 아직 ghcr에 올리는 것으로 만들지 않았다. 사실 별로 변동사항이 없기 때문에 굳이 ghcr에 올리지 않아도 될 것 같긴 하다. 

 

docker image prune은 ec2에 쌓여있는 이전 버전 이미지들을 삭제하기 위한 명령어다. 요걸 해주지 않으면 EC2에 이미지가 계속 쌓여서 disk 용량 부족이 발생할 수 있다. 

 

 

🗑️ delete old image action

github package에서는 자동으로 옛날 이미지를 삭제해주는 옵션을 제공해주지 않는다!!

엔터프라이즈를 사용해도 20GB밖에 제공해주지 않기 때문에 오래 사용하다 보면 용량을 더 사용해서 추가요금을 낼 수 있다. 이럴 때는 과거 이미지를 삭제해야 하는데, github 페이지에서는 일일이 수동으로 하나하나 삭제하는 방법밖에 제공하지 않는다. 

 

배포 자동화 했으니 이것도 자동화 시켜주자.

 

name: Delete Old Docker Images

on:
  schedule:
    - cron: "0 0 * * *"  # Runs daily at midnight
  workflow_dispatch:  # Allows manual triggering

jobs:
  clean-ghcr:
    runs-on: ubuntu-latest

    steps:
    - name: Delete old Docker images
      uses: snok/container-retention-policy@v2
      with:
        image-names: app-django # 앱 이름
        cut-off: "1 day ago UTC"
        timestamp-to-use: created_at
        account-type: org
        org-name: my organization # 내 오가니제이션 이름
        keep-at-least: 1
        token: ${{ secrets.PERSONAL_TOKEN }}

 

 

위 액션은 날마다 00시에 하루 지난 이미지를 삭제하는 github action이다. workflow_dispatch: 를 on:에 추가하면 github 홈페이지에서 수동으로 action을 실행시킬 수 있다.

 

 

 

 

⌚️ 마무리

github에서 제공하는 무료(제한적) 기능들로 생산성을 높일 수 있는 일들은 많다. 특히 개발단계에서 CI/CD를 프로덕션 레벨로 구현하기는 힘들다. 구현이 힘든 게 아니라 계속 바뀌므로, 번거롭다고 표현하는 게 더 좋겠다. 

 

사이드 프로젝트 하면서 항상 하는 고민이 이걸 k8s로 만들어놔 말아? 인 것 같다. 미리 k8s로 구성해 놓으면 확실히 편하긴 한데, 아직 개발단계에서는 너무 이른 단계가 아닐까? 개발할 때는 조그마한 서버 하나만 있으면 돌아가기 때문에 k8s로 구성하는 것조차 너무 허들이 크다. 

 

따라서 가장 단순하고, 쉽고, 빠르게 구현하는 것이 개발단계에서의 필요한 마음가짐이다. github action은 빠르고 간단하게 CI/CD를 만들 수 있기 때문에 아주 유용한 툴이다. 쓸 때마다 좀 헷갈리기는 하는데,, yaml 특성상 그게 그거 같단 말이지...

 

아무튼 github aciton으로 docker 빌드, 배포, package관리까지 끝~

 

* 더 나아가 슬랙 빌드 완료 시 슬랙 알림도 넣으면 개꿀~

 

 

 

 

 

 

반응형

댓글

Designed by JB FACTORY