최근에 웹 애플리케이션이 하나를 개발했다.
Django를 통해 개발했으며 EC2로 배포를 할 예정이다.
처음 EC2로 배포를 할 때 구글링과 블로그등을 통해 정보를 수집해서 진행했었는데
배포 처음부터 끝까지의 원싸이클의 과정이 담긴 게시물이 많지 않아서 정보 수집이 쉽지 않았다.
그래서 나와 같은 어려움을 겪는 사람들을 위해 EC2 생성부터 배포 그리고 도메인과 https 적용까지
총 2개의 게시물로 나눠서 정리해둘 예정이다.
참고로 글을 작성하기에 앞서 혼동을 방지하기 위해 프로젝트 구조를 미리 명시하겠다.
django_server # 루트 디렉토리
├── config # 프로젝트 디렉토리
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── user # app
├── msk # app
├── share # app
├── static
├── templates
├── manage.py
├── db.sqlite3
└── requirements.txt
Django 프로젝트를 초기에 생성하면 루트 디렉토리와 프로젝트 디렉토리 명이 같다.
본인은 이게 혼동이 되어서 보통 프로젝트 생성 후 프로젝트 디렉토리 명을 config로 바꾸어준다.
글을 읽으면서 이 부분이 혼동 될 것 같아서 미리 명시한다.
앞으로 프로젝트 최상단 디렉토리를 루트 디렉토리라 부르고,
settings.py가 위치한 config 디렉토리를 프로젝트 디렉토리 라고 부르겠다.
1. 배포를 위한 Django 설정
우리가 개발하는 로컬서버와 EC2로 배포하는 배포서버는 다른 환경으로 운영되어야한다.
이를 위해 배포를 하기전 Django에서 해줘야 하는 사전 작업들이 있다.
- 패키지 의존성 관리를 위해 requirements.txt 생성
- settings.py 분리
- env 환경변수 파일 생성
1) requirements.txt 생성
python 가상환경은 독립적으로 운영된다. 그렇기 때문에 개발서버와 배포서버의 환경 또한 다르다.
이 두 환경의 패키지를 일관성 있게 관리하기 위해 requirements.txt를 생성한다.
파일은 루트 디렉토리 내에 위치시키면 된다. (manage.py와 같은 위치)
# requirements.txt 생성
pip freeze > requirements.txt
requirements.txt에는 현재 가상환경에 설치된 패키지와 버전 정보가 명시되어있다.
추후에 배포서버에 Django 프로젝트를 clone 해준 후
requirements.txt에 명시된 패키지를 그대로 install 할 것이다.
참고로 requirements.txt를 통해 패키지를 install 하는 명령어는 다음과 같다.
# requirements.txt에 명시된 패키지 install
pip install -r requirements.txt
2) settings.py 분리
배포를 위한 Django 설정 중 가장 중요한 부분이라고 할 수 있다.
DEBUG 설정 ALLOWED_HOSTS 그리고 STATIC 관련 설정 등등은
환경에 따라 모두 다르게 설정해주어야 하는데
하나의 파일로써 관리하기에는 한계가 있다. (가능하긴함)
따라서 settings.py를 분리해서 운영해보겠다.
1) 프로젝트 디렉토리 내 settings 라는 이름으로 디렉토리 생성
2) 기존 settings.py를 settings 디렉토리로 이동시킨 후 base.py로 이름 변경
3) settings 디렉토리에 local.py, prod.py 파일 생성
위 3가지 과정을 거친 후의 디렉토리 구조는 아래와 같다.
# settings 분리 후 프로젝트 구조
django_server
├── config
│ ├── asgi.py
│ ├── settings # 디렉토리 생성
│ │ ├── base.py # 기존 settings.py 그대로 => 파일명만 변경
│ │ ├── local.py # 파일 생성
│ │ └── prod.py # 파일 생성
│ ├── urls.py
│ └── wsgi.py
...
생략
파일 생성 후 우선 base.py에서 변경해 줄 설정이 있다.
settings 디렉토리 생성으로 인해 depth가 한단계 깊어졌으므로
django에서 루트 디렉토리의 위치를 다시 명시 시켜줄 필요가 있다.
# base.py
# settings 디렉토리 생성으로 인해 depth가 한단계 깊어졌으므로 .parent 한번 더 추가
BASE_DIR = Path(__file__).resolve().parent.parent.parent
다음은 local.py와 prod.py에 작성할 코드이다.
두 파일은 base.py를 상속받아 사용할 것이기 때문에 기본 설정은 그대로 유지하고
각 환경에 따라 다르게 적용해야할 설정에 대해서만 명시하면 된다.
# local.py
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost']
STATIC_URL = 'static/'
STATICFILES_DIRS = [BASE_DIR / 'static',]
# prod.py
from .base import *
# 원래는 DEBUG = False가 맞으나 이때 EC2 초반에 테스트 목적으로 True로 설정
# DEBUG = False
DEBUG = True
ALLOWED_HOSTS = [
# 나중에 구매한 도메인, EC2의 퍼블릭 ip등을 명시
]
STATIC_URL = 'static/'
# STATIC_ROOT를 써야 하지만 마찬가지로 테스트를 위해 STATICFILES_DIRS 사용
# STATIC_ROOT = BASE_DIR / 'static'
STATICFILES_DIRS = [BASE_DIR / 'static',]
prod.py는 DEBUG를 False로 하는 것이 맞으나 이때 EC2를 처음 열고
Django가 잘 실행되는지 테스트 해보기 위해 우선 True로 두겠다. STATIC_ROOT 또한 마찬가지다.
(어차피 ALLOWED_HOSTS 때문에 또 이따 수정해야함)
참고로 여기서 중요하게 알아두어야 할 점은
STATICFILES_DIRS과 STATIC_ROOT의 차이점이다.
STATICFILES_DIRS는 개발 환경에서 static 파일이 저장된 폴더들을 지정하는 설정이다.
개발 환경(DEBUG = True)시에는 django.contrib.staticfiles 앱이 자동으로 STATICFILES_DIRS에 지정된 디렉토리에서 정적 파일을 찾고 서빙해준다.
반면 배포시에는 Django가 static 파일을 직접 서빙하지 않고, Nginx와 같은 웹 서버가 이를 처리해야한다.
Django는 STATICFILES_DIRS 및 앱 내부의 static/ 폴더에서 모든 static 파일을 찾아서 STATIC_ROOT에 복사하고 Nginx는 이경로에서 static 파일을 서빙한다.
3) env 환경 변수 파일 생성
자 이제 settings를 분리했다. 분리만 하면 끝일까?
아니다. Django는 현재 서버 환경이 개발 환경인지 배포 환경인지 구분하지 못한다.
따라서 Django에게 어떤 settings로 서버를 실행할지 명시시켜줘야한다.
명시 시켜주는 방법은 여러가지가 있다.
runserver시에 명령어를 통해 명시 시키는 방법도 있고,
bash에서 export DJANGO_SETTINGS_MODULE로 환경 변수를 등록하는 방법도 있으나
매번 해줘야하는 과정은 귀찮다고 생각한다.
기왕 사용하는거 좀 더 Django를 고급지게 사용하기 위해 env 파일을 활용하겠다.
우선 루트 디렉토리 내에 .env 라는 이름으로 파일을 생성한다.
일단 로컬 서버이므로 local.py로 settings를 실행시키기 위해 .local을 명시한다.
# .env
DJANGO_SETTINGS_MODULE=config.settings.local
# 배포서버에서는 아래와 같이 작성
# DJANGO_SETTINGS_MODULE=config.settings.prod
그리고 env 파일에서 값을 코드로 불러오기 위해 이를 보조해주는 dotenv 라는 라이브러리를 설치하겠다.
dotenv는 환경 변수를 로드하는 데 사용되는 라이브러리이다.
( 설치 후 pip install -r requirements.txt 한번 더 해줍시다.... )
pip install python-dotenv
dotenv를 설치 후 manage.py를 수정해준다.
import os
import sys
from dotenv import load_dotenv # 추가
def main():
# .env 파일 로드
load_dotenv() # 추가
"""Run administrative tasks."""
# getenv 함수를 통해 환경변수 값을 가져오고 서버를 실행하도록 변경
os.environ.setdefault('DJANGO_SETTINGS_MODULE', os.getenv('DJANGO_SETTINGS_MODULE', 'config.settings'))
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
추가로 wsgi.py도 설정해준다.
wsgi.py는 배포 환경에서 웹서버가 Django 프로젝트를 로드하고 요청을 처리할 수 있도록 하는 파일이다.
runserver 시에는 사용되지 않지만 배포시 사용되므로 해당 파일에서 prod.py를 사용하도록 명시해둔다.
참고로 wsgi는 Web Server Gateway Interface의 약어이다.
알아두면 좋지 않은가? 알아두자.
# wsgi.py
import os
from django.core.wsgi import get_wsgi_application
# prod로 실행하도록 수정
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.prod')
application = get_wsgi_application()
드디어 배포를 위한 Django 설정이 끝이났다.
참고로 gitignore 파일 또한 만들어두어야 한다.
env 파일과 같은 보안이 중요한 파일은 push 되지 않도록 관리해야하기 때문이다.
아래 접속 후 django, os등을 입력하면 ignore 파일을 만들어주는데
안에 내용을 복사해서 .gitignore 파일을 생성하고 복사 붙여넣어준다.
참고로 .gitignore 파일을 루트 디렉토리 안이 아닌 루트 디렉토리와 같은 위치에 있어야 한다.
gitignore.io
Create useful .gitignore files for your project
www.toptal.com
자 이제 AWS를 통해 EC2를 생성하고 배포 서버에서 Django를 실행해보도록 하겠다.
2. AWS EC2 생성
무료 클라우드 컴퓨팅 서비스 - AWS 프리 티어
이러한 프리 티어 혜택은 AWS 신규 고객에게만 제공되며 AWS 가입일로부터 12개월 동안 유효합니다. 12개월의 무료 사용 기간이 만료되거나 애플리케이션 사용량이 프리 티어 범위를 초과할 경우
aws.amazon.com
우선 aws에 접속해서 계정을 생성한다.
참고로 새롭게 가입한 계정만 12개월간 프리티어로 이용할 수 있기 때문에 새로 가입한 계정을 사용하는 것이 좋다.
계정 생성 후 로그인을 하고 콘솔에 접속한다.
오른쪽 상단에 리전을 서울로 바꿔준 후 EC2를 클릭한다.
그럼 아래와 같은 화면이 나오는데 인스턴스 시작을 클릭한다.
이름을 입력하고 대부분의 서버는 운영체제로 ubuntu를 사용하기 때문에 24.04 LTS를 선택해주겠다.
아래로 내려보면 키 페어를 발급 받아야 한다.
키페어는 SSH를 통해 EC2 인스턴스에 원격접속을 할 때 사용 된다.
새 키 페어 생성 클릭
키 페어 이름을 입력하고 RSA를 선택하면 된다.
여기서 mac OS의 경우에는 .pem을 선택하고,
Window의 경우에는 .ppk를 선택한다.
전부 선택했다면 키 페어 생성을 클릭한다.
클릭 하면 키 페어가 다운로드 될 것이다. 잘 보관해두도록 한다.
좀 더 밑으로 내려가서 네트워크 관련 설정을 해주겠다.
HTTPS와 HTTP를 모두 사용 할 것이기 때문에 체크 한다.
이 후 스토리지 구성등 나머지는 기본값을 유지하고 인스턴스 시작을 클릭한다.
인스턴스가 정상적으로 생성되었다면 아래와 같이 실행 중 이라는 문구가 나온다.
인스턴스 ID를 클릭해 상세정보로 들어가본다.
상세정보로 들어오면 인스턴스 정보가 명시되는데
퍼블릭 IPv4 주소가 바로 우리 서버의 ip주소이다.
연결 버튼 클릭시 원격으로 EC2 터미널로 원격 접속을 할 수 있다.
우선 그전에 개방 주소법을 클릭한다.
아래와 같이 ERR_CONNECTION_REFUSED 라는 에러가 발생하면 정상적으로 인스턴스가 시작 된 것이다.
다시 인스턴스 상세 정보로 돌아와 연결 버튼을 클릭하겠다.
이후 인스턴스에 연결 화면에 나오면 아무것도 건들지 않고 다시 한번 연결을 클릭한다.
자 이제 이 화면이 뜨면 정상적으로 EC2에 원격으로 연결된 것이다.
우리는 이 화면을 통해 필요한 패키지 들을 설치하고 Django를 실행 할 것이다.
이제 인스턴스를 생성했으므로 본격적으로 EC2에 Django 서버를 clone 하고
nginx와 gunicorn을 설치해서 django를 실행시켜보도록 하겠다.
3. github 프로젝트 레포지토리 clone
우선 clone 하기에 앞서 필요한 패키지들을 설치하도록 하겠다.
연결된 ec2 터미널에 아래와 같이 입력해준다.
설치하면서 Y/N을 입력하는 화면이 나오면 전부 Y로 입력해주면 된다.
# ubuntu package를 업데이트
sudo apt update
sudo apt upgrade
# 업데이트 완료 후 python 관련 패키지 들을 설치
sudo apt install python3-pip python3-dev python3-venv
이제 레포지토리에서 clone을 진행할건데
2021년 이후로 레포지토리를 password 방식으로 clone을 하지 못하기 때문에 ssh를 발급해야한다.
매우 간단하므로 ssh를 발급받고 github에등록하자
ssh-keygen -t rsa -b 4096 # 입력 후 전부 엔터
ssh key가 정상적으로 발급되면 아래와 같이 신기한 그림이 나온다.
이제 ssh를 꺼내보도록 하겠다.
cat 명령어를 사용하면 파일 안에 있는 텍스트를 꺼내올 수 있다.
cat /home/ubuntu/.ssh/id_rsa.pub
아래와 같이 cat 명령어를 입력하고 나오는 ssh key를 전부 복사해놓자
이제 github에 발급받은 ssh를 등록해주겠다.
상단 프로필 settings >> SSH and GPG keys >> New SSH Key 클릭
title을 대충 입력하고 Key 부분에 아까 복사해둔 key 값을 붙여넣기 한 후 Add SSH KEY 클릭
이제 clone 하고자 하는 레포지토리로 이동해
초록색 Code >> SSH >> 주소 복사 아이콘 클릭
다시 ec2 터미널로 이동 한 후 clone을 진행 해준다.
git clone <복사한 레포지토리 주소>
이후 ls 명령어를 통해 제대로 clone이 되었는지 확인할 수 있다.
이제 cd 명령어를 통해 clone된 프로젝트 안으로 들어가보겠다.
이 후 가상환경을 생성하고 접속한뒤
이전에 requirements.txt로 정의해놓은 패키지를 가상환경에 설치하겠다.
터미널에서 아래와 같이 진행 하면 된다.
# 레포지토리가 clone 되었나 출력
ls
# 이동
cd <레포지토리 명>
# 가상환경 설치
python3 -m venv venv
# 가상환경 접속
source venv/bin/activate
# django 프로젝트로 이동
cd <프로젝트 명>
# 필요한 패키지 설치
pip install -r requirements.txt
이제 touch 명령어를 통해 .env 파일을 생성해준다.
.env 파일은 git을 통해 push가 되지 않는다. (중요 정보가 담겨 있으므로)
touch .env
env 파일이 생성되었다면 내용을 편집해야한다.
vim을 통해 파일을 편집해보겠다. vim은 터미널 기반에서 파일을 편집할 수 있도록 해주는 녀석이다.
참고로 vim 사용법은 아래와 같다.
왕초보를 위한 vim 사용방법
ㅎㅎ안녕하세요 :) 오늘은 vim에 대해서 알아볼거에요. 오늘 우리가 할 거는 1. vim이 무엇이냐?2. vim을 어떻게 쓸 것이냐? 입니다.시작해볼게요 :) 참고로 저도 못해서 ㅠㅠ 이 글을 쓰는이유는 알
zeddios.tistory.com
vim .env
접속하자마자 i를 누르면 하단에 INSERT 모드가 되었음을 확인할 수 있다.
DJANGO_SETTINGS_MODULE=config.settings.prod
위 내용을 복사 후 붙여넣기 해준다. (직접 입력해도 됌)
이후 ESC를 누르고 :wq를 누르고 엔터를 클릭한다.
이제 runserver를 통해 django를 실행시켜보겠다.
참고로 외부접속을 위해 0.0.0.0:8000으로 서버를 열어주어야 한다.
python manage.py runserver 0.0.0.0:8000
정상적으로 runserver가 실행된다면 8000번 port를 열어주어야 한다.
인스턴스 세부 정보로 이동 후 > 좌측 보안 그룹 클릭 > launch-wizard-1 체크 후 > 인바운드 규칙 편집 클릭
규칙 추가를 누른 후 아래와 같이 입력하고 규칙을 저장해준다.
자 이제 드디어 접속을 해보겠다. => http://{ec2 퍼블릭 ip}:8000
접속을 했을때 아래와 같은 화면이 출력되면 여기까지 정상적으로 문제 없이 온것이다.
익숙한 노란 화면이지 않은가?
Django가 정상적으로 실행되고 있다는 것이다.
해당 오류는 ALLOWED_HOST 오류이다.
배포 사전 설정 때 prod.py에 ALLOWED_HOST를 명시해주지 않았기 때문이다.
다시 로컬 환경의 prod.py로 이동해보겠다.
이제 ALLOWED_HOSTS에 EC2 인스턴스의 퍼블릭 IPv4 주소를 복사한 후 붙여넣기 해준다.
그리고 github 레포로 push를 시키자.
# prod.py
from .base import *
# 원래는 DEBUG = False가 맞으나 이때 EC2 초반에 테스트 목적으로 True로 설정
# DEBUG = False
DEBUG = True
ALLOWED_HOSTS = [
'ec2 public ip 복붙',
]
STATIC_URL = 'static/'
# STATIC_ROOT를 써야 하지만 마찬가지로 테스트를 위해 STATICFILES_DIRS 사용
# STATIC_ROOT = BASE_DIR / 'static'
STATICFILES_DIRS = [BASE_DIR / 'static',]
다시 EC2 터미널로 온 후 pull을 받아준다.
git pull
pull을 받은 후,
python manage.py runserver 0.0.0.0:8000
명령어를 입력하고 다시 접속해보면
정상적으로 페이지에 접속되는 것을 확인할 수 있을 것이다.
그렇다면 이렇게 runserver를 통해 배포를 하면 끝일까?
정답은 아니다.
runserver는 개발용으로는 우수하나 배포용으로는 문제가 많다.
runserver의 문제점은 아래와 같다.
- 싱글 스레드, 싱글 프로세스
- 보안 취약, 안정성 부족
따라서 해당 문제를 보완하기 위해
wsgi인 gunicorn과 웹서버인 nginx를 함께 활용해서 배포해준다.
gunicorn과 nginx의 설명을 하면 너무 오랜 시간이 걸리기 때문에 요약하여 설명하겠다.
a) Gunicorn
- Django 애플리케이션을 실행하는 WSGI 서버
- 멀티스레드
- 웹 서버와의 다리 역할
b) nginx
- 웹서버
- static 파일 서빙
- 로드밸런싱 (트래픽 관리)
따라서 EC2에 gunicorn과 nginx를 설치하고,
django를 실행 시키는 것으로 배포를 마무리 해보도록 하겠다.
gunicorn을 설치하기 전에 이제 정말 배포 환경에서 django를 실행하기 위해서 prod.py를 아래와 같이 한번 더 수정해 준다. 로컬 환경에서 수정 후 레포지토리로 push하고 아까와 같이 EC2에서 pull을 받도록 하자
# prod.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = [
'ec2 public ip 복붙',
]
STATIC_URL = 'static/'
STATIC_ROOT = BASE_DIR / 'static'
4. gunicorn 설치
gunicorn은 pip를 통해 설치된다. gunicorn을 설치해보겠다.
EC2 터미널로 이동 후 해당 명령어를 입력한다.
pip install gunicorn
설치를 완료 했다면 gunicorn 설정 파일을 열어 준다.
sudo vim /etc/systemd/system/gunicorn.service
아래와 같이 입력한다.
# gunicorn config
[Unit]
Description=gunicorn daemon for Django application
After=network.target
[Service]
User=ubuntu
Group=ubuntu
WorkingDirectory=/home/ubuntu/{레포명}/{루트 디렉토리명}
ExecStart=/home/ubuntu/{레포명}/venv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/ubuntu/{레포명}/{루트 디렉토리명}/{프로젝트 디렉토리명}.sock {프로젝트 디렉토리명}.wsgi:application
[Install]
WantedBy=multi-user.target
이후 시스템이 꺼졌다 켜지더라도 gunicorn이 자동으로 실행되도록 설정해준다.
sudo systemctl enable gunicorn
이제 gunicorn을 실행해보자
sudo systemctl start gunicorn
실행 상태를 체크해본다.
sudo systemctl status gunicorn
아래와 같이 초록색으로 active가 실행되면 문제 없이 실행되는 것이다.
5. nginx 설치
이제 거의 다 왔다. 마지막으로 nginx를 설치해주겠다.
sudo apt install nginx
nginx 또한 동일하게 설정 파일을 만들겠다.
sudo vim /etc/nginx/sites-available/{프로젝트 디렉토리명}
아래와 같이 작성해준다.
# nginx.config
server {
server_name {ec2 퍼블릭 ip};
location / {
include proxy_params;
proxy_pass http://unix:/home/ubuntu/{레포명}/{루트 디렉토리명}/{프로젝트 디렉토리명}.sock;
}
location /static {
root /home/ubuntu/{레포명}/{루트 디렉토리명}/;
}
location /media {
root /home/ubuntu/{레포명}/{루트 디렉토리명}/;
}
}
이후 nginx 웹 서버의 설정 파일을 활성화 시키겠다.
sudo ln -s /etc/nginx/sites-available/{프로젝트 디렉토리명} /etc/nginx/sites-enabled/
마찬가지로 nginx 또한 실행시켜주고 상태를 체크해보겠다.
sudo systemctl start nginx
sudo systemctl status nginx
아래와 같이 초록색으로 active가 실행되면 문제 없이 실행되는 것이다.
이제 권한설정만 해주면 된다. 아래 명령어들을 터미널에 입력하자
chmod 755 ~
chmod 755 ~/{레포명}
마지막으로 collectstatic 명령어를 실행시켜주도록 하겠다.
가상환경이 활성화 되어 있지 않다면 활성화 한 후에 manage.py가 있는 위치까지 cd로 이동해야 한다.
python manage.py collectstatic
자 이제 http://{퍼블릭 ip}를 통해 접속하면 nginx와 gunicorn을 통해 배포 환경에서 실행중인 Django가 이상 없이 실행되고 있는 것을 확인할 수 있을 것이다.
혹시나 만약에 똑같이 따라했음에도 불구하고 잘 되지 않는다면 nginx 에러 로그를 통해 원인을 파악할 수 있다. nginx 에러 로그를 확인하는 방법은 아래와 같다.
tail -f /var/log/nginx/error.log
이렇게 길고 긴 배포 과정이 어느정도 마무리 되었다.
그렇지만 아직 끝나지 않았다.
배포가 완료된 것은 맞지만, 서비스를 하기 위한 조건을 아직 갖추지는 않았다.
이제 남은 일들은 아래와 같다.
- 가비아를 통해 도메인 구매
- 구매한 도메인을 Route 53을 통해 연결
- https 적용
나머지 과정들은 글이 너무 길어지는 관계로 다음 게시물로 작성해서 업로드 하도록 하겠다.
'Deploy' 카테고리의 다른 글
[AWS] EC2를 통해 Django 배포하기 - 2 (가비아, nginx 도메인, https 적용, certbot) (2) | 2025.02.02 |
---|---|
[Nginx] nginx에서 certbot 통해 https 적용하기 (0) | 2025.01.31 |
[AWS] 프리티어 기간 요금 발생: Virtual Private Cloud 요금 (0) | 2025.01.31 |