[deprecated] Github-actions 와 AWS Code Deploy를 이용한 Django 배포 자동화

0

Github-actions 와 AWS Code Deploy를 이용한 Django 배포 자동화

github-actionscode deploy 를 이용한 Django EC2 자동 배포 방법입니다.

사용 스택

자동화 flow

배포 자동화를 구축하기에 앞서 배포 자동화의 flow 를 한 번 정리해보도록 하겠다.

  1. github repo 의 특정 branch 에 코드가 푸쉬된다
  2. push event 를 감지한 CI가 code Deploy 에 배포하라는 트리거를 보낸다
  3. code Deploy 는 CI 에서 보낸 커밋 번호를 통해서 EC2 에 배포 트리거를 보낸다
  4. EC2는 deploy script 를 실행한다

우리는 CI 툴로 Github-actions 를 선택함.

CI란 무엇인가? - https://www.notion.so/CI-f56c09dcd7254817889abf46e50d09da

자동화 실습

EC2 설정

1. EC2 AMI
ubuntu 18.04 를 선택하고 다음


2. 인스턴스 유형 (t2.micro)
인스턴스는 프리티어가 적용되는 t2.micro 로 정하고 다음!

3. 인스턴스 세부정보 구성
IAM 역할 클릭 후 새 IAM 역할 생성

역할 만들기 클릭

- 역할 만들기 (AWS 서비스 - 일반 사용 사례 - EC2 - 다음)

- 권한 정책 연결 (Code Deploy 검색하고 [AWSCodeDeployFullAccess, AWSCodeDeployRole] 선택 - 다음)

- 태그 추가
무시하고 다음

- 검토
역할 이름에 원하는 이름 써주기 (이 글에서는 autoDeployEC2Role)

아래처럼 생성되면 완료. 다시 아까 EC2 인스턴스 만들던 화면으로 돌아가봅시다.

4. IAM 역할 새로고침 후 autoDeployEC2Role 선텍 - 다음


5. 스토리지 추가
크기를 프리티어 무료 제공 최대인 30GiB 로 변경 (디폴트 8GiB 해도 문제는 없음)

6. 태그 추가
나중에 이 이름을 통해서 EC2 를 간단하게 선택할 수 있다.

7. 보안그룹추가
보안그룹은 일단 80, 8000포트만 열고 위치 무관으로 설정 (실제 서비스에 사용하려면 좀 더 설정 필요)

8. 시작하기

새 키 페어를 만들고 다운로드
이 키페어는 절대 잃어버리거나 타인에게 노출 금지

잘 생성되었는지 확인

접속도 잘 되는지 확인

ubuntu에 codeagent-deploy 설치
codeagent-deploy 의 의존성 때문에 ruby를 먼저 설치

sudo apt update
sudo apt install ruby

wget 으로 codeagent-deploy 설치 (code deploy 의 명령을 EC2 가 수신하고 실행할 수 있게 해준다)

wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install

chmod +x ./install
sudo ./install auto

# 실행
sudo service codedeploy-agent start

그리고 몇가지 필요 요소를 미리 깔아둡시다.

sudo apt-get install python3-venv build-essential python3-dev

자 이제 AWS EC2 준비는 어느정도 끝났습니다. CodeDeploy 설정을 해봅시다.

Code Deploy IAM 역할 설정

Code Deploy Agent 용 IAM 역할 만들기

다음다음다음 눌러서 생성 완료 해줍시다. (아까 만든 EC2와 방금 만든 CodeDeploy IAM)

CodeDeploy 어플리케이션/배포 설정

aws 메뉴에서 CodeDeploy 선택

애플리케이션 생성 클릭

어플리케이션 이름 작성하고, 플랫폼은 EC2/온프레미스 선택

생성된 어플리케이션 내부에서 배포 그룹 생성 클릭

배포 그루비룸 입력하고, 아까 만들었던 CodeDeploy IAM 선택, 배포 유형은 현재 위치

밑에서 아까 만들어둔 EC2 인스턴스 선택

배포 설정은 AllAtOnce 로 하고, 로드밸런서는 꺼둡니다

  • 배포 유형 blue/green 과 로드 밸런서를 이용해서 무중단 자동배포도 가능할 것 같지만 일단 지금은 둘 다 배제하고 진행하겠습니다.

배포 그룹 생성 완료

이제 이 배포 그룹에서 배포를 생성하면 EC2에 자동으로 배포가 진행됩니다.
물론 그냥 막되는 건 아니기 때문에 조금의 설정이 더 필요합니다.

튜토리얼로 만들어둔 프로젝트를 한 번 봅시다.

튜토리얼 프로젝트

깃허브에 튜토리얼을 진행해 볼 수 있는 프로젝트를 올려놓았습니다.

https://github.com/Neulhan/djangoAutoDeployTutorial

프로젝트 내부에 아래와 같은 파일이 존재하는데 이 친구가 중요합니다

# appspec.yml
version: 0.0
os: linux
permissions:
  - object: /home/ubuntu/
    owner: ubuntu
    group: ubuntu
    mode: 644
    type:
      - directory
      - file
files:
  - source: ./
    destination: /home/ubuntu/djangoAutoDeployTutorial
#hooks:
#  BeforeInstall:
#    - location: scripts/beforeInstall.bash
#  AfterInstall:
#    - location: scripts/afterInstall.bash

CodeDeploy 는 이 appspec.yml 파일을 보고 배포를 진행하게 됩니다.

  • permissions 는 CodeDeploy 의 행동을 어떤 사용자 권한으로 할 지를 설정할 수 있습니다.
    여기서는 default인 ubuntu 그룹의 ubuntu 사용자로 644 권한을 가지고 명령을 처리할 것입니다. (chmod, chown 과 비슷함)

  • files 는 어디에서 어디로 파일을 복사할지를 정합니다. CodeDeploy 는 CodeBuild, S3, Github 등에서 코드를 가져올 수 있습니다. 그 source 에서 ./(전체) 를 복사해서, destination 으로 붙여넣습니다.

  • hooks 는 CodeDeploy 가 코드를 배포하는 과정에서 특정 이벤트가 발생할 때 코드를 실행할 수 있게 만들어줍니다. Install(코드를 복사해서 붙여넣는 이벤트)를 기준으로 BeforeInstall, AfterInstall 을 주로 사용할 것입니다. 해당 hook 이 trigger 될 때 실행할 명령을 정해둘 수 있습니다. 지금은 일단 주석처리 해두겠습니다.

그러면 이 프로젝트로 한 번 배포를 생성해보겠습니다. (fork해서 사용하세요!)

배포 생성

애플리케이션을 GITHUB에 저장을 선택하고, Github 토큰 이름에 자신의 사용자 이름을 입력합니다.
그리고 connect를 해준뒤 배포를 하고 싶은 리포지토리 이름과 커밋 아이디를 입력해줍시다.
(이 부분이 뒤에서 Github actions 를 통해서 자동화 될 부분입니다.)
그리고 추가 설정을 만지지 말고 배포 만들기를 클릭합니다.

성공 화면

인스턴스 /home/ubuntu

git clone 을 받은것 처럼 되어있습니다.

하지만 에러가 발생할 수도 있습니다.
배포 재시도를 한 번 눌러볼까요?

에러발생! file already exist 라는 에러인데요.

우리가 옮겨놓고 싶던 친구들이 이미 있으니 충돌 에러가 나는거죠

이 문제를 해결할 방법이 있을까요? 그리고 코드만 옮겨놓는 것 만으로는 완전 자동 배포라고 보기 힘들죠. 뭔가 추가적인 조치가 필요합니다.

appspec hooks

이제 아까 설명 드렸던 appspec의 hook 을 사용할 때가 왔습니다.
hook 중에서도 Install (Github에서 코드를 복사해서 인스턴스로 옮겨놓는 액션) 을 기준으로 발생하는 beforeInstallafterInstall 을 쓸겁니다.

# djangoAutoDeployTutorial/scripts/beforeInstall.sh

if [ -d /home/ubuntu/djangoAutoDeployTutorial ]; then
    sudo rm -rf /home/ubuntu/djangoAutoDeployTutorial
fi
sudo mkdir -vp /home/ubuntu/djangoAutoDeployTutorial

만약에 해당 디렉토리가 이미 존재하면, Install 전에 그 디렉토리를 삭제해주는 명령입니다.

# appspec.yml
version: 0.0
os: linux
permissions:
  - object: /home/ubuntu/
    owner: ubuntu
    group: ubuntu
    mode: 644
    type:
      - directory
      - file
files:
  - source: ./
    destination: /home/ubuntu/djangoAutoDeployTutorial
hooks:
  BeforeInstall:
    - location: scripts/beforeInstall.bash
#  AfterInstall:
#    - location: scripts/afterInstall.bash

appspec.yml 에서 hooks 와 하위의 BeforeInstall 을 풀어줍니다.

그리고 새로운 커밋 아이디로 배포를 다시 시도하면?

성공입니다!

같은 방식으로 afterInstall.zh 를 이용해서 배포 과정을 자동화시켜두겠습니다.

# djangoAutoDeployTutorial/scripts/afterInstall.sh

curl  -s -d "payload={\"text\":\"github 저장소 다운로드가 완료되었습니다\n패키지 관리를 시작합니다... \"}" "https://hooks.slack.com/services/blahblahblah"

python3 /home/ubuntu/djangoAutoDeployTutorial/scripts/afterInstall.py 1> /home/ubuntu/after.log 2> /home/ubuntu/after.err

curl  -s -d "payload={\"text\":\"배포종료 \"}" "https://hooks.slack.com/services/blahblahblah"
# djangoAutoDeployTutorial/scripts/afterInstall.py

print("after")
import os

os.system('sudo chmod -R gu+rwx /home/ubuntu/djangoAutoDeployTutorial')

os.system('python3 -m venv /home/ubuntu/djangoAutoDeployTutorial/venv')
os.system('sudo chown -R ubuntu.ubuntu /home/ubuntu/djangoAutoDeployTutorial')
os.system('sudo chmod -R gu+rwx /home/ubuntu/djangoAutoDeployTutorial')
os.system(
    '. /home/ubuntu/djangoAutoDeployTutorial/venv/bin/activate && pip install --upgrade pip && pip install -r /home/ubuntu/djangoAutoDeployTutorial/requirements.txt && python /home/ubuntu/djangoAutoDeployTutorial/manage.py migrate')
    

몇가지 이슈 때문에 afterInstallafterInstall.zh 에서 모든 것을 처리하지 않고, 같은 디렉토리의 afterInstall.py 를 호출함으로써 진행을 하겠습니다.

그 이유는 이 부분에서 실행될 때 에러가 일어날 확률이 초반엔 매우 높습니다. 완전히 자신의 프로젝트에 맞게 명령어를 세팅하려면 많은 디버깅이 필요합니다. 그래서 sh 파일에서 한 번에 로그파일을 남김으로써 디버깅을 용이하게 합니다. (로그 파일은 /home/ubuntu/after.log 에 에러 로그 파일은 /home/ubuntu/after.err 에 저장됩니다.

두 번째 이유는 가상환경 때문입니다. 제가 많이 부족한 초보 개발자라 sh 파일에서 가상환경을 유지한 채 과정을 진행할 방법을 찾지 못했습니다. 그래서 한 줄에서 실행될 경우 가상환경을 킨 채 진행이 가능한 os.system을 이용하기 위해 python으로 진행을 했습니다.

혹시 참고할 만한 사항이 있으면 댓글로 알려주시면 감사할 것 같습니다.

아무튼 이렇게 배포 이후에 requirements 재설치와, migrate 자동화가 가능해졌습니다. collectstatic 등 다른 추가 실행도 이 과정에 끼워넣을 수 있습니다.

업데이트 된 새로운 커밋 아이디로 배포를 시도해봅시다.

afterInstall 에 패키지 설치 등등 일거리를 넣어줬더니 33초가 걸렸습니다.

봤더니 db.sqlite3 와 venv 가 생겨있습니다.

가상환경을 켜고 python3 manage.py runserver 0.0.0.0:8000 을 입력해서 아이피:8000 포트로 접속해봅시다.

url 에 [자기 인스턴스 ip]:8000 을 입력하면..?

감동적이네요

이제 거의 다왔습니다. 아마도..?

uwsgi 와 nginx 설정해주기

명색이 배포 자동화인데, 매번 들어와서 runserver 을 해줄 수는 없겠죠.

uwsgi 와 nginx 를 이용해서 서버가 알아서 django 를 돌리고 있도록 해줍시다.

uwsgi 설정

$ mkdir -p /etc/uwsgi/sites # -p 옵션은 중간에 없는 디렉토리까지 같이 생성해준다.
$ sudo vi /etc/uwsgi/sites/config.ini
[uwsgi]
uid = ubuntu
# 여기서  base와 project는 재사용되는 경로를 변수로 설정해둔 것
base = /home/%(uid)/djangoAutoDeployTutorial
project = config

# venv의 경로 설정
home = %(base)/venv
# 프로젝트가 시작되기 전에 해당경로로 이동한다.
chdir = %(base)/%(project) 
# 해당 경로에서 wsgi 모듈의 경로 설정
module = %(project).wsgi:application

master = true
processes = 5

socket = /run/uwsgi/%(project).sock
chown-socket = %(uid):www-data
chmod-socket = 660
vacuum = true
$ sudo vi /etc/systemd/system/uwsgi.service

[Unit]
Description=uWSGI Emperor service

[Service]

ExecStartPre=/bin/bash -c 'mkdir -p /run/uwsgi; chown ubuntu:www-data /run/uwsgi'
ExecStart=/home/ubuntu/djangoAutoDeployTutorial/venv/bin/uwsgi --emperor /etc/uwsgi/sites
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target
$ sudo systemctl enable uwsgi
$ sudo systemctl start uwsgi
$ sudo systemctl status uwsgi
$ sudo apt-get install -y nginx
$ sudo vi /etc/nginx/sites-available/config

server {
    listen 80;
    server_name [IP OR DOMAIN]; # 서버 도메인 or ip 추가

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/ubuntu/djangoAutoDeployTutorial/;
    }

    location / {
        include         uwsgi_params;
        uwsgi_pass      unix:/run/uwsgi/config.sock;
    }
}
$ sudo ln -s /etc/nginx/sites-available/[project] /etc/nginx/sites-enabled
$ sudo nginx -t
$ sudo systemctl restart nginx
$ sudo systemctl enable nginx
$ sudo ufw allow 'Nginx Full'

IAM 설정은 이 글의 초반을 참고 https://jojoldu.tistory.com/281

profile
영어영문학과 출신의 개발자 늘한입니다. Python 을 좋아합니다.

0개의 댓글