YOGYUI

Bestin 홈네트워크 Flask 웹서버 - Nginx 연동 본문

홈네트워크(IoT)/광교아이파크

Bestin 홈네트워크 Flask 웹서버 - Nginx 연동

요겨 2022. 2. 19. 21:11
반응형

 

기존에 내가 구현해둔 Bestin 홈네트워크 - Homebridge 연동 코드는 Python의 Flask 패키지를 활용해 웹서버를 구동하게 된다

https://github.com/YOGYUI/HomeNetwork/tree/main/IPark-Gwanggyo

 

GitHub - YOGYUI/HomeNetwork: HomeNetwork(Homebridge) Repo

HomeNetwork(Homebridge) Repo. Contribute to YOGYUI/HomeNetwork development by creating an account on GitHub.

github.com

[IPark-Gwanggyo/app.py]

from Include import get_home
from web import create_webapp, config

if __name__ == '__main__':
    home = get_home('IPark-Gwanggyo (Bestin)')
    home.initSerialConnection()
    app = create_webapp()
    app.app_context().push()
    app.run(host=config.HOST, port=config.PORT, debug=False)
    home.release()

[IPark-Gwanggyo/web/__init__.py]

import os
import sys
CURPATH = os.path.dirname(os.path.abspath(__file__))
sys.path.extend([CURPATH])
sys.path = list(set(sys.path))

from flask import Flask
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from config import config

bootstrap = Bootstrap()
moment = Moment()

def create_webapp():
    app = Flask(__name__)
    app.config.from_object(config)

    config.init_app(app)
    bootstrap.init_app(app)
    moment.init_app(app)

    from .main import main as blueprint_main
    app.register_blueprint(blueprint_main)

    from .api import api as blueprint_api
    app.register_blueprint(blueprint_api, url_prefix='/api')

    return app

코드를 Home Assistant와 연동하려다보니 SSL 인증이 필요하게 되어, SSL 인증서 발급 과정에서 사용되는 Let's Encrypt 설정에 대응하기 위해 Flask 웹서버와 Nginx(엔진 X)를 연동할 필요가 생겨서 시스템을 한 번 구축해봤다

※ 모든 작업은 서버가 구동중인 라즈베리파이4 보드 (Raspbian OS)에서 진행되었다

1. uWSGI

파이썬 기반으로 구축된 웹서버(ex: Flask, Django)와 Nginx가 상호작용하기 위한 미들웨어(정확히는 어플리케이션 컨테이너 서버)인 uWSGI를 설치한다

- Django 개발자라면 굉장히 친숙한 모듈들이다

 

인터페이스 구성은 아래 그림들을 보면 바로 이해할 수 있다

https://www.pinterest.co.kr/pin/447334175492546867/
https://whatisthenext.tistory.com/124

 

1.1. uwsgi 패키지 설치 (pip)

sudo pip3 install uwsgi

설치 완료 후 터미널에서 uwsgi를 실행해보자

1.2. app.py 수정

uwsgi에서 Flask 인스턴스에 접근할 수 있도록 다음과 같이 수정해준다

(조만간 깃허브에 변경내역 푸시할 예정)

from Include import get_home
from web import create_webapp, config
import atexit

app = create_webapp()
app.app_context().push()
home = get_home('IPark-Gwanggyo (Bestin)')
home.initSerialConnection()

def onExitApp():
    print("Web server is closing...")
    home.release()

atexit.register(onExitApp)

if __name__ == '__main__':
    app.run(host=config.HOST, port=config.PORT, debug=False)
  • 외부에서 app.py를 통해 Flask 인스턴스 app에 접근할 수 있도록 수정
  • 외부에서 app.py 접근 시 Home 객체를 생성하고 초기화할 수 있도록 수정
  • 파이썬 종료 핸들러 atexit 사용해 Home 객체 해제할수 있도록 수정

1.2. uWSGI 설정 파일 작성 (INI)

프로젝트 경로(/home/pi/Project/HomeNetwork/IPark-Gwanggyo)에 wsgi.ini 파일을 생성한 뒤, 다음과 같이 작성해준다 (vim, nano 등 에디터 사용 / VSCode SSH / Raspbian OS UI 등 편한 방법으로~)

[uwsgi]
chdir = /home/pi/Project/HomeNetwork/IPark-Gwanggyo

http = 0.0.0.0:9999
socket = ./uwsgi.sock
chmod-socket = 666

wsgi-file = ./app.py
callable = app
; daemonize = ./uwsgi.log
uid = bestin_server
pidfile = ./uwsgi.pid

master = true
processes = 1
threads = 1
enable-threads = true
vacuum = true
disable-logging = false
die-on-term = true

reload-mercy = 5
worker-reload-mercy = 5
single-interpreter = true
lazy-apps = false
harakiri-verbose = false
  • chdir: 작업경로 설정
  • socket: UNIX 소켓파일 경로 - Nginx와의 인터페이스
  • chmod-socket: 소켓파일 접근권한 (666으로 설정해야 Nginx 연동 원활)
  • wsgi-file: Flask 인스턴스가 생성되는 파이썬 파일 경로 (app.py)
  • callable: app.py에서 지정된 Flask 인스턴스 이름
  • daemonize: 백그라운드로 실행하고자 할 경우 설정 (로그파일 경로)
  • pidfile: 프로세스 아이디 (PID)를 기입할 파일 경로 설정 (백그라운드 실행 중 강제 종료 시 유용하다)
  • master: uWSGI 프로세스 마스터로 실행 여부
  • process: 프로세스 수 (Native 쓰레드 사용할 경우 1로 설정하지 않으면 문제가 발생함을 경험함...)
  • threads: 쓰레드 수
  • enable-threads: app.py가 python 쓰레드 사용할 경우 true로 설정
  • vacuum: uWSGI가 실행되면서 생성되는 파일들을 종료 시 삭제할 지 여부
  • disable-logging: uWSGI 관련 로그 출력할지 여부
  • die-on-term: SIGTERM 시그널 발생 시 재시작하지 않고 종료할 지 여부

이 외에도 설정할 수 있는 다양한 옵션이 있으니 공식 문서 (링크) 참고

 

프로젝트 경로에서 다음 명령어로 실행하면 바로 웹서버를 시작할 수 있다

uwsgi uwsgi.ini

라즈베리파이가 아닌 외부 장비(랩탑, 태블릿 등)에서 웹브라우저로 라즈베리파이 IP에 접속하면 내가 만든 웹페이지로 접속할 수 있다 (ex: 192.168.0.2:9999)

웹페이지는 한참 Flask + Bootstrap 으로 짜다가 방치해버려서... ㅋㅋㅋ 언젠가는 완성해야지... 하면서 세월만 흐르고 있다 ㅠ (로그인 기능 어떻게 할까 고민하다가 때려치운 기억이 새록새록)

2. Nginx

2.1. Nginx 패키지 설치

터미널에서 명령어 수행

※ 중요: 포트 80에서 실행중인 프로세스를 모두 종료해야 한다 (ex: apache2 server 등)

sudo apt install nginx

설치가 되면 바로 실행된다 

혹은 아래 명령어로 실행

sudo service nginx start

호스트 (라즈베리파이) IP 주소 혹은 라즈베리파이에서 0.0.0.0으로 웹브라저에서 접속해서 "Welcome to nginx!" html이 뜨면 정상적으로 실행된 것이다

2.2. 설정파일 생성

설정파일은 /etc/nginx/sites-available/default 로 존재한다

또한, /etc/nginx/sites-enabled/ 경로에 동일한 이름의 심볼릭 링크가 존재한다

nginx 최초 설치 시 default 파일 내용은 다음과 같다 (주석 제거)

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;

    index index.html index.htm index.nginx-debian.html;

    server_name _;

    location / {
        try_files $uri $uri/ =404;
    }
}

80번 포트 http 접속 시 /var/www/html 경로에 있는 index.html 파일을 반환하게 구성되어 있다

default 파일을 삭제한 뒤 원본 템플릿을 참고해서 설정 파일을 하나 새로 만들어주자 (파일명은 마음대로 정하면 된다)

sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default
sudo nano /etc/nginx/sites-available/home_network_server.conf

다음과 같이 작성해주자

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    root /var/www/html;

    location / {
        try_files $uri @app;
    }

    location @app {
        include uwsgi_params;
        uwsgi_pass unix:/home/pi/Project/HomeNetwork/IPark-Gwanggyo/uwsgi.sock;
        access_log /home/pi/Project/HomeNetwork/IPark-Gwanggyo/access.log;
    }
}

중요한 것은 uwsgi_pass에 앞서 uwsgi에서 설정한 UNIX 소켓 파일 경로를 적어주는 것이다

 

그리고 /etc/nginx/sites-enabled 경로에 심볼릭 링크를 생성해준다

sudo ln -s /etc/nginx/sites-available/home_network_server.conf /etc/nginx/sites-enabled

2.3. nginx 서비스 재시작

설정파일(nginx.conf) 적합성 (오류) 검사

sudo nginx -t

서비스 재시작

sudo service nginx restart
[TIP] - 이거 작업하면서 엄청 자주 쓴 명령어들 ㅎㅎ...
프로세스 죽이기: kill -9 {PID}
포트 사용 중인 프로세스 확인: sudo fuser -k {port}/tcp
nginx 중단: sudo service nginx stop, sudo systemctl stop nginx

3. 시작 프로그램 등록 (auto-start)

라즈베리파이 재부팅 시 Flask 서버를 자동으로 시작해주게 하기 위해 /etc/xdg/autostart 경로에 flask.desktop이란 파일을 다음과 같이 구성해뒀었다

[Desktop Entry]
Encoding=UTF-8
ExecStartPre=/bin/sh -c 'until ping -c1 google.com; do sleep 1; done;'
Exec=lxterminal --command "/bin/bash -c '/bin/python3 /home/pi/Project/HomeNetwork/IPark-Gwanggyo/app.py; /bin/bash'"

여기서 ExecStartPre는 인터넷 접속 확인 용도 (google.com으로부터 응답이 있을 때까지 계속 ping)

 

이제 이 파일을 지우고, uWSGI를 자동으로 시작하는 파일을 다음과 같이 만들어주고 재부팅해서 자동으로 동작(lxterminal 상에서 동작)하는지 확인해보면 된다

sudo nano uwsgi.desktop
{
    [Desktop Entry]
    Encoding=UTF-8
    ExecStartPre=/bin/sh -c 'until ping -c1 google.com; do sleep 1; done;'
    Exec=lxterminal --command "/bin/bash -c '/usr/local/bin/uwsgi /home/pi/Project/HomeNetwork/IPark-Gwanggyo/uwsgi.ini; /bin/bash'"
}

4. 동작 확인

이제 외부 장비에서 라즈베리파이 IP주소만 입력하면 바로 웹페이지로 접속할 수 있다 (=포트 80)

공유기의 DDNS(혹은 DNS 구매 등)랑 포트포워딩 기능을 적절히 활용하면 나만의 웹서버 구축을 완성할 수 있다

 

적당히 개발이 되었다면 소스코드랑 uWSGI, Nginx 설정을 컨테이너(docker) 하나로 묶어서 배포할 수 있다

웹페이지 개발을 어느 정도 마무리된다면, 컨테이너로 deploy하는 방법에 대해서도 포스팅해보도록 하자

끝~!

 

[참고]

https://flask-docs-kr.readthedocs.io/ko/latest/deploying/uwsgi.html

https://soyoung-new-challenge.tistory.com/118

https://2kiju.tistory.com/58

https://woolbro.tistory.com/94

반응형