[Django] Github Actions로 django 무중단배포 자동화하기

우노·2023년 8월 3일
0

환경

  1. AWS EC2 서버 인스턴스
  2. python3-pip & python3.8-venv python, 가상환경
  3. nginx 웹 서버
  4. gunicorn 웹 애플리케이션 실행
  5. 명령어 nohup 무중단배포
  6. Github Actions workflow 자동화

시작하면서

Dockerfile을 작성했지만 EC2에서 컨테이너를 구동하면 문제가 없어도 바로 Exited로 넘어가고 AWS Codedeploy 비용은 꺼려지고 gunicorn.service는 계속 다양한 exit-code로 Failed되고..
이런 다양한 이유로 배포 수작업을 피할 수 있는 가장 간단한 방법으로 EC2에서 무중단배포하는 과정을 Github Actions로 자동화하는 방법을 시도했다 !

물론 이 방법은 토이프로젝트 같은 간단한 프로젝트 또는 배포 경험에 활용하기 좋을 것 같다. 큰 프로젝트나 실제 운영을 목적으로하는 프로젝트는 위에 내가 실패한 방법들에 도전하는 것이 좋다🥲

EC2 생성, nginx와 gunicorn 설치 및 설정은 다양한 자료가 이미 존재하기 때문에 이 글에서는 Github Actions와 workflow에 초점을 맞춰보려고 한다 !

사전 세팅

  • EC2 인스턴스 생성
  • sudo apt-get install로 git & python3-pip & python3.8-venv 설치
  • nginx 설치 및 설정
  • pip install gunicorn + 설정
  • 프로젝트 git clone
  • 프로젝트 폴더에 비밀키 파일 작성

deploy.yml

.github/workflows/deploy.yml를 자동화시키고 싶은 레포지토리에 생성해주었다.

Event

name: Django EC2 Deploy

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  ci: ...
  cd: ...

main 브랜치에 push 또는 pull_request 이벤트가 발생했을 때 CI/CD를 수행하도록 설정했다. 혹시 꼬일까봐 다른 브랜치는 추가하지 않았다.
jobs에서 CI와 CD를 나누어서 실행하였다.

CI

설정

  ci:
    name: 'Django CI'
    runs-on: ubuntu-20.04
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.8]

EC2의 OS가 ubuntu 20.04라서 runs-on에 해당 OS 버전을 설정해주었다.
max-parallel: 4는 Github Actions에서 제공하는 기본 파일의 설정이다 matrix에서는 빌드를 진행할 python 버전을 설정해주었다. [ 3.8, 3.9 ] 처럼 여러 버전에서 빌드가 가능하다 !
⚠ 3.10은 3.1로 인식되니 '3.10' 문자열로 작성해야 한다.

steps

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}

    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: create-json
      id: create-json
      uses: jsdaniell/create-json@1.1.2
      with:
        name: "secrets.json"
        json: ${{ secrets.SECERT_JSON }} 
        
    - name: Run Tests
      run: |
        python manage.py test

위에서 설정해둔 OS 버전과 python 버전에서 빌드를 진행한다.
pip install -r requirements.txt이 정상 작동하는지, python manage.py test로 테스트 했을 때 문제가 없는지 확인한다.
비밀키 파일은 Github Actions에서 secrets.json 생성하기 이전 글 참고 !

개인적으로 CI는 로컬에서 문제 없이 python manage.py runserver가 실행되었다면 실패할 일이 없다고 느껴졌다.

CD

설정

  cd:
    name: 'Django CD'
    needs: ci
    runs-on: ubuntu-20.04

처음에는 CI와 CD를 다른 파일에 위치해서 on에 workflow_run으로 CI action이 완료되면 CD가 실행되도록 작성했다. 이후에 Job-Chaining이 생각나서 간단하게 needs로 ci가 실행되면 cd가 실행되도록 설정했다. 두 방법에 큰 차이는 없는 것 같다.

steps

문제가 정말 많았던 CD steps.. 기본 구조는 아래와 같다.

    steps:
    - name: EC2 ssh connection test
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.SSH_HOST }}			# 연결할 EC2 
        username: ${{ secrets.SSH_USERNAME }}	# 사용자
        key: ${{ secrets.SSH_PEM }}				# pem 파일 내용
        command_timeout: 3m						# timeout 시간 설정
        script: |
          # SSH로 실행하고 싶은 명령어 작성
  1. host 탄력적IP를 연결한 EC2를 지정해준다. IP보다는 퍼블릭 IPv4 DNS를 추천한다.
  2. username 작업할 사용자 이름을 지정해준다. (ex.ubuntu)
  3. key pem 키 파일의 내용을 전달한다. 터미널에서 type 키파일이름.pem로 내용을 확인하고 -----BEGIN RSA PRIVATE KEY----- 부터 -----END RSA PRIVATE KEY-----까지 복사해야 한다.
  4. command_timeout은 필수가 아니다. 뒤에 트러블슈팅에 나올 에러 때문에 설정했다 ^____^

        script: |
          sudo apt-get update
          sudo apt-get -y upgrade
          cd {프로젝트 폴더 위치}
          source {가상환경 이름}/bin/activate
          git pull
          sudo lsof -t -i tcp:8000 | xargs kill -9
          pip install -r requirements.txt
          python manage.py makemigrations	
          python manage.py migrate
          nohup gunicorn --bind 0.0.0.0:8000 {django 프로젝트명}.wsgi > nohup.out 2> nohup.err < /dev/null &
          exit 0

script에서는 SSH에서 입력하고 싶은 명령어를 작성한다. 가상환경을 켜는 것을 주의하자 !
sudo lsof -t -i tcp:8000 | xargs kill -9 로 이전에 8000포트에서 실행 중이던 프로세스를 종료한다. pid를 확인하여 kill하기 어려워서 해당 방법을 선택했다.
nohup gunicorn --bind 0.0.0.0:8000 {django 프로젝트명}.wsgi > nohup.out 2> nohup.err < /dev/null & 을 통해 무중단배포를 실행해준다.

테스트

Github Actions로 배포가 잘 되는지 확인하고 싶다면 8000 포트에서 실행하던 서버를 종료하고 프로젝트 레포 main 브랜치에 pull 또는 PR을 해보자 !
script 내용을 사전에 직접 터미널에서 실행해보는 것도 좋다.


트러블슈팅

Run Command Timeout

nohup gunicorn --bind 0.0.0.0:8000 {django 프로젝트명}.wsgi &로 실행하면 분명 서버는 잘 돌아가는데 exit 0에도 터미널이 종료되지 않아 에러가 발생한다. 문제는 없는데 ❌가 보기 싫어서 원인을 찾아봤다.
Issue #40 the action keeps waiting for stdin, stdout, and stderr
대기 상태여서 터미널이 종료되지 않는 것으로 보인다. 모든 nohup 명령어에 해당된다.

댓글에 있는 아래 코드로 수정해주었다.
nohup gunicorn --bind 0.0.0.0:8000 {django 프로젝트명}.wsgi > nohup.out 2> nohup.err < /dev/null &

nohup.out: permission denied

코드를 수정하니 nohup.out 권한이 없어서 서버 실행이 되지 않았다.
nohup.out 소유자가 root라서 사용자를 root로 바꾸고 권한을 바꿔주었다.
물론 이 부분은 직접 SSH 접속해서 미리 해야하는 작업이다.

sudo su - root
# nohup.out이 있는 폴더로 이동
chmod 777 nohup.out  # 모든 권한 허용



자동화가 부실한 느낌이라..
미리 말했던대로 간단한 프로젝트에서 수작업 배포가 귀찮을 때 활용하길 바란다.
그래도 복잡하지 않은만큼 직접 git pull 하는 것보다는 편하다😂

django-github-action 삽질 참고용 레포

profile
기록하는 감자

1개의 댓글

comment-user-thumbnail
2023년 8월 3일

이런 유용한 정보를 나눠주셔서 감사합니다.

답글 달기