Flask는 파이썬 웹 프레임워크 중 하나로, 가볍고 간단한 구조로 되어있어 웹 애플리케이션을 쉽고 빠르게 개발
할 수 있게 도와줍니다.
간결하고 쉬운 사용성
Flask는 진입 장벽이 낮고 간단한 코드로도 웹 애플리케이션을 만들 수 있습니다. 작은 규모의 프로젝트부터 복잡한 프로젝트까지 유연하게 대응할 수 있습니다.
유연하고 확장 가능한 구조
개발자는 필요에 따라 선택적으로 라이브러리를 추가하거나 구성할 수 있어서 프로젝트의 요구 사항에 따라 맞춤형 애플리케이션을 개발할 수 있습니다.
이후 포스팅될 Flask-SQLAlchemy, Flask-WTF, Flask-Login 등과 같은 다양한 확장 라이브러리를 사용하여 개발을 보다 용이하게 할 수 있습니다.
💡 본 글은 예제 코드를 이용하여 설명합니다.
도커를 활용하여 Flask를 이용한 웹 애플리케이션를 구축하는 예제로써 주요한 디렉토리 구조는 다음과 같습니다.
Flask를 이용하여 웹 애플리케이션을 구축할 때, 아래의 구조와 똑같이 할 필요는 없습니다.
.
└── flask
└── source
└── my_app
├── forms
├── models
├── static
├── templates
└── views
디렉토리 별 용도는 아래의 표와 같습니다.
Directory | Description |
---|---|
flask | 웹 서비스를 위한 컨테이너, Flask 관련 코드 등이 존재하는 디렉토리 |
source | Flask가 실행되기 위한 코드가 존재하는 디렉토리 |
forms | HTML 폼과 관련된 코드가 존재하는 디렉토리 |
models | 데이터베이스와 관련된 코드가 존재하는 디렉토리 |
static | CSS, JS, Font 등 정적 파일이 존재하는 디렉토리 |
templates | HTML 파일이 존재하는 디렉토리 |
views | HTTP 요청을 처리하는 코드가 존재하는 디렉토리 |
flask 폴더의 Dockerfile을 살펴보겠습니다.
Python 3.10 환경을 기반으로 하며, requirements.txt는 Python 패키지의 의존성 목록을 정의하며, pip을 이용하여 설치합니다.
컨테이너가 실행될 때, wsgi.py를 실행하여 Flask가 구동됩니다.
FROM python:3.10-bullseye
COPY ./requirements.txt /requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
RUN mkdir /source
WORKDIR /source
CMD ["python", "wsgi.py"]
flask 폴더의 requirements.txt를 살펴보겠습니다.
코드 실행에 필요한 패키지를 정의합니다.
기능을 추가하기 위해 필요한 패키지가 필요한 경우, 패키지를 추가한 후 docker-compose build를 입력하여 도커 이미지를 빌드하면 됩니다.
Flask==2.3.2
Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.1
Flask-Login==0.6.3
Flask-JWT-Extended==4.5.3
Flask-Caching==2.0.2
Flask_SocketIO==5.3.6
flask-RESTx==1.2.0
PyMySQL==1.1.0
redis==4.6.0
psutil==5.9.6
💡 pip freeze를 이용하여 현재 설치된 패키지 목록과 버전을 확인할 수 있습니다.
Flask 시리즈에서 사용하게 될 주요 패키지들은 아래의 표와 같습니다.
Example | Description |
---|---|
Flask | 웹 애플리케이션을 쉽고 빠르게 개발하기 위한 라이브러리 |
Flask-WTF | Flask와 함께 사용되는 폼(Form) 처리를 위한 라이브러리 |
Flask-Login | 사용자 인증 및 세션 관리를 쉽게 구현할 수 있도록 도와주는 라이브러리 |
Flask-RESTx | RESTful API를 쉽게 개발할 수 있도록 도와주는 라이브러리 |
Flask-Caching | 간단하고 유연한 캐싱 기능을 제공하는 라이브러리 |
Flask_SocketIO | Flask에서 웹 소켓을 지원하기 위한 라이브러리 |
Flask-SQLAlchemy | 데이터베이스 액세스 툴킷 및 ORM 라이브러리 |
Flask-JWT-Extended | JWT(JSON Web Tokens)을 구현하고 관리하는 데 사용되는 라이브러리 |
docker-compose.yml을 살펴보겠습니다.
flask는 기본적으로 5000번의 포트를 사용합니다. ports를 이용하여 호스트의 80번 포트를 컨테이너의 5000번 포트로 매핑합니다.
환경 변수로는 데이터베이스와 관련된 정보를 넘겨주어 Flask 코드에서 데이터베이스에 접속할 수 있도록 합니다.
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask
container_name: flask
restart: always
ports:
- 80:5000
depends_on:
- mariadb
- redis
volumes:
- ./flask/source:/source
environment:
- MARIADB_PORT=3306
- MARIADB_USER=user
- MARIADB_PASSWORD=user
- MARIADB_DATABASE=mydb
networks:
default:
internal:
확장 패키지와 Flask 구동을 위한 설정 값들은 flask/source/config.py에 작성되어 있습니다.
PROPAGATE_EXCEPTIONS
은 오류가 뷰에서 발생했을 때 예외를 전파할지 여부를 결정하는 설정입니다. 기본 값은 False이며 오류가 발생하면 Flask가 자동으로 처리하지만, True로 설정하게 되면 오류를 직접 처리할 수 있습니다.
SECRET_KEY
는 세션, 확장 패키지들의 보안 토큰(CSRF Token, JWT 등) 등에 대한 무결성을 검증하는 값입니다. 이 값이 짧은 길이 혹은 알려진 문자 등 취약한 값으로 설정된다면 각종 보안 문제가 발생할 수 있으니 안전한 값(난수)으로 설정되어야 합니다.
DEBUG
는 애플리케이션을 디버그 모드로 실행할지 여부를 결정하는 값입니다. 이 값이 True이면 500 Server Internal Error가 발생 시 웹 브라우저를 통해 자세한 내용을 확인 할 수 있으며, python prompt도 이용할 수 있습니다. 또한, 개발 과정에서 코드가 변경될 때 서버를 재시작하지 않고도 자동으로 다시 시작하여 수정 사항을 즉시 반영하여 개발자의 작업을 효율적으로 만들어줍니다. 그렇기 때문에 배포 단계
에서는 DEBUG를 False
로 변경해야 합니다.
import os
import secrets
import datetime
class Config:
PROPAGATE_EXCEPTIONS = True
SECRET_KEY = secrets.token_bytes(nbytes=16)
# 코드 생략
class ProductionConfig(Config):
DEBUG = False
class DevelopmentConfig(Config):
DEBUG = True
SECRET_KEY = "8846fb3651e8f2b6f4c61a3fa4fab7e6"
JWT_ACCESS_TOKEN_EXPIRES = datetime.timedelta(days=7)
config = {
"development" : DevelopmentConfig,
"production" : ProductionConfig
}
애플리케이션 객체(app)
를 초기화하는 코드는 flask/source/my_app/__init__.py에 작성되어 있습니다.
create_app 함수에서는 config.py에서 지정한 설정 값들을 불러와서 애플리케이션의 동작을 구성합니다.
이후, Flask 확장 패키지들에 대한 객체를 초기화하고 Blueprint를 등록합니다.
app.config.from_object에 전달되는 config 클래스를 production으로 변경하면 DEBUG 모드를 False인 상태로 구동할 수 있습니다.
# 코드 생략
app = Flask(import_name=__name__, template_folder="templates")
db = SQLAlchemy()
lm = LoginManager()
jwt = JWTManager()
cache = Cache()
sock = SocketIO()
rc = StrictRedis(host="redis", port=6379, db=0)
def create_app():
app.config.from_object(obj=config["development"])
with app.app_context():
from my_app.views.index import bp_index
from my_app.views.board import bp_board
from my_app.views.comment import bp_comment
from my_app.views.dashboard import bp_dashboard
from my_app.views.cache import bp_cache
from my_app.api.v1 import bp_api
app.register_blueprint(blueprint=bp_index)
app.register_blueprint(blueprint=bp_board)
app.register_blueprint(blueprint=bp_comment)
app.register_blueprint(blueprint=bp_dashboard)
app.register_blueprint(blueprint=bp_cache)
app.register_blueprint(blueprint=bp_api)
db.init_app(app=app)
db.create_all()
lm.login_view = "index.login"
lm.login_message = "로그인이 필요합니다."
lm.init_app(app=app)
jwt.init_app(app=app)
cache.init_app(app=app)
cache.clear()
sock.init_app(app=app)
return app
__init__.py에서 설정된 애플리케이션 객체를 통해 실행하는 코드는 flask/source/wsgi.py에 작성되어 있습니다.
기본적으로는 app.run()
을 이용하지만, 본 시리즈에서는 Flask-SocketIO를 이용한 웹 소켓 예제가 포함되어 있기 때문에 sock.run()으로 실행됩니다.
from my_app import create_app, sock
app = create_app()
if __name__ == "__main__":
sock.run(app=app, host="0.0.0.0", port=5000, allow_unsafe_werkzeug=True)
docker-compose.yml이 존재하는 디렉토리에서 docker-compose up을 입력하여 이미지를 빌드하고 애플리케이션을 실행합니다.
flask | * Debug mode: on
flask | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
flask | * Running on all addresses (0.0.0.0)
flask | * Running on http://127.0.0.1:5000
flask | * Running on http://172.20.0.3:5000
flask | Press CTRL+C to quit
flask | * Restarting with stat
flask | Werkzeug appears to be used in a production deployment. Consider switching to a production web server instead.
flask | * Debugger is active!
flask | * Debugger PIN: 103-183-404
웹 브라우저를 통해 http://서버 IP/
로 접속하면 아래와 같은 페이지가 출력될 것입니다.
Flask를 이용하여 웹 애플리케이션을 구성할 경우, 기본적으로는 HTTP 기반으로 작동합니다.
인증서를 통해 HTTPS로 구동하기 위한 방법을 알아보겠습니다.
flask/source 디렉토리에 cert라는 폴더를 생성 후 인증서와 개인키를 넣습니다.
.
├── cert
│ ├── cert.crt
│ ├── cert.csr
│ └── private.key
본 글에서는 자체 서명 인증서를 이용합니다.
openssl req -nodes -newkey rsa:2048 -keyout private.key -out cert.csr -subj "/C=KR/ST=Seoul/L=Seoul/O=Alexandria/OU=Alexandria/CN=192.168.61.142"
openssl req -x509 -key private.key -in cert.csr -out cert.crt -days 365
HTTPS에 맞게 443번 포트를 매핑합니다.
flask:
build:
context: ./flask
dockerfile: Dockerfile
image: flask
container_name: flask
restart: always
ports:
- 443:5000
depends_on:
- mariadb
- redis
volumes:
- ./flask/source:/source
environment:
- MARIADB_PORT=3306
- MARIADB_USER=user
- MARIADB_PASSWORD=user
- MARIADB_DATABASE=mydb
networks:
default:
internal:
애플리케이션을 구동하는 wsgi.py에서 다음과 같이 SSL 설정을 합니다. Flask-SocketIO를 사용하지 않을 경우, app.run(ssl_context=ssl_context)로 사용하면 됩니다.
import ssl
from my_app import create_app, sock
app = create_app()
if __name__ == "__main__":
ssl_context=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(certfile="cert/cert.crt", keyfile="cert/private.key")
sock.run(app=app, host="0.0.0.0", port=5000, allow_unsafe_werkzeug=True, ssl_context=ssl_context)