난, 그동안 stackoverflow 만 있으면 개발이 되는줄 알고있었다. 그런데, 이번에 카카오 데이터 센터 화재 사건 때문에 내가 꽤 많이 티스토리 사이트들에 의존적인지 알게 되었다. 영어로된 개발정보들은 stackoverflow 에서 찾을 수 있다. 그러나 한국어로 된 개발정보는 꽤 많이 티스토리가 검색됨을 알게 되었다. 아무래도 빠르게 내용을 확인하기 위해서 내게는 영어보다는 한국어가 매우 편하다.
오늘 vue 와 여러 javascript 정보를 검색하면서 여러번 티스토리가 검색되었다. 그런데 아직 카카오 데이터 센터 화재에서 티스토리가 복구가 되지 않아 내용을 볼 수 없었다. 꽤 많은 한국어 자료들이 facebook 에도 있을 것 같긴한데, 검색이 되지 않으니 무용지물이다.
오늘 티스토리 개발 블로그들의 도움을 많이 받고 있다는 것을 느낀 하루였다.
분류 전체보기
- stackoverflow 만 있으면 개발이 되는 줄 알았는데. 2022.10.17
- postgresql 에서 IS DISTINCT FROM 비교문 2022.10.05
- gunicorn 으로 flask 배포 2022.10.02
stackoverflow 만 있으면 개발이 되는 줄 알았는데.
postgresql 에서 IS DISTINCT FROM 비교문
오늘 postgresql 에서 where 조건에 비교문을 작성하였다. 파이썬에서 None 에 대해 비교하듯이 Null 과 비교하자 같은 결과가 나오지 않는 것을 발견했다.
파이썬에서
None != 8 이라고 하면 True 가 된다.
javascript 에서도 유사하게
null != 8 과 undefined != 8 모두 true 가 된다.
그런데 Postgresql 에서
select null != 8; 은 null 이 된다. where 절에서 특정 row의 column 이 null 인 상태에서 이런 비교 연산이 내부적으로 동작한다면 Python 과 JS 에서 사용하는 의도와 null 이 포함된 row 가 나오지 않게 된다.
이를 방지하기 위해 IS DISTINCT FROM 로 비교할 수 있다.
select null is distinct from 8 처럼 사용하면 true 라는 결과를 얻을 수 있다.
유사하게
Postgresql 에서는
select null=8 에 대해서 null 이라는 결과를 리턴하는데
select null is not distinct from 8 라고 사용하면 false 라는 결과를 얻을 수 있다.
gunicorn 으로 flask 배포
이 때까지 uwsgi 를 이용한 배포를 사용했었다. 그러다 개인 서버를 ubunt22.04 로 업그래이드 하고 이번에는 gunicorn 을 사용해보기로 했다. 개인 서버이기 때문에 여러 flask 가 배포될 수 있는 환경이다. 그래서 virtualenv 환경은 나에게 필수이다. 그리고 프로세스가 죽지 않도록 관리하기 위해 systemd 를 적용했다. 그러다 보니 일반적은 gunicorn 환경보다는 좀 복잡한 편이다.
전체 환경은
flask + gunicorn + Nginx 그리고 systemd 설정이다.
Ubuntu 22.04
Ngin 1.22.0
gunicorn 20.1.0
Flask 2.2.2
Python 3.10.6
참고 사이트
* https://docs.gunicorn.org/en/stable/deploy.html#using-virtualenv
* https://stackoverflow.com/questions/42219633/running-two-instances-of-gunicorn/42230370#42230370
nginx 를 설치한다든지 파이썬 가상환경을 설치하는 등의 과정은 생략했다. 인터넷에 나보다 더 잘 설명해둔 자료가 많기에 어렵지 않을 것이다. 다만 gunicorn 으로 배포시 systemd target 을 등록하는 과정은 찾기 어려워서 기록을 남겨둔다.
0. 여러개의 flask 가 동작하는 환경이기 때문에 프로젝트명을 통일하는게 좋다. 여기서는 flask_labstoo 이다.
1. virtualenv 환경 구축
내 경우 이런 환경은 /opt/py_envs/ 아래 폴더에 구축하는 편이다. /opt/py_envs/flask_labstoo 디렉토리에 virtualenv 환경을 구축했다.
2. gunicorn 설치
gunicorn 을 apt 로 설치할 수도 있고, 파이썬으로도 설치할 수 있다. virtualenv 환경을 사용해야하는 경우라면 꼭 해당 virtualenv 가 active 된 상태에서 pip instal gunicorn 으로 설치해야한다.
이 경우 gunicorn 위치가 /opt/py_envs/flask_labstoo/bin/gunicorn 에 존재하게 된다.
3. 파이썬 소스코드 받기
내 소스코드의 위치는 /user/share/nginx/flask_labstoo 이다.
4. gunicorn 설정 테스트
/usr/share/nginx/flask_labstoo 디렉토리에서 아래 코드를 실행시킨다.
/opt/py_envs/flask_labstoo/bin/gunicorn --workers 2 --bind 0.0.0.0:8000 main:app
여기서 main 은 flask 가 동작하는 wsgi 파일명이다. 내 경우는 main.py 에 flask 를 동작하는 코드가 아래 처럼 들어있다.
======== main.py ========
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
if __name__ == '__main__':
PORT = os.getenv('PORT') or 9000
if len(sys.argv) == 1 or sys.argv[1] == "runserver":
app.run(host='0.0.0.0', port=PORT, debug=True)
================================
이렇게 해서 웹브라우저에서 8000 포트로 접속이 가능한지 테스트 해 본다. 경우에 따라서 리눅스 서버의 포트 open 이 안되어 있거나 클라우드 서버의 port 가 안 열려 있으면 접속이 안되는 경우도 있다.
working 디렉토리 상관없이 gunicorn 을 실행하기 위해서는 아래와 같이 사용해야 한다. systemd 설정 할 때는 아래 코드를 사용하게 될 것이다.
/opt/py_envs/flask_labstoo/bin/gunicorn --workers 2 --bind unix:/var/run/flask_labstoo_gunicorn.sock --chdir /usr/share/nginx/flask_labstoo main:app
--chdir 를 사용해서
5. sytemd 설정
systemd 의 여러 서비스들의 묶음을 target 이라고 한다. gunicorn 이 여러개가 실행될 때, 이 서비스를 묶기위해 guicorn target 을 설정하게 된다. 그리고 systemd 서비스를 등록하게 하기위해 서비스 템플릿을 사용하게 된다.
sudo vim /etc/systemd/system/gunicorn.target
==== gunicorn.target ====
[Unit]
Description=Gunicorn
[Install]
WantedBy=multi-user.target
========
서비스 템플릿파일 생성한다. %i 를 잘 사용해야 한다.
sudo vim /etc/systemd/system/gunicorn@.service
==== gunicorn@.service ====
[Unit]
Description=gunicorn daemon
After=network.target
PartOf=gunicorn.target
# Since systemd 235 reloading target can pass through
ReloadPropagatedFrom=gunicorn.target
[Service]
User=root
Group=root
# WorkingDirectory가 동작하지 않음.
# WorkingDirectory=/user/share/nginx/%i
ExecStart=/opt/py_envs/%i/bin/gunicorn --workers 2 --bind unix:/var/run/%i_gunicorn.sock --chdir /usr/share/nginx/%i main:app
[Install]
WantedBy=gunicorn.target
========
우선 처음에 타켓서비스를 등록하고 서비스를 등록하고, 서비스를 시작해야 한다.
sudo systemctl start gunicorn.target
sudo systemctl enable gunicorn@flask_labstoo
sudo systemctl start gunicorn@flask_labstoo
sudo systemctl enable gunicorn@flask_labstoo 하는 과정에서 gunicorn@.service 파일의 %i 부분에 flask_labstoo 가 들어가서 등록되게 된다. (이 말은 추가적으로 flask 서버를 배포할 때는 소스파일, virtualenv 등 이름을 잘 정해야 한다는 것을 의미한다.
잘된다면 이것으로 끝나야 한다. 잘 동작하는지 확인은 sudo systemctl status gunicorn@flask_labstoo 으로 확인가능하다. 잘동작한다면 Active: active (running) 이 보여야 한다. 잘 안된다면 sudo journalctl -u gunicorn@flask_labstoo 명령어도 더 자세한 에러 상태를 알 수 있다. 키워드을 찾아 인터넷에 검색해 가면서 해결해야 한다. 그리고 여기서 위에서 설정한 것을 변경할 필요가 있다면 변경 후 시스템을 재로딩한 후 서비스를 다시 동작시켜야 한다.
sudo systemctl daemon-reload
sudo systemctl restart gunicorn@flask_labstoo
systemd 설정 명령어
#### systemd 설정 명령어 ####
# taret 등록과 부팅시 자동시작(stop 은 제거)
sudo systemctl start gunicorn.target
sudo systemctl stop gunicorn.target
# 서비스 추가 삭제
# 위의 gunicorn@.service 에서 %i 대신 flask_labstoo 가 들어가게된다.
sudo systemctl enable gunicorn@flask_labstoo
sudo systemctl disable gunicorn@flask_labstoo
# 서비스 시작, 종료
sudo systemctl start gunicorn@flask_labstoo
sudo systemctl stop gunicorn@flask_labstoo
sudo systemctl restart gunicorn@flask_labstoo
# 시스템 재로딩시
# 모든 변경 사항을 적용한다. 위의 파일을 수정하고 나서 적용해야 한다.
sudo systemctl daemon-reload
# 시스템 오류 확인
sudo systemctl status gunicorn.target
# 서비스 오류 확인
sudo systemctl status gunicorn@flask_labstoo
# 좀 더 상세한 오류 확인
sudo journalctl -u gunicorn@flask_labstoo
잘 동작한다면 /var/run/flask_labstoo_gunicorn.sock 이라는 파일이 생성되어야 한다. 이 파일을 통해 nginx 와 통신하게 한다.
6. Nginx 설정
/etc/nginx/sites-available 폴더에 적당한 파일 생성. 여기서는 labstoo 라는 이름이다.
======== labstoo ========
server {
listen 80;
server_name labstoo.com;
return 302 https://labstoo.co$request_uri;
}
server {
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/labstoo.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/labstoo.com/privkey.pem;
server_name labstoo.com;
client_max_body_size 100M;
keepalive_timeout 0;
# GET, POSST, PUT, DELETE, PATCH 만 가능
if ($request_method !~ ^(GET|POST|PUT|DELETE|PATCH)$ ) {
return 444;
}
root /var/www/flask_labstoo/;
access_log /var/log/nginx/flask_labstoo_access.log;
access_log /var/log/nginx/flask_labstoo_request.log post_log;
error_log /var/log/nginx/flask_labstoo_error.log;
location /static/ {
alias /usr/share/nginx/flask_labstoo/static/;
}
location /{
include proxy_params;
proxy_pass http://unix:/var/run/flask_labstoo_gunicorn.sock;
}
}
================================
내 경우에는 letsencrypt 인증서를 설정했다.
가장 중요한 부븐은 proxy_pass 부분으로 해당 sock 파일의 위치가 gunicorn 에서 생성한 sock 파일위치와 동일해야한다.
이 설정 후 nginx 를 재시작시키면 된다.
추가 필요작업
* gunicorn 에서 로그를 남기는 과정을 설정하지 않은 것 같다. flask 에러가 발생히 확인을 위해 로그를 남기는 세팅이 필요하다.
* systemd 설정에서 그냥 root 권한으로 gunicorn을 실행하도록 했는데, 이 부분을 www-data 같은 것으로 실행하도록 설정해야 해킹이 발생할 때 대비할 수 있을 것 같다.