Python/Flask

Flask 백엔드 서버 Synology NAS 배포기

Juhyuck 2023. 8. 23. 20:22
728x90

간단한 웹서비스 백엔드 서버를 만들일이 있는데 한동안 자바스크립트로만 코딩하다보니 이번에는 Flask로 가볍고 빠르게 만들고 싶었다.

 

주요 기능은

  1. 주기적인 이미지 thumbnail 변환과 디렉토리의 파일 수 및 txt 파일 값 읽고 처리하기
  2. 이미지 파일과 짧은 영상 파일을 client 호출에 따라 전달
  3. 로그인, 회원가입, 가입승인, 사용자 권한 설정 및 사용자 삭제 등과 같은 기본적인 CRUD

 

1번 기능은 APScheduler 라이브러리를 사용해서 cron job을 만들고, PIL을 사용해서 thumbnail 만드는 작업이다.

 

cron은 os에서 세팅하는 것이 더 안정적이고 서버 상태와 독립적으로 실행되는 장점이 있겠지만, Synology NAS의 os에서 실행하는 것은 가급적 배제하는 것이 NAS 안정성 측면에서 좋겠다는 생각이 들었고, 서버 상태가 불안정한다거나 하는 등의 문제가 발생한다면 cron job도 실행안되는 것은 무방하다고 생각했다.

 

2번은 두 가지 방법을 두고 고민했다.

 

파일 그대로 전송할지, resize 후에 전송할지. 파일 그대로 전송하면 파일 크기가 3~4MB정도 되는 사진이 초당 전송되는 상황이 생길 수 있고, 가정용 인터넷 회선에 연결되어있는 나스인 것을 감안했을 때 몇명만 접속해서 동시에 사진을 요청하면 대역폭 한계로 느려지지 않을까 하는 걱정. 

 

반대로 PIL로 리사이즈 후에 전송하게 되면 사진 크기는 대폭 줄어들긴 하지만, 이미지 처리하는 프로세스와 메모리에 저장했다가 전송하는 방법으로 메모리 용량을 잠시라도 잡아두는 문제. 

 

우선 영상은 지켜보는 시간이 있어 빈번한 호출이 발생하지 않을 것으로 보고(이미 resize된 이미지로 만든 영상이기도 하고) 바로 보냈고, 이미지는 PIL thumbnail로 이미지 리사이즈를 해서 보내는 방식을 선택했다. 이 이미지는 send_file을 사용해서 보내고, 1번 기능에서 만든 thumbnail은 static 폴더에 저장되기 때문에 send_from_directory 메서드를 사용했다.

 

3번은 mongo DB와 연결해서 단순하게 구현했고, 로그인 시 jwt를 부여해서 jwt에 사용자의 권한을 설정하고, 설정된 권한에 대한 확인을 서버에서 진행하는 것으로 구현했다.

 

회원가입과 로그인 시 비밀번호 확인은 werkzeug 라이브러리를 사용해 hash 처리해서 저장했고, 사용자 인가는 flask_jwt_extended 라이브러리를 활용 해 권한이 필요한 endpoint에 @jwt_required() 데코레이터로 검증 하고, get_jwt_identity() 메서드로 검증된 jwt의 payload를 사용해서 권한 처리 등을 추가했다. 

 

static 폴더에서 이미지를 보내주는 엔드포인트에도, thumbnail을 보내줄 때는 권한을 확인하도록 했고, thumbnail 외 static 파일은 누구나 가져갈 수 있도록 jwt_required 데코레이터가 없기 때문에, thumbnail 요청 시에는 verify_jwt_in_request 메서드를 사용해서 jwt의 payload를 확인하는 처리를 추가했다.

 

비즈니스 로직 상, 회원가입을 하자마자 로그인해서 서비스를 사용하는 것은 아니고, 관리자의 승인과정을 거쳐 사용할 수 있게끔 구현했고, 이를 위해서 사용자 정보를 담는 컬렉션 외에 pending user 컬렉션을 별도로 만들어서 승인 대기 상태의 유저 정보를 담아두고 관리자가 승인할 경우 사용자 컬렉션으로 db 정보를 저장하도록 했다. 

 

추가로 사용자 가입 후 또는 계약기간 등을 감안해서 계정을 활성화 또는 비활성화 할 수 있도록 active 필드를 추가했고, 가입할 당시 상호 공유된 가입 코드를 입력하게 해서, 관리자가 승인 할 때의 휴먼에러를 줄일 수 있도록 했다.

 

앱은 완성했고,


Synology NAS에 배포하려면 우선...(모델: DS220+, DSM: 7.1.1 기준)

 

1. 패키지 센터에서 Web Station을 설치하고, Python 설치 (나는 3.9 설치함)

2. Web Station에서 스크립트 언어 설정 → Python 선택 후 생성

프로필 이름과 설명, Python 버전 선택 → uWSGI 설정(수정하지 않음) → python 모듈 추가(사용할 library/모듈 입력)

하면된다. python 모듈 추가는 프로필 만들고 난 뒤에도 다시 설정할 수 있으니 넘어가도 되고, 개발환경에서 pip freeze > requirements.txt 로 만든 파일을 불러오면 편한데, 이때 모듈명==버전 으로 저장된 것에서 모듈명과 == 사이에 스페이스가 있어야 제대로 불러와진다. (버근가? 스페이스가 없으면 모듈 명에 "==버전명"까지 포함되서 저장되는데... Synology에 리포트 해봐야겠다.)

 

3. 웹 서비스를 만들기

생성을 눌려서, 기본 스크립트 언어 웹사이트를 선택하고, 서비스는 python으로, 프로필은 아까 스크립트 언어 설정에서 만든 프로필을 선택해 준다. 물론 처음부터 웹서비스를 만들고 프로필 새로 만들기를 눌러도 되긴 하지만, 한단계씩 해본다는 측면에서... 뒤에 웹포탈에서도 마찬가지지만.

 

여튼 프로필 선택 후 다음을 누르면, 아래와 같이 일반 설정 구성이 나오는데,

여기서 이름과 설명을 적어주고, 문서루트는 작성한 코드가 있는 루트 폴더를 선택하고, WSGI 파일은 app.py나 application.py같이 서버가 실행되는 루트 파일을 선택하면 된다. 호출가능은 옆에 i 버튼에 마우스를 올리면 응용프로그램의 진입점 기능이라는데, 정확히 이해하진 못했고, 선택한 WSGI파일에서 플라스크 앱을 선언한 변수명을 적는 것 같아서, 나는 app으로 작성했다. (app.py 코드에는 app = Flask(__name__) 로 작성) 이후 다음으로 가면 작성한 것 확인 후 생성할 수 있다.

 

4. 웹 포털 설정

이제 마지막으로 웹 포털을 설정하면 되는데, 기본 포털은 편집할 수가 없고, 새로 생성해야 한다. 

생성을 누르면 웹 서비스 포털을 선택하고,

서비스는 아까 만든 웹 서비스를 선택한 다음, 포털 유형에서 이름 기반(도메인?), 포트 기반(내부 포트), 별칭 포털(??) 을 선택하면 되는데, 나는 포트 기반으로 내부 포트를 설정했다. HTTP와 HTTPS 포트를 설정해주면 되고, HSTS는 클라이언트 브라우저에게  HTTP 주소를 입력하더라도 HTTPS로 바꿔라고 하는 명령을 주입하는 설정이므로, 개발할 때 localhost에서 테스트하기 귀찮은 점이 많기 때문에 비활성화 했다.

 

그 외 엑세스 로그 활성화를 하면 접속 로그와 아이피 등을 볼 수 있다.

 

그렇게 생성하면, NAS 내부 포트에서 서버와 연결이 잘 되는 것을 확인할 수 있다.

다만, 외부에서 접속해서 확인하기 위해서는 아까 설정한 내부 포트와 외부 포트를 붙여줘야 한다. 여러 방법이 있지만, Synology NAS의 제어판 - 외부 액세스 - 라우터 구성에서 포트 포워딩을 해주는 설정은 iptime공유기를 사용하는 상황에서 잘 작동하지 않았다. (iptime - 고급설정 - 포트포워드 설정 - UPNP 규칙에 NAS에 설정한 포트포워딩 설정이 작성되는데 시간이 지나면 자꾸 지워지는 현상... 이유는 모르겠다.) 그래서, iptime 공유기 설정에서 포트포워딩 설정을 해주는 것으로 해결했다. 

 

그리고 synology에서 제공해주는 ssl 인증서를 사용하기 위해 제어판 - 로그인 포털 - 고급 - 역방향 프록시에서 localhost:포트번호로 연결해서 https로도 백엔드 서버에 요청할 수 있도록 했다. (CORS설정도 미리 해둬야 하고)

 

여튼 그렇게 하면, 외부에서도 사용가능한 백엔드 서버로 Synology NAS를 사용할 수 있는데.....


여기서 한참을 해맨 문제가 바로 방화벽.

 

이렇게 하면 이론적으로 다 되야 하는데 개발 단계에서 도메인으로 접속할 땐 되는데, 휴대폰이나 외부망에서는 접속이 안되는 문제가 발생하는 것, NAS 문제인지 포트포워딩 문제인지 ...한참 해매다가 방화벽 설정을 확인했고 해결했다. 

 

언제인지, default인지 모르겠지만 내가 내부망에서만 NAS접속이 되도록 설정해둔 것... 그러니 도메인으로 접속해도 같은 내부망에 있는 PC는 허용이 되서 개발할 때 테스트는 다 됐지만, 외부에서는 접속이 안되었던 것. 

 

제어판 - 보안 - 방화벽 - 방화벽 프로파일 - 규칙편집 에서, HTTP, Reverse Proxy와 HTTPS, Reverse Proxy를 모든 IP에서 허용되도록 하는 것으로 우선순위를 높였고, 외부에서 접속이 가능해졌다.


이제 작동은 잘 하긴 하는데 또 사진을 동시에 여러개 요청하거나 하면 서버가 멈추는 현상이 발생했는데, 그건 아가 스크립트 언어 설정에서 Python 프로필 설정의 uWSGI 탭에서 프로세스와 스레드 수, 최대요청 수를 늘려주는 것으로 해결했다. Synology NAS 메모리와 성능 자체로 어디정도의 부하까지 서비스가 가능할지는 별도의 부하테스트를 해봐야 알 것 같다. 그건 나중에...

'Python > Flask' 카테고리의 다른 글

Flask로 간단한 웹 서버 만들기  (0) 2023.04.07