lightweight and flexible framework for web development and web applications that utilizes the Python programming language
앞서 언급한 프로젝트에서, 서비스 포트 스캐너를 구현하며 Flask 프레임워크를 사용하였다. 프론트엔드에서는 React를 사용했는데... 이 둘이 서로 그다지 어울리는 조합이 아니라는 것은 나중에서야 알게 되었다. 그래도 평소 Django만 써보던 나로서는 새로운 프레임워크를 익숙한 언어로 배워 볼 수 있다는 점에 매력을 느껴, 긍정적으로 생각하고 공부를 시작했다. 오늘의 데일리 CS 에서는 이 공부 과정에서 알게 된 점들에 대해 다뤄보려 한다.
Spoiler: 오늘 포스팅에 코드 엄청 많음
Flask는 Python으로 작성된 마이크로 웹 프레임워크로, 웹 애플리케이션 및 RESTful API 개발에 사용된다. "마이크로"라는 명칭은 크기가 작다는 것을 의미하지 않으며, 핵심 기능만 제공하고 확장성을 강조하는 설계를 뜻한다. Flask는 간결하고 유연한 코드를 작성할 수 있도록 설계되었으며, Jinja2 템플릿 엔진과 Werkzeug WSGI 라이브러리를 기반으로 동작한다. 초보자부터 전문가까지 모두 사용하기 쉽도록 설계된 Flask는 Python 웹 개발의 대표적인 선택지 중 하나이다.
Flask는 2010년 Armin Ronacher가 Pallets 프로젝트의 일부로 시작한 오픈소스 프레임워크이다. Flask는 "April Fool's Joke"로 처음 공개되었으나, 사용자들이 그 간결함과 유연성을 주목하며 폭발적인 인기를 얻었다. Flask는 Django와 같은 대규모 프레임워크와 달리 필요한 기능만 추가하는 구조로 설계되었으며, 다양한 확장(예: Flask-SQLAlchemy, Flask-WTF)을 통해 사용자가 프로젝트에 필요한 기능을 쉽게 통합할 수 있다.
Flask는 간단하고 직관적인 코딩 경험을 제공하며, 다양한 이유로 많은 개발자가 선택한다. 주요 장점은 다음과 같다:
WSGI는 Python 웹 애플리케이션과 웹 서버 간의 통신을 정의하는 표준 인터페이스이다. Flask는 Werkzeug를 통해 WSGI를 구현하며, 이로 인해 Python 웹 애플리케이션이 서버와 상호작용할 수 있다. Flask가 자체적으로 웹 서버를 제공하지는 않지만, 개발 환경에서 flask run
명령어로 내장 WSGI 서버를 사용할 수 있다. 이 표준은 Flask가 다양한 웹 서버(예: Gunicorn, uWSGI)와 호환되도록 한다.
Flask는 사용자가 정의한 URL 라우트와 뷰 함수를 통해 요청을 처리하고 응답을 생성한다. 사용자가 브라우저에서 URL을 요청하면 Flask는 해당 URL에 매핑된 뷰 함수를 실행하고 결과를 반환한다. Flask는 요청(Request)와 응답(Response)을 처리하기 위해 Werkzeug WSGI를 활용하며, 템플릿 엔진(Jinja2)을 통해 HTML 페이지를 동적으로 렌더링할 수 있다.
예시:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Hello, Flask!"
if __name__ == "__main__":
app.run()
Framework | 특징 | 적합한 프로젝트 |
---|---|---|
Flask | 경량, 유연성 중심, 필요한 기능만 추가 가능 | 소규모 프로젝트, RESTful API 개발 |
Django | 완전한 웹 프레임워크, ORM 및 관리 인터페이스 기본 제공 | 대규모 프로젝트, 데이터 중심 애플리케이션 |
FastAPI | 비동기 지원, 자동 문서화(Swagger UI), 성능 최적화 | 실시간 API, 고성능 애플리케이션 |
Flask는 간결성과 확장성을 중시하며, 사용자가 애플리케이션 구조를 자유롭게 설계할 수 있는 반면, Django는 모든 것을 포함한 풀스택 프레임워크이다. FastAPI는 비동기 프로그래밍에 특화되어 있으며, 최신 기술을 적용한 고성능 API 개발에 적합하다.
Flask 애플리케이션은 Flask
클래스를 사용해 생성하며, app.route()
데코레이터를 통해 URL 라우트를 정의한다. 개발 환경에서는 app.run()
을 호출하여 애플리케이션을 실행한다.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Welcome to Flask!"
if __name__ == "__main__":
app.run(debug=True)
위 코드에서 debug=True
는 실시간 코드 변경 반영 및 오류 디버깅 화면을 활성화한다.
Flask 애플리케이션의 디렉토리 구조는 프로젝트 규모와 요구 사항에 따라 다르지만, 일반적으로 다음과 같다. 아래의 구조는 코드의 재사용성과 유지보수를 용이하게 하는 것을 우선순위로 두고 있다.
project/
├── app/
│ ├── __init__.py # 애플리케이션 팩토리 함수
│ ├── routes.py # URL 라우트 정의
│ ├── models.py # 데이터베이스 모델
│ ├── templates/ # HTML 템플릿
│ └── static/ # 정적 파일 (CSS, JS, 이미지 등)
├── tests/ # 테스트 코드
├── .env # 환경 변수 설정
└── run.py # 실행 스크립트
Flask의 요청-응답 주기는 클라이언트의 요청이 서버에서 처리되어 응답으로 반환되는 일련의 과정이다. 주요 단계는 다음과 같다.
Flask 객체는 애플리케이션의 중심으로, URL 라우팅, 설정 관리, 확장 로드 등을 담당한다. Flask 객체는 Flask
클래스를 사용해 생성하며, 애플리케이션 전역 상태를 관리한다. 앱 컨텍스트(Application Context)는 특정 요청과 관련된 데이터를 Flask 애플리케이션에서 안전하게 처리할 수 있도록 지원하는 환경이다. 예를 들어, current_app
은 현재 활성화된 Flask 애플리케이션 객체에 대한 참조를 제공하며, g
객체는 요청 수명 동안 데이터를 저장할 수 있다.
from flask import Flask, current_app, g
app = Flask(__name__)
@app.route("/")
def index():
g.message = "Hello from g!"
return g.message
with app.app_context():
print(current_app.name) # 현재 앱 이름 출력
요청 객체는 클라이언트가 서버로 보내는 요청 데이터를 처리하는 데 사용된다. Flask의 request
객체는 HTTP 메서드(GET, POST 등), URL 매개변수, 폼 데이터, 헤더 등을 포함한다. 이를 통해 사용자의 입력을 처리하거나 쿼리 스트링 데이터를 파싱할 수 있다. 요청 객체는 요청 수명 동안만 유효하며, Flask가 요청을 처리하는 동안 안전하게 사용된다.
from flask import Flask, request
app = Flask(__name__)
@app.route("/search")
def search():
query = request.args.get("q") # 쿼리 스트링에서 'q' 값 가져오기
return f"Searching for: {query}"
위처럼 코드를 작성하면, http://127.0.0.1:5000/search?q=flask
를 요청할 시 Searching for: flask
가 반환된다.
응답 객체는 Flask에서 클라이언트에게 반환하는 HTTP 응답을 생성한다. Flask는 문자열, JSON, HTML 등의 간단한 값도 응답으로 변환할 수 있지만, Response
객체를 사용하면 상태 코드, 헤더, 콘텐츠 유형을 세부적으로 제어할 수 있다. 응답 객체는 REST API 개발이나 커스텀 헤더 설정이 필요한 경우 유용하다.
from flask import Flask, Response
app = Flask(__name__)
@app.route("/custom")
def custom():
response = Response(
"This is a custom response",
status=201,
mimetype="application/json"
)
response.headers["X-Custom-Header"] = "CustomValue"
return response
URL 라우팅은 특정 URL 요청을 처리할 뷰 함수와 매핑하는 역할을 한다. Flask는 @app.route
데코레이터를 사용하여 URL 경로를 정의하고, HTTP 메서드(GET, POST 등)를 설정할 수 있다. 라우트는 동적 URL을 지원하며, URL 경로에서 매개변수를 추출하여 뷰 함수로 전달할 수 있다.
from flask import Flask
app = Flask(__name__)
@app.route("/user/<username>")
def show_user(username):
return f"Hello, {username}!"
@app.route("/post/<int:post_id>")
def show_post(post_id):
return f"Post ID: {post_id}"
http://127.0.0.1:5000/user/john
→ Hello, john!
http://127.0.0.1:5000/post/42
→ Post ID: 42
Flask는 Jinja2 템플릿 엔진을 사용해 HTML을 동적으로 생성한다. 템플릿은 동적 데이터를 삽입하기 위한 변수와 로직(반복문, 조건문)을 포함할 수 있다. Flask의 render_template
함수는 지정된 HTML 파일을 렌더링하며, 이를 통해 서버에서 생성된 데이터를 사용자에게 전달할 수 있다.
# template file - hello.html
<!DOCTYPE html>
<html>
<head><title>Hello Flask</title></head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
# Flask Application
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/hello/<name>")
def hello(name):
return render_template("hello.html", name=name)
위처럼 코드를 구성하면 http://127.0.0.1:5000/hello/Flask
를 요청할 시 HTML 페이지에 Hello, Flask!
가 표시된다.
HTTP 메서드(GET, POST, PUT, DELETE 등)는 클라이언트가 서버와 상호작용하는 방식을 정의한다. Flask에서는 각 라우트에서 지원하는 HTTP 메서드를 명시할 수 있다. 기본값은 GET
이지만, 다른 메서드를 추가하려면 methods
매개변수를 사용한다. 각 메서드는 고유한 목적을 가지며, RESTful API 설계에 핵심적인 역할을 한다.
from flask import Flask, request
app = Flask(__name__)
@app.route("/data", methods=["GET", "POST"])
def handle_data():
if request.method == "GET":
return "This is a GET request"
elif request.method == "POST":
data = request.json
return f"Received data: {data}"
This is a GET request
Received data: {...}
HTTP 메서드를 통해 CRUD(Create, Read, Update, Delete) 작업을 구현할 수 있으며, 이를 RESTful 서비스 설계에 활용하게 된다!
Flask Blueprints는 애플리케이션을 모듈화하여 대규모 프로젝트를 효율적으로 관리하도록 돕는 기능이다. 이를 통해 각 기능(예: 사용자 인증, 블로그 게시물 관리)을 별도의 모듈로 분리하고 독립적으로 관리할 수 있다. Blueprints는 라우트, 템플릿, 정적 파일 등을 포함하여 코드의 재사용성과 유지보수를 향상시킨다.
from flask import Blueprint
# 블루프린트 정의
auth = Blueprint('auth', __name__)
@auth.route("/login")
def login():
return "Login Page"
# 애플리케이션에 등록
from flask import Flask
app = Flask(__name__)
app.register_blueprint(auth, url_prefix="/auth")
# /auth/login 경로에서 "Login Page" 출력
Flask Extensions는 애플리케이션의 기능을 확장하는 플러그인으로, 데이터베이스 연동, 폼 처리, 인증 등 다양한 기능을 제공한다.
Extension | 주요 기능 |
---|---|
Flask-SQLAlchemy | 데이터베이스 ORM 관리 |
Flask-WTF | HTML 폼과 데이터 검증 |
Flask-Migrate | 데이터베이스 마이그레이션 |
Flask-Login | 인증 및 권한 관리 |
Flask-Caching | 캐싱을 통한 성능 최적화 |
Flask는 확장 가능성을 염두에 두고 설계된 가벼운 프레임워크인 만큼, 커뮤니티에서 개발된 플러그인과 서드파티 라이브러리를 활용할 수 있다.
이러한 플러그인들은 Flask 애플리케이션의 기능을 확장하는 데 핵심적인 역할을 한다.
Flask는 데이터베이스와 연동하기 위해 Flask-SQLAlchemy 확장을 사용한다. SQLAlchemy는 객체 관계 매핑(ORM)을 지원하며, Python 클래스와 데이터베이스 테이블 간 매핑을 통해 SQL 쿼리를 간소화한다. Flask-Migrate는 Alembic을 사용해 데이터베이스 스키마 변경(마이그레이션)을 관리한다.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
db.create_all()
Flask-WTF는 폼 생성과 데이터 검증을 간소화하는 확장이다. 이는 CSRF 방지 기능을 기본적으로 제공하며, 서버에서 데이터 유효성을 쉽게 확인할 수 있다.
from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
class NameForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
submit = SubmitField('Submit')
@app.route("/", methods=["GET", "POST"])
def index():
form = NameForm()
if form.validate_on_submit():
return f"Hello, {form.name.data}!"
return render_template("index.html", form=form)
Flask-Login은 사용자의 로그인 상태를 관리하고, 보호된 페이지에 접근을 제한하는 기능을 제공한다. JWT(JSON Web Token)는 클라이언트-서버 간 인증을 위한 토큰 기반 메커니즘으로, RESTful API에서 자주 사용된다.
from flask import Flask, redirect, url_for
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
login_manager = LoginManager(app)
class User(UserMixin):
def __init__(self, id):
self.id = id
@login_manager.user_loader
def load_user(user_id):
return User(user_id)
@app.route("/login")
def login():
user = User("123")
login_user(user)
return "Logged in!"
@app.route("/protected")
@login_required
def protected():
return "Protected page."
@app.route("/logout")
def logout():
logout_user()
return redirect(url_for("login"))
CSRF(Cross-Site Request Forgery)는 악의적인 요청을 서버에 보내는 공격으로, Flask는 CSRF 방지를 위해 Flask-WTF를 사용한다. 세션 관리는 Flask의 내장 세션 객체를 통해 클라이언트 상태를 유지한다.
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
csrf = CSRFProtect(app)
세션은 flask.session
을 통해 키-값 저장소처럼 데이터를 관리할 수 있다.
RESTful API는 HTTP 메서드(GET, POST, PUT, DELETE)를 통해 자원을 CRUD(생성, 읽기, 수정, 삭제) 방식으로 관리하는 아키텍처 스타일이다. Flask는 RESTful API 구현에 적합하며, 라우트를 정의하고 HTTP 메서드를 처리하는 방식으로 간단히 API를 구축할 수 있다. RESTful API는 URL을 통해 리소스를 명확히 표현하며, 요청/응답은 보통 JSON 형식으로 이루어진다.
from flask import Flask, request, jsonify
app = Flask(__name__)
# 간단한 RESTful API 구현
users = []
@app.route("/users", methods=["GET", "POST"])
def manage_users():
if request.method == "GET":
return jsonify(users) # 전체 사용자 반환
elif request.method == "POST":
new_user = request.json
users.append(new_user)
return jsonify(new_user), 201 # 새 사용자 생성
@app.route("/users/<int:user_id>", methods=["GET", "PUT", "DELETE"])
def user_detail(user_id):
if user_id >= len(users):
return {"error": "User not found"}, 404
if request.method == "GET":
return jsonify(users[user_id]) # 특정 사용자 반환
elif request.method == "PUT":
users[user_id] = request.json
return jsonify(users[user_id]) # 사용자 정보 수정
elif request.method == "DELETE":
users.pop(user_id)
return {}, 204 # 사용자 삭제
if __name__ == "__main__":
app.run(debug=True)
Flask-RESTful은 RESTful API 구축을 간소화하는 확장으로, 클래스를 사용해 리소스를 관리한다. 이 확장은 HTTP 메서드를 명확히 정의할 수 있으며, 코드 가독성과 재사용성을 높여준다.
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class UserResource(Resource):
def get(self, user_id):
return {"user_id": user_id}
def post(self):
return {"message": "User created"}, 201
api.add_resource(UserResource, "/users/<int:user_id>")
위 코드는 /users/<int:user_id>
경로에서 GET과 POST 메서드를 처리한다. API는 명확하고 확장 가능하게 작성된다.
JSON은 RESTful API의 데이터 교환 형식으로, Flask는 JSON 데이터를 쉽게 처리할 수 있도록 request.json
과 jsonify
를 제공한다. 클라이언트가 JSON 데이터를 POST 요청으로 보내면, Flask에서 이를 처리하고 JSON 응답을 생성할 수 있다.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/data", methods=["POST"])
def handle_data():
received_data = request.json # 클라이언트로부터 JSON 데이터 읽기
return jsonify({"received": received_data}) # JSON 응답 생성
// request
POST /data
{
"name": "John Doe",
"age": 30
}
// response
{
"received": {
"name": "John Doe",
"age": 30
}
}
Flask는 API의 버전을 URL 또는 헤더로 관리할 수 있다. URL 기반 버전 관리는 /v1/resource
와 같은 형식을 사용하며, 여러 버전의 API를 동시에 지원한다. 또한, Flask의 @app.errorhandler
데코레이터를 사용하여 오류를 처리하고 사용자에게 명확한 오류 메시지를 반환할 수 있다.
@app.route("/v1/users")
def users_v1():
return {"version": 1, "users": []}
@app.route("/v2/users")
def users_v2():
return {"version": 2, "users": []}
@app.errorhandler(404)
def not_found_error(error):
return {"error": "Resource not found"}, 404
비동기 프로그래밍은 작업이 동시에 실행되지 않고, 하나의 작업이 끝날 때까지 기다리지 않고 다음 작업을 시작할 수 있는 프로그래밍 방식이다. 예를 들어, 비동기 작업을 통해 서버는 파일을 읽거나 외부 API 호출을 기다리는 동안 다른 요청을 처리할 수 있다. Flask는 기본적으로 동기적으로 작동하지만, 비동기 처리를 통해 CPU 또는 I/O 집약적인 작업을 효율적으로 처리할 수 있다.
Flask 2.0부터는 async
키워드를 사용해 비동기 뷰 함수를 작성할 수 있다. 이는 데이터베이스 작업, 외부 API 호출과 같은 작업에서 비동기 코드를 활용할 수 있도록 한다.
from flask import Flask
import asyncio
app = Flask(__name__)
@app.route("/")
async def async_view():
await asyncio.sleep(1)
return "Hello, Async Flask!"
Flask는 자체적으로 비동기 작업 큐를 제공하지 않지만, Celery와 같은 강력한 도구를 통합하여 백그라운드 작업을 처리할 수 있다. Celery는 작업을 큐에 넣고, 독립적인 워커(worker)가 이를 처리하는 방식으로 작동한다. 이는 서버가 주 작업(예: HTTP 요청 처리)을 수행하는 동안 시간이 많이 걸리는 작업(예: 이메일 전송, 데이터 분석)을 백그라운드에서 처리할 수 있도록 한다.
예를 들어, 이메일 전송 작업을 동기적으로 처리하면 서버는 이메일 전송이 끝날 때까지 대기해야 하므로 다른 요청을 처리하지 못한다. Celery를 사용하면 이메일 전송 작업을 큐에 넣고, 워커가 이를 처리하도록 하여 서버의 가용성을 높일 수 있다.
from flask import Flask
from celery import Celery
app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
@celery.task
def send_email(email):
print(f"Sending email to {email}")
@app.route("/send/<email>")
def send(email):
send_email.delay(email) # 백그라운드 작업으로 실행
return "Email task started!"
WebSocket은 HTTP와는 다른 방식으로 클라이언트와 서버 간 실시간 양방향 통신을 가능하게 하는 기술이다. 일반 HTTP 요청은 클라이언트가 요청을 보낼 때만 서버가 응답을 반환하지만, WebSocket은 클라이언트와 서버가 지속적으로 연결된 상태를 유지하며 실시간으로 데이터를 주고받을 수 있다.
Flask-SocketIO는 Flask에서 WebSocket 기능을 간편하게 구현할 수 있는 확장이다. 실시간 채팅 애플리케이션, 실시간 알림 시스템, 주식 가격 스트리밍과 같은 애플리케이션에 적합하다.
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
@socketio.on('message')
def handle_message(msg):
print(f"Received: {msg}")
socketio.send(f"Echo: {msg}")
if __name__ == "__main__":
socketio.run(app)
비동기 요청과 응답 처리는 서버가 외부 API 호출이나 데이터베이스 쿼리 같은 대기 시간이 긴 작업을 처리할 때 효율적으로 작동한다. 동기 방식에서는 이러한 작업이 끝날 때까지 서버가 다른 요청을 처리하지 못하지만, 비동기 방식에서는 대기하는 동안 다른 작업을 처리할 수 있다.
Flask 2.0부터는 async
와 await
키워드를 사용해 비동기 요청을 처리할 수 있다. async
함수는 일반적인 동기 함수와 다르게 실행되며, await
키워드는 비동기 함수가 완료될 때까지 기다린다. 이를 통해 Flask는 외부 API와 효율적으로 상호작용하거나, 비동기 데이터베이스 드라이버를 사용할 수 있다.
aiohttp
를 사용해 외부 API에 비동기로 요청을 보냄import aiohttp
from flask import Flask
app = Flask(__name__)
@app.route("/async_request")
async def async_request():
async with aiohttp.ClientSession() as session:
async with session.get("https://jsonplaceholder.typicode.com/posts/1") as response:
data = await response.json()
return data
프로파일링은 애플리케이션의 성능 병목 현상을 식별하기 위해 각 함수나 코드 블록의 실행 시간을 분석하는 작업이다. Flask에서는 Werkzeug Debugger
와 같은 디버깅 도구를 기본적으로 제공하며, 이를 활성화하면 코드에서 발생하는 오류를 실시간으로 확인할 수 있다. 또한, flask-profiler
와 같은 플러그인을 사용하면 요청 처리 시간과 데이터베이스 쿼리 성능을 분석할 수 있다.
app = Flask(__name__)
app.config["DEBUG"] = True # 실시간 디버깅 활성화
캐싱은 동일한 요청에 대해 서버가 결과를 재생성하지 않고, 이전에 계산된 응답을 반환하는 방식으로 성능을 향상시킨다. Flask-Caching은 Flask 애플리케이션에서 간단히 캐싱을 구현할 수 있는 확장으로, 메모리, 파일, Redis 등을 캐싱 백엔드로 지원한다.
from flask import Flask
from flask_caching import Cache
app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
cache = Cache(app)
@app.route("/")
@cache.cached(timeout=60) # 캐시된 결과를 60초 동안 유지
def index():
return "This is a cached response"
Flask는 기본적으로 개발용 서버를 제공하지만, 이는 생산 환경에서 적합하지 않다. WSGI(Web Server Gateway Interface) 서버인 uWSGI나 Gunicorn을 사용하면 다중 프로세스를 통해 동시에 여러 요청을 처리할 수 있다. Gunicorn은 간단하게 설정할 수 있고, uWSGI는 고급 설정이 가능하며 성능이 뛰어나다. 상황에 따라 잘 골라 쓰자.
gunicorn -w 4 -b 127.0.0.1:5000 app:app
-w 4
: 워커(worker) 프로세스 수를 4개로 설정-b
: 애플리케이션 바인딩 주소로드 밸런싱은 여러 서버에 걸쳐 네트워크 트래픽을 고르게 분산시키는 기술이다. 이는 단일 서버의 과부하를 방지하고, 애플리케이션의 가용성과 성능을 유지하는 데 필수적이다. Flask 애플리케이션은 여러 서버(또는 WSGI 워커)로 구성된 환경에서 로드 밸런서를 통해 요청을 분산받을 수 있다. 실제 작업 환경에 응용하면 Nginx는 로드 밸런서로 작동하고, Gunicorn은 Flask 애플리케이션을 실행하는 방식으로 운용할 수 있다.
upstream flask_app {
server 127.0.0.1:8000;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
server {
listen 80;
location / {
proxy_pass http://flask_app;
}
}
Flask는 백엔드(API 서버)로 사용되고, React, Angular, Vue는 프론트엔드 애플리케이션을 구성한다. Flask는 RESTful API를 제공하며, 프론트엔드에서 데이터를 요청하고 화면에 표시한다.
/api/data
와 같은 엔드포인트를 제공AJAX(Asynchronous JavaScript and XML)는 웹 페이지를 새로고침하지 않고 서버와 데이터를 주고받을 수 있도록 한다. Flask는 AJAX 요청을 처리하여 동적인 사용자 경험을 제공할 수 있다.
<script>
function sendData() {
fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'John' })
})
.then(response => response.json())
.then(data => console.log(data));
}
</script>
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/api/submit", methods=["POST"])
def submit():
data = request.json
return jsonify({"received": data["name"]})
Flask에서 정적 파일은 HTML 페이지에서 사용하는 CSS, JavaScript, 이미지와 같은 리소스를 의미하며, 애플리케이션의 /static
디렉토리에 저장된다. Flask는 이러한 정적 파일을 효율적으로 관리하고 제공하기 위해 url_for
함수와 함께 정적 파일 경로를 쉽게 생성할 수 있도록 지원한다.
project/
├── app.py # Flask 애플리케이션
├── static/ # 정적 파일 폴더
│ ├── style.css # CSS 파일
│ ├── script.js # JavaScript 파일
│ └── logo.png # 이미지 파일
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<script src="{{ url_for('static', filename='script.js') }}"></script>
</head>
<body>
<h1>Flask Static Files Example</h1>
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo">
</body>
</html>
위와 같은 형태로 코드를 구성하고 구동하면 Flask는 url_for
함수를 사용하여 static
디렉토리에 있는 파일의 경로를 자동으로 생성한다. 예를 들어, {{ url_for('static', filename='style.css') }}
는 /static/style.css
로 변환된다.
Webpack과 Vite는 최신 프론트엔드 개발에서 자산(파일)을 효율적으로 관리하고 최적화하는 도구이다. Flask와의 통합은 주로 프론트엔드 코드(CSS, JS)를 번들링하고, Flask가 이를 정적 파일로 제공하도록 구성하는 방식으로 이루어진다.
const path = require('path');
module.exports = {
entry: './src/index.js', // 번들링 시작 파일
output: {
path: path.resolve(__dirname, 'static'), // 번들 파일 저장 위치
filename: 'bundle.js' // 생성될 파일 이름
},
mode: 'production' // 최적화 모드
};
vite.config.js
)에서 빌드 결과를 Flask의 /static
디렉토리에 저장하도록 지정/static
에 저장<script src="{{ url_for('static', filename='bundle.js') }}"></script>
Flask 애플리케이션을 배포하려면 로컬 개발 환경에서 실행 중인 코드를 실제 서버에서 운영할 수 있도록 구성해야 한다.
requirements.txt
를 생성하여 필요한 패키지를 설치할 수 있도록 한다.env
파일로 관리해 민감한 정보를 보호한다Flask==2.1.1
gunicorn==20.1.0
gunicorn -w 4 -b 0.0.0.0:8000 app:app
위의 명령은 app.py
에 정의된 Flask 애플리케이션을 4개의 워커 프로세스를 사용하여 실행한다.
Flask 애플리케이션은 다양한 클라우드 플랫폼을 통해 배포할 수 있다. 여기서 가장 접근성이 높은 Heroku와 대규모 애플리케이션 배포에 적합한 AWS/GCP/Azure를 비교한다.
Heroku는 간단한 설정으로 Flask 애플리케이션을 클라우드에 배포할 수 있다.
heroku create
git add .
git commit -m "Deploy Flask app"
git push heroku main
Docker는 애플리케이션과 실행 환경(의존성, 라이브러리)을 하나의 컨테이너에 묶어 어디서나 일관되게 실행할 수 있도록 하는 도구이다. "컨테이너"는 가상 머신과 달리 경량화되어 있으며, 개발 환경과 프로덕션 환경 간의 차이를 최소화한다.
애플리케이션의 디렉토리 구조를 다음과 같이 구성:
project/
├── app.py # Flask 애플리케이션 진입점
├── requirements.txt # 의존성 목록
├── Dockerfile # Docker 이미지 빌드를 위한 설정 파일
# app.py 예시
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, Dockerized Flask!"
# requirements.txt 예시
Flask==2.1.1
gunicorn==20.1.0
Dockerfile은 Docker 이미지를 생성하기 위한 스크립트이다. Python 기반 Flask 애플리케이션을 실행하기 위해 필요한 설정을 포함한다.
# Dockerfile 예시
# Python 3.9 환경에서 시작
FROM python:3.9
# 작업 디렉토리 설정
WORKDIR /app
# 소스 코드와 의존성 파일 복사
COPY . .
# 필요한 Python 패키지 설치
RUN pip install -r requirements.txt
# Gunicorn을 사용해 Flask 애플리케이션 실행
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "app:app"]
FROM
: Python 3.9 이미지를 기반으로 사용WORKDIR
: 컨테이너 내부에서 코드가 저장될 디렉토리 설정COPY
: 현재 디렉토리의 파일을 컨테이너 내부로 복사RUN
: requirements.txt
를 이용해 필요한 패키지 설치CMD
: Gunicorn으로 Flask 애플리케이션 실행(4개의 워커로 실행)Dockerfile을 기반으로 이미지를 생성!
docker build -t flask-app .
flask-app
이라는 이름의 Docker 이미지가 생성docker images
명령어로 생성된 이미지를 확인할 수 있을 것생성된 이미지를 기반으로 컨테이너를 실행
docker run -p 8000:8000 flask-app
-p 8000:8000
: 호스트의 8000번 포트를 컨테이너의 8000번 포트에 매핑flask-app
: 이전 단계에서 생성한 Docker 이미지 이름테스트 하는 법: 브라우저에서 http://localhost:8000
로 접속하여 Flask 애플리케이션이 정상적으로 실행되는지 확인!
Docker Compose는 여러 컨테이너(예: Flask와 데이터베이스)를 한꺼번에 실행하고 관리할 수 있다.
# docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
FLASK_ENV: production
docker-compose up
CI/CD는 소프트웨어 개발과 배포 과정을 자동화하여 효율성과 신뢰성을 높이는 프로세스를 의미한다.
이 시스템은 코드 변경 후 수동으로 테스트하거나 배포하는 과정을 줄여주며, 테스트 자동화, 버그 감지, 빠른 배포라는 장점을 제공한다.
Flask 애플리케이션도 지속적인 통합과 배포의 이점을 활용할 수 있다. CI/CD를 통해
Github Actions는 Github에서 제공하는 무료 자동화 도구로, 저장소의 코드 푸시 시 자동으로 워크플로를 실행한다. Flask 프로젝트에 CI/CD 파이프라인을 구축하기에 적합하다.
(1) 워크플로 파일 생성
Github 저장소에서 .github/workflows/ci.yml
파일을 생성한다. 이 파일은 CI/CD 프로세스를 정의한다.
(2) 워크플로 정의
워크플로 파일에서 다음을 설정:
on
: 이 워크플로가 실행되는 조건(예: 코드 푸시, PR 생성)jobs
: 실행할 작업의 집합다음은 Flask 애플리케이션의 CI/CD 구축 예제이다. 이 예제는 코드 push 시 자동으로 테스트와 Docker 이미지를 빌드하고, 서버에 배포한다.
.github/workflows/main.yml
name: Flask CI/CD
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 코드 다운로드
- name: Checkout code
uses: actions/checkout@v2
# Python 설정
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
# 종속성 설치
- name: Install dependencies
run: |
pip install -r requirements.txt
# 테스트 실행
- name: Run tests
run: |
pytest
# Docker 이미지 빌드
- name: Build Docker image
run: |
docker build -t flask-app .
# Docker 이미지 Docker Hub에 업로드
- name: Push Docker image to Docker Hub
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
docker tag flask-app my-dockerhub-user/flask-app:latest
docker push my-dockerhub-user/flask-app:latest
# 원격 서버에 배포
- name: Deploy to production
run: |
ssh user@your-server "docker pull my-dockerhub-user/flask-app:latest && docker run -d -p 8000:8000 flask-app"
(1) 의존성 설치
requirements.txt
파일에 기록된 Python 라이브러리를 설치한다. 이 단계에서 Flask, Gunicorn 등의 필수 패키지가 설치된다.
(2) 테스트 실행
pytest
를 사용해 코드를 테스트한다. 모든 테스트가 성공하면 다음 단계로 진행된다.
(3) Docker 이미지 빌드
Dockerfile을 기반으로 Flask 애플리케이션의 이미지를 빌드한다. 빌드된 이미지는 로컬 환경에서 실행 가능하다.
(4) Docker Hub에 이미지 업로드
이미지를 Docker Hub와 같은 컨테이너 레지스트리에 업로드하여 원격 서버에서 재사용할 수 있게 한다. 이는 여러 서버에서 이미지를 쉽게 가져와 실행할 수 있도록 한다.
(5) 프로덕션 서버 배포
SSH를 사용해 원격 서버에 연결하고, Docker 이미지를 가져와 실행한다. 이 단계에서 새 컨테이너가 실행되며 애플리케이션이 배포된다.
CI/CD가 성공적으로 설정되면 다음과 같은 자동화가 가능해진다.
Jenkins는 오픈소스 CI/CD 도구로, 복잡한 파이프라인을 구축하고 자동화된 테스트 및 배포를 지원한다. Flask 애플리케이션의 지속적 통합과 배포를 위한 강력한 옵션이다.
Jenkinsfile을 사용해 Flask 애플리케이션의 CI/CD를 정의한다.
pipeline {
agent any
stages {
stage('Checkout Code') {
steps {
git 'https://github.com/your-repo.git'
}
}
stage('Install Dependencies') {
steps {
sh 'pip install -r requirements.txt'
}
}
stage('Run Tests') {
steps {
sh 'pytest'
}
}
stage('Build Docker Image') {
steps {
sh 'docker build -t flask-app .'
}
}
stage('Deploy') {
steps {
sh 'ssh user@your-server "docker pull flask-app && docker run -d -p 8000:8000 flask-app"'
}
}
}
}
Github Secrets
, Jenkins 환경 변수
활용) 할 것!마이크로서비스는 애플리케이션을 작고 독립적인 서비스로 나누어 개발, 배포, 확장성을 높이는 설계 방식이다. Flask는 경량 프레임워크로서 각 마이크로서비스를 쉽게 구현할 수 있다. Flask 애플리케이션은 RESTful API를 통해 서로 통신하며, 이를 통해 독립적인 배포와 확장이 가능하다.
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/users")
def get_users():
return jsonify([{"id": 1, "name": "John"}])
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/orders")
def get_orders():
return jsonify([{"id": 101, "item": "Laptop"}])
참고로, 각 서비스는 개별적으로 실행되며, API Gateway를 통해 통합된다.
메시지 브로커는 서비스 간 데이터를 비동기적으로 전달하는 데 사용되며, Flask는 Celery와 통합하여 RabbitMQ 또는 Kafka를 활용할 수 있다.
from flask import Flask
from celery import Celery
app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'amqp://localhost'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
@celery.task
def add(x, y):
return x + y
@app.route("/add/<int:x>/<int:y>")
def add_task(x, y):
result = add.delay(x, y)
return f"Task submitted: {result.id}"
celery -A app.celery worker --loglevel=info
API 게이트웨이는 클라이언트 요청을 여러 마이크로서비스로 라우팅하는 중앙 허브 역할을 한다. Flask는 이를 위한 경량 API 게이트웨이로 동작할 수 있다.
from flask import Flask, jsonify, redirect
app = Flask(__name__)
@app.route("/api/users")
def user_service():
return redirect("http://localhost:5001/users")
@app.route("/api/orders")
def order_service():
return redirect("http://localhost:5002/orders")
if __name__ == "__main__":
app.run(port=8000)
이 게이트웨이는 클라이언트가 http://localhost:8000/api/users
에 요청을 보내면, 이를 사용자 서비스로 전달한다.
Flask는 경량 프레임워크로 설계되었기 때문에 단순성과 유연성에서는 강점을 가지지만, 대규모 애플리케이션 개발이나 복잡한 요구 사항을 처리할 때는 몇 가지 한계를 가진다.
첫 번째 한계는 기본 제공 기능이 제한적이라는 점이다. 예를 들어, Flask는 데이터베이스 연동(ORM), 사용자 인증, 관리자 페이지와 같은 기능을 기본적으로 제공하지 않는다. 이러한 기능은 Flask-SQLAlchemy, Flask-Login 등의 확장을 통해 추가해야 하며, 확장이 많아질수록 코드의 복잡성이 증가할 수 있다.
두 번째 한계는 개발자가 프로젝트 구조를 직접 설계해야 한다는 점이다. Flask는 자유로운 구조 설계를 지원하지만, 이로 인해 프로젝트가 커질수록 코드 관리가 어려워질 수 있다. 특히 초보자는 표준화된 구조가 없는 점에서 어려움을 느낄 수 있다.
세 번째 한계는 비동기 작업에 대한 제한이다. Flask는 2.0 버전부터 비동기 처리를 지원하지만, FastAPI와 같은 비동기에 최적화된 프레임워크에 비해서는 여전히 제약이 있다. 예를 들어, 실시간 데이터 처리나 고성능 비동기 API 구현에는 Flask보다 FastAPI가 더 적합하다.
Flask와 Django는 모두 Python 기반의 웹 프레임워크지만, 철학과 사용 사례에서 큰 차이를 보인다. Flask는 "마이크로 프레임워크"로, 간단한 애플리케이션부터 시작하여 필요한 기능을 추가하며 점진적으로 확장할 수 있도록 설계되었다. 반면 Django는 "풀스택 프레임워크"로, 데이터베이스 관리, 사용자 인증, 관리자 인터페이스 등 대부분의 기능을 기본적으로 제공한다.
철학과 설계 방식
Flask는 자유도가 높아 개발자가 원하는 대로 프로젝트를 설계할 수 있다. 필요한 기능을 최소화하고 경량 애플리케이션을 개발하거나, 확장을 통해 대규모 프로젝트로 성장시킬 수 있다. 반면, Django는 "한 가지 명확한 방법"을 따르는 것을 철학으로 삼아, 개발자가 표준화된 방식으로 애플리케이션을 설계하도록 한다.
기본 제공 기능
Django는 ORM(Object Relational Mapping), 사용자 인증 시스템, 관리자 페이지 등 대규모 애플리케이션에 필요한 기능을 기본적으로 제공한다. Flask는 이러한 기능을 제공하지 않으므로, 확장을 통해 직접 구현해야 한다.
프로젝트 크기와 적합성
Flask는 소규모 프로젝트나 RESTful API 개발에 적합하며, Django는 데이터베이스 중심의 대규모 웹 애플리케이션 개발에 더 적합하다.
특징 | Flask | Django |
---|---|---|
철학 | 유연성, 간단함 | 규칙 기반, 많은 기능 기본 제공 |
기본 제공 기능 | 제한적(라우팅, 템플릿 렌더링 등) | ORM, 인증, 관리자 페이지 등 다수 제공 |
학습 난이도 | 낮음, 단순한 구조 | 높은 진입 장벽, 복잡한 구조 |
적합한 프로젝트 | 소규모 애플리케이션, API 서버 | 대규모 데이터 중심 애플리케이션 |
FastAPI는 Flask와 비교되는 최신 Python 웹 프레임워크로, 비동기 프로그래밍을 기본으로 지원하며 높은 성능을 제공한다. 특히, 자동 문서화 기능(OpenAPI/Swagger)과 타입 힌트 기반 개발로 개발 속도와 생산성을 높이는 데 강점을 가진다.
특징 | Flask | FastAPI |
---|---|---|
비동기 지원 | 제한적으로 지원 | 기본적으로 지원 |
자동 문서화 | 수동 작성 필요 | OpenAPI/Swagger 자동 생성 |
성능 | 적절한 확장으로 성능 향상 가능 | 비동기에 최적화된 높은 성능 제공 |
적합한 프로젝트 | 소규모 프로젝트, RESTful API | 실시간 데이터 처리, 대규모 API 서비스 |
FastAPI는 최신 기술과 요구를 반영한 프레임워크로, 고성능 API와 실시간 데이터 처리에 강점을 가진다. 하지만 Flask는 여전히 단순성과 자유도를 선호하는 개발자들에게 사랑받고 있으며, 기존 확장 도구와 커뮤니티 지원을 바탕으로 경쟁력을 유지하고 있다.
정말... 소개사진 한 장 말고는 아무 이미지 없이 끝난 포스팅이다. 덕분에 사진을 붙여넣을 수고는 줄었지만, 코드를 다량 작성하면서 시간은 더 오래 걸렸다. 그래도 연습은 좀 됐으니 아무튼 럭키비키인걸로..?
사실 프로젝트에서 Flask를 쓰면서 느꼈던 불편함이 없지는 않았다. 처리 속도, 작업을 구현해놓은 라이브러리 등 정말 수많은 편의 요소들이 아쉬웠는데, 막상 조사해보니 원래부터 있었다는 것이다! 아무래도 처음 접해보는 프레임워크인데다, 제대로 공부할 시간 없이 바로 개발에 들어가느라 놓친 부분이 좀 있었던 것 같다. 다음 플젝이나 경험에선 사전 조사와 계획 세우기에 공을 좀 더 들여보는 걸로 하자..!