Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jangjunha/6c79a85dff6b209805281f034d943a9a to your computer and use it in GitHub Desktop.
Save jangjunha/6c79a85dff6b209805281f034d943a9a to your computer and use it in GitHub Desktop.
Flask Deployment: Ubuntu server 16.04.1 LTS + Nginx + Uwsgi 환경에서 Flask 애플리케이션 배포하기

Flask 애플리케이션 배포하기

개요

Deployment를 수업 때 했는데 따라 치는 내용만 많아 이해하기 어려웠을 것 같아요. 경험상 배포하다 이것저것 다양한 에러를 많이 겪어봐서 따로 문서를 만들어 정리해봤습니다.

수업 시간에 VirtualBox 설정을 잘못해서 접속을 실패했었는데 마지막 부록에 설정 방법을 적어놨으니 참고하시면 됩니다. DB 설정 부분을 빠뜨렸었는데 그 부분도 확인 바랍니다.

그간의 삽질을 바탕으로 배포하는 방법을 전체적으로 정리했으니 도움이 되길 바랍니다. :)

목차

[TOC]

사용 환경

환경과 스택은 순전히 제 맘입니다.

  • Ubuntu Server 16.04 LTS
  • Nginx
  • uwsgi (2.x)

배포하기

Flask 설정 확인하기

앱을 배포하기 전에는 설정을 다시 한 번 점검할 필요가 있습니다. SECRET_KEY가 GitHub와 같은 public repository에 올라가 공개되거나 너무 간단해 공격자가 유추해낸다면 공격자가 세션을 읽거나 조작할 수 있고 Flask의 debug 모드가 켜져있으면 소스 코드가 노출되거나 (지금은 PIN code를 입력 절차가 추가됐지만) python shell에 접근할 수 있습니다. SQLALCHEMY_ECHO 옵션이 켜져있으면 로그파일 용량이 너무 커지겠죠...?
수업시간에 그대로 따라했다면 SECRET_KEY가 누구나 볼 수 있는 저장소인 GitHub에 올라가서 세션 값이 조작될 위험이 있지만 공개 배포하지 않을거라 상관 없다면 이 파트(설정 확인)는 건너뛰셔도 무방합니다.

app.config['KEY'] = 'value'와 같이 설정했던 config를 다른 파일로 관리해보겠습니다. 앱 기본 설정 값과 배포 환경(Local test, Server test, Release)별로 설정 파일을 나누는 등으로 관리할 수도 있습니다 - 만은 귀찮으니까 하나만 만들겠습니다. SECRET_KEY는 python shell에서 os.urandom(24)을 입력해 출력되는 값을 복사해 붙여넣어봅시다.1 데이터베이스 경로가 바뀌었는데 이유는 나중에 설명하겠습니다(DB 생성 · 설정하기 참조).  

satcounter_config.py

SECRET_KEY = '쉽게 유추할 수 없는 키. 유출되면 안됩니다.'
SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/test.db'

그리고 위의 파일을 config로 설정하도록 애플리케이션에 다음 코드를 추가합니다.

application.py (Flask app 소스파일)

app.config.from_object('satcounter_config')

원격 저장소에 소스 코드 올리기

서버에서 만든 Flask 애플리케이션을 돌리려면 소스 코드를 서버에 옮겨야합니다. 대부분의 경우 소스코드 버전 관리 시스템을 사용하므로 가장 많이 사용되는 git을 사용해서 원격 저장소에서 서버로 소스 코드를 다운(clone)받겠습니다.
그 전에 지금까지 작성한 소스 코드를 git을 사용해서 원격 저장소(GitHub)에 올리는 방법을 간단하게 설명하겠습니다. git에 대한 설명은 최소한으로만 할 테니 git을 공부하실 분은 다른 문서를 보면서 공부해주세요. 간단한 git 사용법은 https://rogerdudler.github.io/git-guide/index.ko.html 이 문서를 추천드립니다.

.gitignore 파일 만들기

git으로 버전 관리를 하지 않을 파일들을 설정합니다. 컴파일 된 파이썬 파일(*.pyc), Virtualenv 디렉토리, OS 임시 파일, SQLite DB 등을 제외하도록 설정합니다. https://www.gitignore.io 같은 사이트를 사용하면 편리하게 생성할 수 있습니다.

예시(Python, Virtualenv, Windows)

위 링크(gitignore.io)에서 생성한 리스트를 .gitignore 파일에 추가하고, SQLite DB와 SECRET_KEY가 포함된 설정파일도 제외하도록 다음을 추가합니다.

*.db
satcounter_config.py

git 클라이언트 설치하기

https://git-scm.com/download/win에서 git 클라이언트를 다운받아 설치합니다. GitHub Desktop이나 SourceTree와 같은 GUI 툴을 사용해도 무방하나 설명은 커맨드로 하겠습니다.

git (로컬) 저장소 만들기

프로젝트 폴더에서 다음 명령을 실행합니다.

git init

.git폴더가 만들어졌을겁니다. 윈도우 탐색기 기본 설정에서는 보이지 않을 수 있습니다.

git commit 만들기

git status 명령으로 현재 상태를 확인할 수 있습니다. 아직 commit을 한 적이 없다면 Untracked files만 확인할 수 있을겁니다. 여기서 버전을 관리할(원격 저장소에 올릴) 파일과 디렉토리를 git add 명령으로 stage 영역에 추가합니다. 변경 사항을 모두 커밋하고 싶다면(제외할 파일이 없다면) 다음과 같이 git add --all 명령으로 모두 추가할 수 있습니다.

git add --all

stage 영역에 변경한 파일들을 추가했으면 확정본(commit)을 만들어야합니다. 다음 명령으로 commit을 만듭니다. 버전 관리는 기본적으로 commit 단위로 하게됩니다. (이전의 특정 commit으로 소스 코드를 돌릴 수 있습니다.) -m 옵션에는 commit 메시지를 적는데 변경 내용을 간략하게 적어봅시다.

git commit -m "커밋 메시지"

원격 저장소 만들고 로컬 저장소에 설정하기

GitHub에 로그인 하고 New Repository 버튼을 눌러 원격 저장소를 만듭니다. Public 옵션으로 만들면 다른 모든 사람들이 GitHub에서 소스 코드를 볼 수 있습니다. Private 옵션은 유료입니다. Bitbucket, GitLab과 같은 서비스도 있습니다. 여기서는 GitHub 기준으로 설명합니다.

원격 저장소를 만들었으면 현재 프로젝트에 원격 저장소를 추가해야합니다. 다음 명령으로 GitHub에서 만든 원격 저장소를 origin이라는 이름으로 추가할 수 있습니다. 주소는 방금 생성된 repository 페이지에 Quick setup 박스에서 확인할 수 있습니다.

git remote add origin https://github.com/<username>/<reponame>.git

commit을 원격 저장소에 올리기

git push origin master

GitHub repository 페이지를 새로고침하면 올라간 소스 코드를 확인할 수 있습니다!

서버에 패키지 설치하기

서버에 필요한 패키지들을 설치합니다.

sudo apt-get install nginx uwsgi python-pip virtualenv uwsgi-plugin-python3 git
패키지 설명
nginx HTTP 서버. Apache(httpd)와 같은 다른 HTTP 서버를 사용해도 무방하나 이 문서에서는 nginx를 사용해 설정하는 법을 설명함.
uwsgi2 만든 Flask 애플리케이션을 구동할 애플리케이션 서버. 개발 단계에서는 Flask에 내장된 build-in 서버를 사용했지만 배포 할 때는 uwsgi와 같은 애플리케이션 서버가 필요.
python-pip Python 패키지 매니저. Flask 등 Python 패키지를 설치할 때 사용.
virtualenv 가상의 독립된 Python 환경을 구성해주는 툴.
uwsgi-plugin-python3 uwsgi에서 python3 애플리케이션을 돌리기 위한 플러그인
git 소스코드 버전 관리 시스템. git과 원격 저장소인 Github을 사용해서 앱을 배포 할 서버에 소스를 받을 예정.

원격 저장소에서 코드 내려받기

서버에 소스 코드를 내려받아야합니다. 저는 보통 그냥 홈디렉토리에 받습니다. 원격 저장소 주소는 아까 그... 주소입니다. repository 페이지의 Clone or download 버튼을 누르면 주소를 확인하실 수 있습니다. 만약 Private repository를 사용중이라면 로그인을 해야할겁니다.

git clone <원격 저장소 주소>

repository 이름으로 프로젝트 디렉토리가 만들어졌을겁니다.

Virtualenv 만들기

virtualenv을 이용해 가상환경을 만들어 사용하겠습니다. 저희 프로젝트는 python 3을 사용하는데 시스템 기본이 python 2.x이므로 python3을 사용하도록 명시해주겠습니다.

virtualenv venv -p python3

activate는 다음과 같이 합니다. activate 되면 프롬프트 앞에 (venv)와 같이 virtualenv의 이름이 뜹니다.

$ source venv/bin/activate
(venv)$

deactivate는 다음과 같이 합니다.

(venv)$ deactivate
$

(Virtualenv에) 파이썬 패키지 설치하기

프로젝트에 사용하는 파이썬 패키지들을 설치합니다. virtualenv를 사용한다면 activate 후에 설치합니다.

(venv)$ pip install Flask Flask-SQLAlchemy

패키지 관리 팁

파이썬 프로젝트들은 보통 requirements.txt에 사용하는 패키지명들을 기록해둡니다. requirements.txt에 의존 패키지들을 기록해두면 프로젝트 소스코드를 새로 받았거나 의존 패키지 버전에 변화가 있을 때 다음 명령어로 간단하게 설치할 수 있습니다.

(venv)$ pip install -U -r requirements.txt

현재 설치된 패키지들을 모두 requirements.txt에 기록하려면 다음 명령을 사용하면 됩니다.

(venv)$ pip freeze > requirements.txt

DB 생성 · 설정하기

일반적으로는 DB서버가 별도로 존재하거나 서비스가 돌고 있어서 이런 작업이 필요하지 않지만 저희 프로젝트에서는 파일 기반 데이터베이스를 이용하므로 애플리케이션 프로세스에서 DB를 쓸 권한이 있어야합니다. 그냥 프로젝트 폴더에 두면 권한 설정이 복잡해 귀찮으니까 아예 /tmp 디렉토리에 DB를 만들겠습니다.

DB 경로 변경하기

이 문서를 그대로 따라오셨다면 이미 수정돼있을겁니다. satcounter_config.py (아래 부분 수정)

SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/test.db'

DB 생성하기

DB 파일을 만들고 테이블을 생성합니다. python shell에서 다음과 같이 입력합니다. virtualenv를 사용한다면 activate 후에 python shell을 실행하도록 합시다.

>>> from application import db
>>> db.create_all()

DB 파일 권한 설정

sudo chgrp www-data /tmp/test.db
sudo chmod 660 /tmp/test.db

nginx 설정하기

설정 파일 작성

/etc/nginx/sites-available/default

server {
	listen 80 default_server;

	server_name _;

	location / {
		include uwsgi_params;
		uwsgi_pass unix:/tmp/satcounter.sock;
	}

}
  • Ln5: 별도로 수정하지 않았습니다. server_name을 _로 설정하면 모든 도메인을 받습니다.
  • Ln7: uwsgi를 설정하는 데 필요한 parameters가 적혀 있는 /etc/nginx/uwsgi_params 파일을 include합니다.
  • Ln8: Uwsgi랑 어떻게 통신하는지 설정하는 부분 같습니다. 127.0.0.1:5678 같이 주소를 적을 수도 있는 것 같은데 저는 socket 파일을 생성해 통신합니다. unix:/tmp/satcounter.sock 로 설정합니다. 이후 uwsgi 설정에 등장합니다. uwsgi 프로세스에 의해 자동으로 생성·삭제되니 다른 디렉토리로 설정한다면 파일 생성 권한이 있는 디렉토리인지 확인해야합니다. 만약에 한 서버 컴퓨터에서 여러 서버 애플리케이션를 돌린다면 sites-available 디렉토리에 애플리케이션별로 설정 파일을 만들고 sites-enabled 디렉토리에 심볼릭링크를 만들어 관리하면 됩니다.

변경한 설정 적용하기

nginx 서버를 재시작하고 변경한 설정을 적용합니다.

sudo systemctl restart nginx

# 또는
sudo service nginx restart

서버 상태 확인하기

재시작 시 아무 말이 안나오고 다음 명령어로 상태를 확인했을 때 정상이면 성공입니다. 실패했다면 힘내세요. /var/log/nginx/error.log의 에러 메시지가 도움이 될 수 있습니다.

sudo systemctl status nginx

# 또는
sudo service nginx status

uwsgi 설정하기

설정 파일 작성하기

/etc/uwsgi/apps-available/satcounter.ini (새로 생성)

[uwsgi]
vhost = true
plugins = python34
socket = /tmp/satcounter.sock
venv = /path/to/project/venv
chdir = /path/to/project
uid = www-data
module = application
callable = app
  • vhost: Virtualhost 설정입니다.
  • plugins: uwsgi 서버에서 사용할 플러그인 리스트입니다. python34 플러그인을 사용합니다. (설치: apt-get install uwsgi-plugin-python3)
  • socket: nginx 설정의 uwsgi_pass 에서 설정한 소켓 경로와 같아야합니다. nginx에서 이 소켓을 통해 uwsgi와 통신합니다.
  • venv: virtualenv 경로입니다. 수업대로 따라하셨다면 프로젝트 경로 아래의 venv 디렉토리일겁니다.
  • chdir: 실행 경로를 프로젝트 디렉토리로 수정합니다. 프로젝트 디렉토리에서 pwd 명령을 치면 확인하실 수 있습니다.
  • uid: uwsgi 프로세스 실행 유저 설정입니다. www-data 계정으로 돌리겠습니다. (후에 ps -ef | grep uwsgi 명령으로 확인해보세요!)
  • module: 작성한 소스파일의 파일명을 확장자 없이 쓰시면 됩니다. Flask app 객체가 있는 파일.
  • callable: Flask(__name__)으로 만든 플라스크 앱을 가지고 있는 변수명을 쓰시면 됩니다. 거의 app 으로 쓰고계실겁니다.

활성화하기

일반적으로 apps-available 디렉토리에 애플리케이션 설정을 작성하고 apps-enabled 디렉토리에 심볼릭링크를 걸어 설정 파일을 관리합니다. uwsgi는 apps-enabled 디렉토리에 있는 설정 파일만 읽습니다. 다음 명령어를 실행해 심볼릭링크를 걸어봅시다.

sudo ln -s /etc/uwsgi/apps-available/satcounter.ini /etc/uwsgi/apps-enabled/satcounter.ini

변경한 설정 적용하기

sudo systemctl restart uwsgi

# 또는
sudo service uwsgi restart

uwsgi 상태 확인하기

재시작 했을 때 에러가 안뜨고 다음 명령어로 상태를 확인했을 때 정상이면 성공입니다. 실패했을 경우 /var/log/uwsgi/app/<your-app>.log가 도움이 될겁니다.

sudo systemctl status uwsgi

# 또는
sudo service uwsgi status

방화벽에서 HTTP 허용하기

시스템 방화벽과 서버 컴퓨터 방화벽에서 HTTP 포트(80)를 허용해줘야합니다. 위에서 말한 환경에서는 기본적으로 inactive 상태이므로 따로 설명하지 않겠습니다. 리눅스의 방화벽 설정이 궁금하시다면 ufw, iptables를 공부하시면 됩니다.

접속하기

드디어 모든 설정이 끝났습니다! 접속해봅시다! ip주소는 ifconfig 명령으로 확인하실 수 있습니다.

부록

  • 접속했는데 아무 반응이 없습니다: 서버 컴퓨터까지 접근이 안될 가능성이 있습니다. 방화벽 설정을 잘 확인해보세요. 가상머신이라면 네트워크 어댑터 설정이 잘못됐을 수 있습니다.
  • 502 Bad Gateway: uwsgi 설정이 잘못됐을 가능성이 높습니다. /var/log/uwsgi/app/<your-app>.log 파일을 확인해보세요. nginx 설정이 잘못됐을 가능성도 적지 않습니다. nginx 로그는 /var/log/nginx 디렉토리에 있습니다.
  • 500 Internal Server Error: 여러분의 소스코드에 버그가 있을 가능성이 높습니다. 로컬에서는 문제가 없고 서버에서만 문제가 발생한다면 DB접근 권한이 없을 가능성도 있습니다.

VirtualBox에서 네트워크 설정하기

수업 중에 VirtualBox 네트워크 설정을 잘못해서 서버에 접근할 수 없었습니다... Host-only 어댑터를 하나 만들어 서버를 만들 때 만든 어댑터를 추가하시면 쉽고 이미 만드셨다면 아래를 따라하시면 됩니다.

Host-only 어댑터 새로 만들기

VirtualBox 설정 > Network 탭 > Host-only Networks 탭에서 +버튼을 눌러 새 Host-only 어댑터를 만듭니다. 이미 존재한다면 그걸 써도 좋습니다. 이 때 설정된 IPv4 Address와 서브넷마스크를 기억해둡시다.

가상머신에 Host-only 어댑터 추가하기

  1. 가상머신을 종료합니다.
  2. **가상머신 설정 > Network > Adapter 2 (빈 Adapter)**에서 Enable Network Adapter를 체크합니다.
  3. Attached toHost-only Adapter로 설정합니다.
  4. Name을 위에서 만든 어댑터로 설정합니다.
  5. 가상머신을 부팅합니다.

가상머신에서 어댑터 설정하기

다음 명령어로 어댑터 리스트와 새로 추가된 어댑터를 확인합니다.

ls /sys/class/net

새로 추가된 어댑터를 설정합니다. /etc/network/interfaces (예시)

# Host-only interface
auto eth1
iface eth1 inet static
	address		192.168.56.20
	netmask		255.255.255.0
	network		192.168.56.0
	broadcast 	192.168.56.255
  • Ln 3: eth1 자리에 새로 추가된 어댑터(인터페이스) 이름을 씁니다.
  • Ln 4 address: 다른 호스트와 겹치지 않도록 이 호스트의 IP를 설정합니다. 나중에 이 IP로 서버에 접근합니다.
  • netmask, network, broadcast는 따로 설명하지 않아도 될 것 같습니다.

고쳐주세요!

잘못됐거나, 보안상 문제가 있거나, 개선하면 좋겠다는 부분이 있다면 jangjunha113@gmail.com으로 연락 부탁드립니다!

Footnotes

  1. Flask 공식 문서: How to generate good secret keys - http://flask.pocoo.org/docs/0.12/quickstart/

  2. Flask 공식 문서: uWSGI에 배포하기 - http://flask.pocoo.org/docs/0.12/deploying/uwsgi/

@tmd9936
Copy link

tmd9936 commented Nov 11, 2020

안녕하세요!
virtualenv venv -p python3을 할 경우
아래와 같은 에러가 나오는데 해결책은 없을까요>
module 'os' has no attribute 'PathLike'

서버 환경은 Ubuntu Server 16.04 LTS입니다.

@jangjunha
Copy link
Author

안녕하세요!
virtualenv venv -p python3을 할 경우
아래와 같은 에러가 나오는데 해결책은 없을까요>
module 'os' has no attribute 'PathLike'

서버 환경은 Ubuntu Server 16.04 LTS입니다.

오류가 나는 정확한 원인은 모르겠지만 Python 3의 경우 파이썬의 virtualenv 모듈을 사용해보시면 좋을 같습니다.

$ python3 -m venv <가상환경폴더명>

# 예시
$ python3 -m venv venv

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment