안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

AWS (Amazon Web Service)

[AWS] EC2, RDS에 Django REST API 서버 배포하기 (Amazon Linux 2, PostgreSQL, Gunicorn)

피그브라더 2020. 11. 29. 19:32

여기서는 EC2 인스턴스(AMI : Amazon Linux 2)와 RDS 인스턴스(엔진 : PostgreSQL)를 이미 생성했고, EC2 인스턴스에 SSH로 접속하는 방법을 알고 있으며, EC2 인스턴스에서 RDS 인스턴스로 접근이 가능하도록 인바운드 규칙까지 설정해둔 상태라고 가정한다. 더불어 EC2 인스턴스에 Nginx라는 웹 서버를 깔고 기본적인 설정도 이미 해둔 상태를 가정한다. 이와 관련해서는 EC2에 대해 설명하는 이 포스팅, RDS에 대해 설명하는 이 포스팅, 그리고 EC2에 Nginx를 세팅하는 것을 다루는 이 포스팅을 반드시 먼저 읽고 오길 바란다. 아래에서 설명하는 명령어들은 기본적으로 해당 EC2 인스턴스에 SSH 접속이 이뤄진 상태에서 입력하는 것들에 해당한다고 보면 된다.

 

* 하나의 EC2 인스턴스에 프론트 엔드 서버와 백 엔드 서버를 두는 경우를 가정한다.

* 즉 하나의 웹 서버(Nginx)가 정적 파일의 서비스뿐 아니라 백 엔드 서버로 요청을 전달하는 프록시 서버 역할도 수행할 것이다.

* 이러한 방식(프록시 서버 이용)의 장점은 CORS 문제와 쿠키 응답 문제의 해결이다. 관련 내용은 이곳을 참고하자.

* 또한, Django 어플리케이션의 URL은 모두 'api/'로 시작한다고 가정한다. 따라서 클라이언트도 API 호출 시 'api/'를 붙여야 한다.

 

1. 기본 소프트웨어 설치

$ sudo yum update

 

현재 EC2 인스턴스에 설치되어 있는 모든 소프트웨어 패키지들을 업데이트한다. 개발자라면 yum 혹은 apt 명령어에 대해 한 번쯤은 들어봤을 것이다. yum과 apt는 둘 다 Linux 운영체제에서 소프트웨어 패키지들을 쉽게 설치 및 관리할 수 있도록 도와주는 패키지 관리자(Package Manager)이다. 즉 Linux 운영체제의 컴퓨터에서는 각종 소프트웨어를 설치할 때 yum 혹은 apt를 사용하게 된다.


$ sudo yum groupinstall "Development Tools" 
$ sudo yum install zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel xz xz-devel

 

개발을 위한 각종 도구 및 유틸리티(git, gcc 등등)를 설치한다. yum은 패키지들을 용도에 따라 그룹으로 분류하므로, 그룹 단위로도 패키지 설치가 가능하다.

 

2. 파이썬 가상 환경 구축

2-1. pyenv 패키지 설치

pyenv 패키지는 하나의 로컬 시스템에서 다양한 버전의 파이썬을 사용하고 관리할 수 있도록 하는 도구이다. 다음은 pyenv 패키지를 설치하고 pyenv를 사용하기 위한 몇 가지 초기 설정 작업을 수행하는 것을 나타낸다.

 

# pyenv 패키지 설치 (Github을 통해 직접 다운로드)
git clone https://github.com/pyenv/pyenv.git ~/.pyenv


# PYENV_ROOT : 사용자 정의한 쉘 변수로서, export 명령어에 의해 환경 변수로 지정된다.
# PATH : OS 환경 변수로서, pyenv의 경로를 추가하여 갱신된다.
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile 

echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile 

echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bash_profile 

# 쉘 스크립트 파일 실행 (쉘 스크립트 변동 사항 적용)
source ~/.bash_profile

 

2-2. pyenv-virtualenv 패키지 설치

pyenv-virtualenv 패키지는 pyenv를 이용하여 특정 파이썬 버전의 가상 환경을 쉽게 만들고 관리할 수 있도록 하는 도구이다.

 

# pyenv-virtualenv 패키지 설치 (Github을 통해 직접 다운로드)
git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv


# eval : 가능한 모든 치환을 수행한 후 명령행을 수행  
# pyenv-virtualenv가 정상 동작 할 수 있도록 쉘 설정 파일에 초기화 코드를 추가
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bash_profile

# 쉘 스크립트 파일 실행 (쉘 스크립트 변동 사항 적용)

source ~/.bash_profile

 

2-3. 파이썬 및 가상 환경 설치

pyenv를 이용하여 원하는 버전의 파이썬을 설치하고, 그 버전을 바탕으로 가상 환경을 생성한다.

 

# 파이썬 3.6.7 버전 설치
pyenv install 3.6.7

# 파이썬 3.6.7 버전을 사용하는 가상 환경 생성
pyenv virtualenv 3.6.7 {가상 환경 이름}

 

2-4. 프로젝트 폴더 Clone 및 가상 환경 지정

원격 저장소에서 Django 프로젝트의 리포지토리를 Clone 해온다. 그리고 그 폴더에 들어가면 자동으로 앞서 만든 가상 환경에 진입이 되도록 설정한다.

 

# 원격 저장소에서 프로젝트 리포지토리 Clone
git clone {원격 저장소의 프로젝트 리포지토리 주소}


# 가상 환경 선택 (해당 폴더에 들어가면 자동으로 가상 환경에 진입)
cd {프로젝트 폴더 이름}
pyenv local {가상 환경 이름}

 

3. PostgreSQL 클라이언트 설치

Django 어플리케이션이 RDS 인스턴스의 PostgreSQL 서버와 통신할 수 있도록 PostgreSQL 클라이언트를 설치해줘야 한다. 그리고 나면 당연히 Django의 설정 파일에도 PostgreSQL 서버를 사용하도록 설정을 해줘야 하는데, 이에 대해서는 아래에서 다루도록 하겠다. 일단 PostgreSQL 클라이언트부터 설치하고, 이를 이용하여 RDS 인스턴스의 PostgreSQL 서버에 접속한 뒤 Django 어플리케이션이 사용할 데이터베이스를 하나 생성해주자. 참고로 루트 유저의 이름은 특별히 바꿔주지 않았다면 postgres이다.

 

# PostgreSQL yum 리포지토리 추가
sudo tee /etc/yum.repos.d/pgdg.repo << EOF
> [pgdg12]
> name=PostgreSQL 12 for RHEL/CentOS 7 - x86_64
> baseurl=https://download.postgresql.org/pub/repos/yum/12/redhat/rhel-7-x86_64
> enabled=1
> gpgcheck=0
> EOF

# yum 캐시 재구성
sudo yum makecache


# PostgreSQL 설치
sudo yum install postgresql12 postgresql-devel

# PostgreSQL 서버에 접속 후 Django 어플리케이션이 사용할 데이터베이스 생성
psql \
--host={데이터베이스 서버 호스트명} \
--port={데이터베이스 서버 포트 번호} \
--username={루트 유저 이름} \
--password \
--dbname={루트 유저 이름}

   create database {데이터베이스 이름};  
   \q

 

4. EC2 인스턴스의 환경 변수 설정

$ echo 'export DJANGO_SETTINGS_MODULE="###"' >> ~/.bash_profile

 

Django 어플리케이션은 DJANGO_SETTINGS_MODULE이라는 이름의 환경 변수의 값을 읽어와서 어떠한 설정 파일을 사용할지 결정한다. 따라서 위 명령어를 통해 EC2 인스턴스의 환경 변수 목록에 DJANGO_SETTINGS_MODULE을 추가해주도록 하자. ###에는 사용할 Django 설정 파일의 상대 경로를 적어줘야 한다. 만약 Django 프로젝트의 루트 폴더 하위에 존재하는 config/settings/production.py 파일을 사용할 것이라면 config.settings.production이라고 적어야 한다.


$ echo 'export RDS_DB_NAME="{데이터베이스 이름}"' >> ~/.bash_profile
$ echo 'export RDS_USERNAME="{루트 유저 이름}"' >> ~/.bash_profile
$ echo 'export RDS_PASSWORD="{루트 유저 패스워드}"' >> ~/.bash_profile
$ echo 'export RDS_HOSTNAME="{데이터베이스 서버 호스트명}"' >> ~/.bash_profile
$ echo 'export RDS_PORT="{데이터베이스 서버 포트 번호}"' >> ~/.bash_profile

 

Django 어플리케이션이 접근할 RDS 인스턴스의 데이터베이스 서버 정보도 환경 변수로 관리해주는 게 좋다. 이렇게 설정된 환경 변수 값들은 Django 설정 파일에서 읽어오게 될 것이다(뒷부분 설명 참고).

 

※ Django Secret Key도 환경 변수로 관리하는 것을 권장한다. 여기서 다루진 않겠다.


$ source ~/.bash_profile

 

수정한 쉘 스크립트 파일을 실행한다. 즉, 쉘 스크립트 변동 사항을 적용한다.

 

5. 파이썬 의존성 패키지 설치

$ cd {프로젝트 폴더 절대 경로}

 

Clone 해온 프로젝트 폴더로 이동한다. 그러면 앞서 구축한 가상 환경에 자동으로 진입될 것이다.


$ pip install --upgrade pip

 

pip을 최신 버전으로 업그레이드한다.


$ pip install -r requirements.txt

 

pip을 이용하여 해당 프로젝트의 파이썬 의존성 패키지들을 일괄 설치한다. 단, 해당 프로젝트에서 필요로 하는 파이썬 패키지(Django, Django REST Framework 포함)들의 목록이 requirements.txt 파일에 명시되어 있음을 가정한다(이것이 관습이다). 참고로 여기에는 psycopg2 패키지도 반드시 포함되어 있어야 한다. PostgreSQL 서버에 Django가 접근하기 위함이다.

 

6. Django 프로젝트 설정 및 테스트

6-1. Django 설정 파일 수정

앞에서 말한 것처럼 이제 Django 설정 파일에도 데이터베이스 관련 설정을 해줘야 한다. 접근할 데이터베이스의 종류를 SQLite3에서 PostgreSQL로 변경해주고(psycopg2 패키지가 설치되어 있어야 함), 미리 생성해 놓은 데이터베이스를 Django와 연결해준다(이 과정에서 os.environ.get 함수를 이용하여 환경 변수의 값을 읽어옴). 이로써 Django 어플리케이션이 PostgreSQL 서버와 통신하며 데이터를 주고받을 수 있게 되었다. 다음으로, ALLOWED_HOSTS 옵션에는 EC2 인스턴스의 IP 주소를 지정해주도록 하자.

 

import os

# PostgreSQL 데이터베이스 연결

DATABASES = {
    'default': {
        'ENGINE''django.db.backends.postgresql_psycopg2',
        'NAME': os.environ.get('RDS_DB_NAME'),
        'USER'os.environ.get('RDS_USERNAME'),
        'PASSWORD': os.environ.get('RDS_PASSWORD'),
        'HOST': os.environ.get('RDS_HOSTNAME'),
        'PORT': os.environ.get('RDS_PORT'),
    }
}

# 접속 허용 호스트 설정
ALLOWED_HOSTS = ['{EC2 인스턴스의 IP 주소}']

 

6-2. 데이터베이스 마이그레이션

이제 Django 프로젝트 폴더로 찾아가서 다음 명령어를 수행한다. Django의 여러 (앱의) 기능들을 현재 연결된 데이터베이스에 적용시켜준다.

 

python manage.py migrate

 

6-3. Django 개발 서버 실행 (테스트)

지금까지 한 설정을 바탕으로 Django (개발용) 서버를 실행시켜보자. (참고로 이는 개발에만 사용하도록 Django가 기본 제공하는 간단한 버전의 WSGI 서버이다. 실제로 배포할 때는 Gunicorn 등의 WSGI 서버를 사용하게 된다.) 그런데 이렇게 Django 서버를 테스트로 실행해보기 전에 먼저 설정해줘야 할 것이 있다. 바로 EC2 인스턴스의 보안 그룹을 설정해주는 것이다. AWS 콘솔로 들어가서 EC2 인스턴스의 보안 그룹을 찾고, 인바운드 규칙을 추가하여 8000번 포트에서의 접근을 허용해주도록 하자. 다음과 같이 말이다.

 


이제 Django 서버를 다음과 같이 실행해보자. 0.0.0.0:8000은 자기 자신 할당된 어떠한 IP 주소를 이용하여 접근을 하든 8000번 포트로 서버를 개방해주겠다는 의미이다.

 

python manage.py runserver 0.0.0.0:8000

 

성공적으로 실행이 되었다면 브라우저를 켜서 EC2 인스턴스의 IP 주소로 8000번 포트에 접속해보자. 물론 유효한 URL이어야 한다. 여기서는 에러가 발생하지 않는지 정도만 확인하자. 이때, Django의 Static 파일 관련 설정을 해주지 않았기에 CSS가 깨져 나오는 건 당연하니 이건 신경 안 써도 된다.

 

7. Gunicorn 설치 및 테스트 실행

이제 웹 서버(Nginx)와 Django 어플리케이션 사이에서 중계 역할을 수행하는 WSGI 서버를 설치해볼 것이다. 다음과 같이 pip 명령어를 이용하여 gunicorn 패키지를 설치해주자. 물론이지만 Python 가상 환경에 진입되어 있는 상태여야 한다.

 

pip install gunicorn

 

이제 Django 서버가 아닌 Gunicorn을 실행시켜보자. 다음과 같이 실행할 수 있다. {wsgi.py 파일의 상대 경로}:application 부분의 경우, Django의 wsgi.py 파일이 현재 경로 기준으로 config/wsgi.py 경로에 위치한다면 config.wsgi:application이라고 적으면 된다.

 

gunicorn --bind 0.0.0.0:8000 {wsgi.py 파일의 상대 경로}:application

 

이제 브라우저를 켜서 EC2 인스턴스의 IP 주소로 8000번 포트에 접속해보자. 접속에 성공한다면 Gunicorn이 정상적으로 동작하는 것이다. 테스트에 성공하고 나면 아까 생성했던 보안 그룹은 다시 삭제해주자. 우리는 하나의 EC2 인스턴스에 설치된 웹 서버(Nginx)가 프록시 서버의 역할을 수행하도록 하여 백 엔드 서버에 접근하게끔 할 것이기 때문이다. 즉, 외부에서 들어오는 80번 포트의 요청을 Nginx가 내부에서 돌아가는 Gunicorn에게 전달하도록 설정할 것이다. 이는 뒤에서 알아보자.

 

8. Gunicorn 서비스 등록 및 실행

Gunicorn이 정상적으로 실행되는 것을 확인했다면, 이제 이를 시스템에 서비스로 등록해보도록 하자. 이는 Gunicorn의 실행, 중지, 혹은 자동 실행 설정 등을 쉽게 하기 위함이다. 이를 위해서는 해당 서비스의 등록을 위한 스크립트를 작성해줘야 한다. /etc/systemd/system 경로에 다음과 같이 스크립트를 생성하자. AAA, BBB, CCC 부분에는 원하는 파일 이름을 적으면 된다.

 

sudo vi /etc/systemd/system/AAA.service

 

[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=ec2-user
Group=ec2-user
WorkingDirectory={프로젝트 폴더 절대 경로}
EnvironmentFile=/home/ec2-user/env/BBB.env
ExecStart=/home/ec2-user/.pyenv/versions/{프로젝트 가상 환경 이름}/bin/gunicorn \
--workers 2 \
--bind=unix:/home/ec2-user/CCC.sock \
{wsgi.py 파일의 상대 경로}:application

[Install]
WantedBy=multi-user.target

 

BBB.env 파일은 무엇일까? 우린 이러한 파일을 생성한 적 없다. 이 파일은 바로 이어서 우리가 직접 생성해줄 것이다. 이 파일의 역할은 서비스로 등록되어 실행될 Gunicorn이 읽어야 할 환경 변수들을 담고 있는 것이다. 그런데 이상하지 않은가? 환경 변수는 이미 앞서 설정하지 않았는가? 사실 필자도 아직 이해하지 못한 부분인데, 서비스로 등록되어 실행되는 Gunicorn은 이미 설정되어 있는 환경 변수들을 읽지 못한다. 따라서 직접 우리가 명시해주고 읽게 만들어야 한다. (왜 그런지 아는 사람은 댓글로 알려주시면 감사하겠습니다.) 그러면 BBB 파일도 이어서 생성해보자. 경로는 /home/ec2-user/env로 정했다. 위에서 설정한 환경 변수들을 모두 적어주기 바란다.

 

sudo mkdir /home/ec2-user/env

 

sudo vi /home/ec2-user/env/BBB.env

 

DJANGO_SETTINGS_MODULE=config.settings.production
RDS_DB_NAME={데이터베이스 이름}
RDS_USERNAME={루트 유저 이름}
RDS_PASSWORD={루트 유저 패스워드}
RDS_HOSTNAME={데이터베이스 서버 호스트명}
RDS_PORT={데이터베이스 서버 포트 번호}

※ Django Secret Key도 환경 변수로 관리했다면 당연히 여기에도 포함시켜줘야 한다. 안 그러면 Gunicorn이 실행되지 않는다.

 

다음으로, CCC.sock 파일은 무엇을 의미할까? 이는 Gunicorn을 실행하면서 생성할 소켓 파일의 이름이다. 나중에 Nginx까지 실행하면 Nginx가 80번 포트로 들어오는 API 요청들을 Gunicorn에게 전달해주기 위해 이 소켓 파일을 참조하게 될 것이다. 이와 관련된 Nginx 설정은 아래 설명(9번)을 참고하자.

 

이제 위에서 작성한 파일들을 토대로 Gunicorn을 실행하자. enable 명령어는 해당 서비스가 부팅 시에 자동으로 실행되도록 설정하는 것이고, start 명령어는 지금 즉시 실행하는 것이다. 그리고 나면 status 명령어로 Gunicorn이 정상 실행되는지도 확인해주자.

 

sudo systemctl daemon-reload

 

sudo systemctl enable AAA

 

sudo systemctl start AAA

 

sudo systemctl status AAA

 

9. Nginx 프록시 설정 (Gunicorn으로의 리다이렉트)

이제 Nginx가 'api/'로 시작하는 URL로 들어오는 요청을 Gunicorn에게 전달하도록 설정해보자. Nginx 설정 파일에 다음과 같이 location 블록 하나를 추가 작성해주면 된다. proxy_pass 부분은 아까 Gunicorn을 실행시키면서 생성한 소켓 파일의 이름을 지정해준다. 필자의 경우, Nginx 설정 파일의 경로가 '/etc/nginx/sites-available/'이므로 "sudo vi /etc/nginx/sites-available/파일명.conf" 명령어를 입력하여 설정 파일을 열고 내용을 수정하였다. 각자 Nginx 설정 파일의 위치는 미리 파악해두도록 하자.

 

location /api/ {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_pass http://unix:/home/ec2-user/CCC.sock;
}

 

※ 참고로 개발 모드에서는 package.json 파일에 proxy 속성을 지정해줌으로써 Webpack 개발 서버가 프록시 서버의 역할을 수행하도록 할 수 있다. 이와 관련해서는 Create React App의 공식 문서에 있는 이 설명을 참고하자.

 

이제 Nginx도 실행해보자. 이때 Nginx도 부팅 시에 자동 실행되도록 enable 명령어까지 실행해주자. 그리고 나면 Gunicorn 때와 마찬가지로 status 명령어로 Nginx가 정상적으로 실행되는지까지 확인 한 번 해주자.

 

sudo systemctl enable nginx

 

sudo systemctl start nginx

 

sudo systemctl status nginx