이 때까지 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 같은 것으로 실행하도록 설정해야 해킹이 발생할 때 대비할 수 있을 것 같다. 

 

 중복되지 않는 랜덤 숫자 리스트를 일반적으로는 shuffle 이라고 부른다. 쉽게 말해 이미 존재하는 숫자 리스트에 대해 트럼프 카드를 섞는 것 처럼 섞으면 중복되지 않는 랜덤 숫자 리스트를 만들 수 있다. 그런데 이렇게 하면 전체 숫자리스트를 저장해야 해서 메모리 사용이 많은 문제가 있다. 그리고 속도도 그렇게 빠른 편이 아니다. 
 리스트 아이템에 대해 섞는 대표적인 알고리즘은 Fisher–Yates shuffle(https://ko.wikipedia.org/wiki/%ED%94%BC%EC%85%94-%EC%98%88%EC%9D%B4%EC%B8%A0_%EC%85%94%ED%94%8C )이다. 

그런데 적당히 중복되지 않는 랜덤 숫자 리스트(적당히라는 의미는 security 관점에서 보면 하나의 숫자 다음의 숫자를 예측 할 수도 있다는 의미이다.)를 만들려고 한다면 암호화 알고리즘을 이용하면 괜찮을 거라는 생각을 했다. 데이터를 암호화 한다는 것은 복화화가 가능해야 하기 때문에 모든 대응이 1대1 이라는 의미이다. 이 말은 암호화된 결과는 중복되지 않는다는 의미이다. 대충 아래와 같은 생각을 할 수 있다. 

for i in range(N):
	new_i = 암호화(i)
    if new_i >= N:
    	continue
    print(new_i)

이렇게 출력된 new_i 는 N 보다 작을 것이고 중복되지 않을 것이다.

좋은 생각이긴 한데, 일반적인 암호화는 최소 block 이 64bit 이다. (blowfish 의 최소 blocksize 가 64bit 이다.)
간단히 32bit 이하 숫자에 대해서는 제공하는 암호화는 없다. 그래서 간단히 1대1 대응이 되는 연산을 반복해서 암호화 유사하게 만들어 보았다. 

코드는 
https://gist.github.com/yiunsr/d1186be93fb99119a15a0917efdb1d27  
에서 확인 할 수 있다. 

 

어떻게 하면 자연스러운 한국 주소를 만들 수 있을까? 고민하다가 이것도 사람들이 많이 사용하는 주소를 기준으로 하는게 좋을 것 같았다. 그래서 인구기준으로 한국주소를 정리 했다. 행정안전부_지역별(법정동) 성별 연령별 주민등록 인구수(https://www.data.go.kr/data/15099158/fileData.do) 데이터를 통해서 법정동(법정리) 별 인구수를 확인 할 수 있다. 이에 따라서 인구수가 적은 법정동(법정리)는 제거 했다.(전체를 포함할 경우 데이터가 너무 많아진다.)
우리가 사용하는 동 주소에는 행정동과 법정동이 있다. 경우에 따라서 행정동과 법정동 이름 자체가 다른 경우도 꽤 많다. (https://www.seoul.go.kr/seoul/autonomy_sub.do 에서 추가정보를 확인 할 수 있다. ). 그러나 가짜 데이터용을 만들면서 이런 것 까지는 신경쓰기는 힘들다. 

한국에서 주소는 시도, 시군구, 읍면동, 리 로 세분화되어있다. 시도는 특별시, 광역시, 도(경기도, 강원도...) 를 포함하는 체계이고, 시군구는 과천시, 수원시 같은 시와 부여군, 예산군 같은 군과 광진구, 성북구 같은 구를 포함하는 개념으로 경우에 따라서는 성남시 수정구까지를 하나의 구로 볼 수 있다. 읍면동은 우리가 흔히 알고 있는 주소이고, 리는 읍면에서만 사용 되는 주소 체계이다. 

한국 주소 종류는 지번주소와 도로명 주소가 있다. 도로명주소 체계를 확인해보니 도로명으로  ~대로, ~로, ~길이 있고 이 곁가지 도로에 ~대로2길 ~대로2번길   ~로 2길 ~로 2번길  형태로 되어있다.(지역에 따라서 번길을 사용하는지 길을 사용하는지에가 다르다. 두 개가 번호를 붙이는 방식이 다른 방식이라고 한다. ). 그리고 경우에 따라서 ~안길을 사용하는 경우도 있지만 이를 무시했다. 

그래서 대충 아래와 같은방식으로 가짜 주소 데이터를 만들 수 있을 것 같다.
1. 인구수에 따라서 확률적으로 지번주소(address_bunji.csv 이용)를 생성한다.
2. 인구수에 따라서 적당히 동주소를 확장한다. ( ex: 구의동  =>  구의2동, 인구수가 많을 수록 많은 큰 값의 n동이 생성대도록 한다.)
3. 인구수에 따라서 적당히 번-지를 상성한다. ( 인구수가 많으면 큰 번이 생성가능하도록 하고 지는 가능한 100보다 작게 한다. 경우에 따라서는 지가 0일 경우 번만 사용되기도 한다. )
4. address_bunji.csv에 법정동코드가 있는데 이 주소와 일치하는 도로명주소(address_road.csv 이용)를 찾아서 해당 도로명주소중 랜덤하게 선택한다.
5. address_road.csv 파일에 보면 확장형태가 있는데, 이에 따라서 도로명주소에 적당히 N확장형태를 붙여서 주소를 완성한다. ( 강원도,홍천군,가일길,번길  의 경우 가일길2번길 로 확장주소를 만들 수 있다.)
6. 추가적으로 주거 형태(아파트, 빌라, 단독주택)에 따라서 건물명이 필요할 수 있다. 이 부분을 차후 더 분석해보도록 하겠다.  

 

address_bunji.csv
0.38MB
address_road.csv
0.41MB