[React+Flask] 플라스크Flask 서버에서 리액트React 배포하기
이전 포스팅에서 다루었던 리액트 프론트엔드와 플라스크 백엔드 구조를 리눅스 서버에서 배포하는 주제를 다루어보려한다. 기본적인 내용들은 Miguel Grinburg씨의 'How to Deploy a React + Flask Project(https://blog.miguelgrinberg.com/post/how-to-deploy-a-react--flask-project)'에서 가져 왔음을 먼저 밝혀둔다. 부족한 부분들은 필자의 탓이다.
이전 포스팅 '[React+Flask] 리액트React 프론트엔드 + 플라스크Flask 백엔드'에서는 하나의 컴퓨터 안에서, 즉 로컬(local)에서 파이참과 npm을 이용해서 각각 플라스크 서버와 리액트 컴포넌트를 실행시키고 이 둘을 연동했었다.
2021.10.16 - [웹 프로그래밍 고급 주제들] - [React+Flask] 리액트React 프론트엔드 + 플라스크Flask 백엔드
[React+Flask] 리액트React 프론트엔드 + 플라스크Flask 백엔드
리액트React 튜토리얼 등에서는 프론트엔드 프레임워크로 리액트를 사용하고 백엔드로 node.js를 사용한다. 하지만 백엔드 프레임워크로 이미 장고Django나 플라스크Flask를 사용하고 있다면 파이썬
youngbyte.tistory.com
이번 포스팅에서는 리눅스 원격지 서버에서 리액트로 만들어진 페이지를 어떻게 배포하는지 그 과정을 다루려한다. 이번 포스팅에서 사용하는 리액트 프로젝트와 플라스크 코드는 이전 포스팅의 것을 기반으로 할 것이다.
원격지 서버를 통해 서비스를 배포(release 또는 deploy)할 때는 npm을 이용해서 리액트 컴포넌트를 직접 실행할 수 없기 때문에 클라이언트의 페이지 요청에 대해 플라스크 서버가 페이지를 전달하는 과정이 추가된다.
리액트로 만들어진 페이지가 클라이언트의 웹 브라우저에서 실행되면 이후의 과정은 이전 포스팅의 그 것, 즉 프론트엔드와 백엔드가 HTTP API를 이용해서 요청과 응답을 주고받는 것과 동일하다.
위 두 과정을 생각하면서 글을 읽어나가면 도움이 될 것이다.
리액트 프로젝트는 개발 코드가 포함되어 있기 때문에 크기가 크고 배포에는 적합하지 않다. 먼저 리액트 프로젝트를 빌드(build)한다. 리액트 프로젝트 디렉토리에서 VS code의 터미널 등을 이용해서 npm run build 커맨드를 입력하면 프로젝트가 빌드되고 이전에는 없던 ./build 디렉토리가 생성된다.
※ 프로젝트를 빌드하기 전에 package.json 파일 안의 proxy 설정은 삭제한다.
FTP 등을 이용해서 서버의 플라스크 app 객체가 있는 위치에 이 build 파일을 업로드한다. 필자의 코드 구조를 기준으로 살펴보면 아래와 같다.
Flask_API_Server
├ appmain
│ ├ build
│ ├ __init__.py
│ └ routes.py
└ run.py
__init__.py 파일 안에 아래와 같이 플라스크 app 객체가 있고 이 app 객체가 있는 디렉토리 안에 build 디렉토리가 함께 위치한다. 사용하고 있는 플라스크 서버의 구조가 필자의 서버 구조와 다르더라도 플라스크의 app 객체를 기준으로 app 객체가 위치하는 디렉토리에 build 결과를 업로드하면 된다.
아래와 같이 app 객체를 설정한다. 필자는 __init__.py 파일 안에서 app 객체를 생성하고 설정하였다.
▶ 플라스크 서버 프로젝트 디렉토리/appmain/__init__.py
from flask import Flask
# app = Flask(__name__)
app = Flask(__name__, static_folder='build', static_url_path='/')
app.config["SECRET_KEY"] = 'd8835y3ek4238f994ea8241e356a68de'
from appmain.routes import main
app.register_blueprint(main)
빌드된 리액트 프로젝트가 플라스크 app 디렉토리의 build 디렉토리 안에 들어있다. 플라스크 앱이 클라이언트로부터 페이지 제공을 요청받으면 플라스크 서버가 리액트로 만들어진 페이지를 찾을 수 있도록 static 폴더의 위치를 static_folder 변수의 값을 통해 'build'로 지정하였다. 플라스크 서버는 리액트로 빌드된 index.html에 포함되어 있는 CSS, 자바스크립트 파일들을 플라스크 서버의 static 폴더를 기준으로 검색하기 때문이다.
그리고 서버의 static_url_path 변수를 '/'로 지정하여 플라스크 서버가 static/build 폴더에 위치하는 index.html에 /static/index.html과 같이 접근하지 않고 /index.html 주소로 접근이 가능하도록 설정하였다.
이제 리액트로 만들어진 페이지를 요청할 수 있도록 URL 엔드 포인트를 추가한다.
▶ 플라스크 서버 프로젝트 디렉토리/appmain/routes.py
from flask import Blueprint
import time
from appmain import app
main = Blueprint('main', __name__)
@main.route('/api/time')
def get_current_time():
return {'time': time.time()}
# 새로 추가된 URL 엔드 포인트
@main.route('/')
def index():
return app.send_static_file('index.html')
send_static_file() 함수 대신 send_from_directory() 함수를 사용할 수도 있다. 둘 중 어떤 함수를 사용해도 무방하다.
@main.route('/')
def index():
response = send_from_directory('build', 'index.html')
return response
클라이언트가 서비스 URL의 루트(root) 포인트(/)로 접근하면 플라스크 서버는 build 디렉토리 안의 index.html를 제공하도록 설정되어 있다.
로컬이 아닌 서버의 IP를 받아올 수 있도록 플라스크 앱 구동 커맨드도 아래와 같이 수정하였다.
▶ 플라스크 서버 프로젝트 디렉토리/run.py
from appmain import app
if __name__ == '__main__':
#app.run('127.0.0.1', 8000)
app.run('0.0.0.0', 8080)
※ 서버의 구성 환경 관계로 포트를 8000번 → 8080번으로 변경하였다. 각자 사용하는 서버의 설정에 적합한 포트 번호를 사용하면 된다.
원격지 서버를 이용하여 웹 서비스를 배포하는 경우 일반적으로 부하(load) 관리와 보안을 위해 HTTP 서버와 WSGI(Web Server Gateway Interface)를 사용한다. 예를 들어 HTTP 서버로는 Nignx를 많이 사용하고 플라스크 WSGI로는 gunicorn을 많이 사용한다. Nginx 와 같은 HTTP 서버는 클라이언트의 요청을 WSGI를 통해 플라스크 서버에 전달하고 플라스크 서버가 응답하는 방식으로 동작한다.
[클라이언트] ↔ (웹) ↔ [Nginx ↔ WSGI(gunicorn) ↔ 플라스크 서버] |
마지막으로 HTTP 서버를 설정(을 변경)한다.
Nginx 사이트 설정 파일(보통 /etc/nginx/sites-enabled 아래에 심볼링 링크로 위치한다)을 아래와 같이 수정하거나 새로운 server 컨텍스트(context)를 추가한다.
server {
server_name www.example.com;
root /home/react_flask/appmain/build;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location /api {
proxy_pass http://localhost:8080;
include /etc/nginx/proxy_params;
proxy_redirect off;
}
}
root 디렉티브(directive)는 Nginx 서버에게 서비스의 static 파일들이 있는 위치를 알려준다. index 디렉티브는 클라언트가 '/'로 끝나는 URL로 서버에 요청하거나 별도의 path를 지정하지 않은 경우 전달할 파일을 지정한다. 리액트를 이용해서 만들어진 페이지인 index.html 파일을 전달하도록 설정하였다.
다음으로 첫 번째 location 블록(block)은 서버에 들어오는 모든 URL(이를 '/'로 지정하였다)을 위한 설정이다. Nginx 서버는 외부로부터 들어오는 요청을 우선 파일($uri)에 대한 요청으로 취급하고 URL에 해당하는 파일이 없다면 이를 다시 디렉토리($uri/)로 해석한다. 그리고 요청받은 URL에 파일, 디렉토리 모두 존재하지 않을 경우 404 오류(page not found)를 전달한다.
두 번째 location 블록은 /api URL 요청을 서버의 8080번 포트에서 동작하는 WSGI 프로그램으로 전달하는 프록시 설정이다.
설정을 마쳤으면 설정에 오류가 없는지 'nginx -t'와 같은 커맨드로 확인하고(보통 라인 맨 마지막의 세미콜론 ;을 자주 빠뜨린다) Nginx를 reload 한다.
$> sudo systemctl reload nginx
리액트로 만들어진 페이지를 플라스크 서버에서 서비스하기 위해 아래와 같은 설정 과정들을 거쳤다.
① 리액트 프로젝트를 build 하고 서버에 업로드한다.
② 플라스크 app에 static 폴더 위치와 static 폴더의 자원에 접근하기 위한 주소를 지정한다.
③ 리액트로 만든 페이지에 end point를 설정해준다.
④ Nginx 설정을 변경한다.
이제 WSGI를 실행하여 플라스크 서버를 실행시킨 후 웹 브라우저를 통해 서버에 접근해보자.
$> gunicorn -w 1 -b :8080 run:app
아래와 같이 플라스크 서버에서 리액트로 만들어진 페이지를 서비스하고 설정한 기능들도 사용할 수 있는 것을 확인할 수 있다.
예제 진행에 필자의 연구실에서 사용하는 서버를 이용했기 때문에 웹 브라우저에서 URL을 삭제하였다. 독자들께서 사용하는 서버와 도메인을 이용해서 실습해도 동작하는 페이지를 볼 수 있을 것이다.
아래 내용은 서비스의 동작을 위해 반드시 갖추어야 할 것은 아니지만 참고 삼아 알아두면 좋을 것 같다.
웹 서버의 cache 기능을 사용하여 리액트 페이지를 캐싱(caching)할 수 있도록 할 수 있다. 응답 시간과 같은 성능에 긍정적인 영향을 줄 수 있다. Nginx의 사이트 설정을 수정하여 웹 서버의 cache 기능을 추가할 수 있다.
server {
server_name www.example.com;
root /home/react_flask/appmain/build;
index index.html;
location / {
try_files $uri $uri/ =404;
add_header Cache-Control "no-cache";
}
location /static {
expires 1y;
add_header Cache-Control "public";
}
location /api {
proxy_pass http://localhost:8080;
include /etc/nginx/proxy_params;
proxy_redirect off;
}
}
리액트 페이지의 캐싱 관련해서는 아래 문서를 참조해 볼 수 있다.
https://create-react-app.dev/docs/production-build/#static-file-caching
Creating a Production Build | Create React App
npm run build creates a build directory with a production build of your app. Inside the build/static directory will be your JavaScript and CSS files. Each filename inside of build/static will contain a unique hash of the file contents. This hash in the fil
create-react-app.dev
리액트 프론트엔드와 플라스크 백엔드로 구성된 서비스를 웹 서버를 통해 배포하는 과정에 대해 살펴보았다. 전체 과정을 다시 한번 정리해보면 아래와 같다.
① 리액트 프로젝트를 build 하고 서버에 업로드 한다.
② 플라스크 app에 static 폴더 위치와 static 폴더의 자원에 접근하기 위한 주소를 지정한다.
③ 리액트로 만든 페이지에 end point를 설정해준다.
④ 웹 서버(예, Nginx) 설정을 변경한다.
그리고 마지막으로 아래 보안과 관련한 과정들이 추가되면 좋다.
⑤ 보안을 위한 HTTPS 및 방화벽 설정
보안을 위해 HTTPS를 통한 메시지 암호화와 ufw등을 통한 방화벽을 설정해 준다. 방화벽은 필요한 포트들(예, 22-SSH, 80-HTTP, 443-HTTPS)을 제외한 포트들로 접근을 차단하여 서버를 보호하도록 설정해준다.
■