다양한 이유로 배포 수작업을 피할 수 있는 가장 간단한 방법으로 EC2에서 재배포하는 과정을 Github Actions로 자동화하는 방법을 시도했다 !
물론 이 방법은 토이프로젝트 같은 간단한 프로젝트 또는 배포 경험에 활용하기 좋을 것 같다. 큰 프로젝트나 실제 운영을 목적으로하는 프로젝트는 위에 내가 실패한 방법들에 도전하는 것이 좋다🥲
EC2 생성, nginx와 gunicorn 설치 및 설정은 다양한 자료가 이미 존재하기 때문에 이 글에서는 Github Actions와 workflow에 초점을 맞춰보려고 한다 !
.github/workflows/deploy.yml
를 자동화시키고 싶은 레포지토리에 생성해주었다.
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:
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:
- 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:
name: 'Django CD'
needs: ci
runs-on: ubuntu-20.04
처음에는 CI와 CD를 다른 파일에 위치해서 on에 workflow_run으로 CI action이 완료되면 CD가 실행되도록 작성했다. 이후에 Job-Chaining이 생각나서 간단하게 needs로 ci가 실행되면 cd가 실행되도록 설정했다. 두 방법에 큰 차이는 없는 것 같다.
문제가 정말 많았던 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로 실행하고 싶은 명령어 작성
host
탄력적IP를 연결한 EC2를 지정해준다. IP보다는 퍼블릭 IPv4 DNS를 추천한다.username
작업할 사용자 이름을 지정해준다. (ex.ubuntu)key
pem 키 파일의 내용을 전달한다. 터미널에서 type 키파일이름.pem
로 내용을 확인하고 -----BEGIN RSA PRIVATE KEY-----
부터 -----END RSA PRIVATE KEY-----
까지 복사해야 한다.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 내용을 사전에 직접 터미널에서 실행해보는 것도 좋다.
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 권한이 없어서 서버 실행이 되지 않았다.
nohup.out 소유자가 root라서 사용자를 root로 바꾸고 권한을 바꿔주었다.
물론 이 부분은 직접 SSH 접속해서 미리 해야하는 작업이다.
sudo su - root
# nohup.out이 있는 폴더로 이동
chmod 777 nohup.out # 모든 권한 허용
자동화가 부실한 느낌이라..
미리 말했던대로 간단한 프로젝트에서 수작업 배포가 귀찮을 때 활용하길 바란다.
그래도 복잡하지 않은만큼 직접 git pull 하는 것보다는 편하다😂
이런 유용한 정보를 나눠주셔서 감사합니다.