[Python 100일 챌린지] Day 89 - 배포 준비하기
[Python 100일 챌린지] Day 89 - 배포 준비하기
지금까지
localhost:5000에서만 테스트했습니다. 🏠 하지만 실제 사용자들이 접속하려면 인터넷에 배포해야 합니다!개발 환경과 프로덕션 환경은 완전히 다릅니다:
- 개발: 편의성 우선 (debug=True, 상세한 에러 메시지)
- 프로덕션: 보안과 성능 우선 (debug=False, 에러 로깅)
오늘은 Flask 애플리케이션을 실제 서비스 환경에 배포하는 방법을 배웁니다. 환경 변수, WSGI 서버, 데이터베이스 마이그레이션까지!
내일 있을 최종 프로젝트 배포를 위한 준비입니다! 🚀
(30분 완독 ⭐⭐⭐⭐)
🎯 오늘의 학습 목표
📚 사전 지식
- [Day 81~88] - Flask 기본부터 인증까지
- 리눅스 기본 명령어 - 있으면 좋지만 필수 아님
- Git 기초 - 코드 버전 관리
🎯 학습 목표 1: 개발 환경 vs 프로덕션 환경
개발 환경 (Development)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app.py (개발 환경)
from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = True # 자동 재시작, 상세 에러
app.config['SECRET_KEY'] = 'dev-key' # 하드코딩 OK
@app.route('/')
def home():
return "Hello, Dev!"
if __name__ == '__main__':
app.run(debug=True, host='127.0.0.1', port=5000)
# ↑ Flask 내장 서버 (개발용)
개발 환경 특징:
1
2
3
4
5
✅ debug=True # 코드 변경 시 자동 재시작
✅ 상세한 에러 메시지 # 브라우저에 스택 트레이스 표시
✅ 테스트 데이터 # 샘플 데이터 사용
✅ SQLite # 간단한 파일 기반 DB
✅ 하드코딩 가능 # 빠른 개발
프로덕션 환경 (Production)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app.py (프로덕션 환경)
import os
from flask import Flask
app = Flask(__name__)
app.config['DEBUG'] = False # 에러 메시지 숨김
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') # 환경 변수
@app.route('/')
def home():
return "Hello, Production!"
# Flask 내장 서버 사용 안 함!
# Gunicorn, uWSGI 등 WSGI 서버 사용
프로덕션 환경 특징:
1
2
3
4
5
6
7
❌ debug=False # 보안을 위해 디버그 끔
✅ 최소한의 에러 정보 # 해커에게 정보 노출 방지
✅ 실제 데이터 # 사용자 데이터
✅ PostgreSQL/MySQL # 강력한 DB
✅ 환경 변수 # 민감한 정보 분리
✅ HTTPS # 암호화 통신
✅ 로깅 # 에러 추적
개발 vs 프로덕션 비교
1
2
3
4
5
6
7
8
9
10
항목 개발 환경 프로덕션 환경
─────────────────────────────────────────────────────────
디버그 모드 debug=True debug=False
서버 Flask 내장 Gunicorn/uWSGI
데이터베이스 SQLite PostgreSQL/MySQL
Secret Key 하드코딩 환경 변수
에러 표시 상세 스택 트레이스 최소 정보
HTTPS 선택 필수
로깅 콘솔 출력 파일/서비스
성능 느려도 OK 최적화 필수
🎯 학습 목표 2: 환경 변수와 설정 관리
환경 변수란?
1
2
3
4
5
6
7
8
9
10
11
# 환경 변수 = 운영체제에 저장되는 설정 값
# 코드에 민감한 정보를 직접 쓰지 않음!
# ❌ 위험: 코드에 직접 작성
app.config['SECRET_KEY'] = 'my-secret-key-123'
DATABASE_URI = 'postgresql://user:password@localhost/db'
# ✅ 안전: 환경 변수 사용
import os
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
DATABASE_URI = os.environ.get('DATABASE_URI')
환경 변수 설정 방법
1. 리눅스/맥 (bash):
1
2
3
4
5
6
7
8
9
# 임시 설정 (터미널 세션에만)
export SECRET_KEY="your-secret-key-here"
export DATABASE_URI="sqlite:///app.db"
# 확인
echo $SECRET_KEY
# Flask 실행
python app.py
2. Windows (PowerShell):
1
2
3
4
5
6
# 임시 설정
$env:SECRET_KEY = "your-secret-key-here"
$env:DATABASE_URI = "sqlite:///app.db"
# Flask 실행
python app.py
3. .env 파일 사용 (권장):
.env 파일:
1
2
3
4
# .env
SECRET_KEY=your-secret-key-here
DATABASE_URI=sqlite:///app.db
DEBUG=False
python-dotenv 설치:
1
pip install python-dotenv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# app.py
from flask import Flask
from dotenv import load_dotenv
import os
# .env 파일 로드
load_dotenv()
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['DEBUG'] = os.environ.get('DEBUG', 'False') == 'True'
DATABASE_URI = os.environ.get('DATABASE_URI')
if __name__ == '__main__':
app.run()
⚠️ 주의: .env 파일은 Git에 올리면 안 됨!
.gitignore:
1
2
3
4
# .gitignore
.env
*.db
__pycache__/
설정 클래스 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# config.py
import os
class Config:
"""기본 설정"""
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
"""개발 환경 설정"""
DEBUG = True
DATABASE_URI = 'sqlite:///dev.db'
class ProductionConfig(Config):
"""프로덕션 환경 설정"""
DEBUG = False
DATABASE_URI = os.environ.get('DATABASE_URI')
class TestConfig(Config):
"""테스트 환경 설정"""
TESTING = True
DATABASE_URI = 'sqlite:///test.db'
# 환경에 따라 설정 선택
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'test': TestConfig,
'default': DevelopmentConfig
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# app.py
from flask import Flask
from config import config
import os
def create_app(config_name=None):
"""애플리케이션 팩토리 패턴"""
app = Flask(__name__)
# 환경별 설정 로드
if config_name is None:
config_name = os.environ.get('FLASK_ENV', 'default')
app.config.from_object(config[config_name])
return app
# 사용
app = create_app('production')
if __name__ == '__main__':
app.run()
🎯 학습 목표 3: WSGI 서버 설정하기
Flask 내장 서버의 한계
1
2
3
4
5
6
7
8
9
# ❌ 프로덕션에서 사용하면 안 됨!
if __name__ == '__main__':
app.run(debug=True)
# 문제점:
# 1. 단일 스레드 (동시 요청 처리 불가)
# 2. 느린 성능
# 3. 보안 취약
# 4. 프로세스 관리 안 됨
Gunicorn (추천)
Gunicorn = Python WSGI HTTP Server (유닉스 전용)
설치:
1
pip install gunicorn
기본 실행:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app:app = 파일명:Flask객체명
gunicorn app:app
# 포트 지정
gunicorn --bind 0.0.0.0:8000 app:app
# 워커 프로세스 개수 (CPU 코어 * 2 + 1 권장)
gunicorn --workers 4 app:app
# 타임아웃 설정
gunicorn --timeout 120 app:app
# 모든 옵션 조합
gunicorn --bind 0.0.0.0:8000 --workers 4 --timeout 120 app:app
설정 파일 사용:
gunicorn_config.py:
1
2
3
4
5
6
7
# Gunicorn 설정 파일
bind = "0.0.0.0:8000"
workers = 4
timeout = 120
accesslog = "logs/access.log"
errorlog = "logs/error.log"
loglevel = "info"
실행:
1
gunicorn --config gunicorn_config.py app:app
프로세스 관리 (Systemd)
리눅스 서버에서 자동 시작/재시작:
/etc/systemd/system/myapp.service:
1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=My Flask Application
After=network.target
[Service]
User=myuser
WorkingDirectory=/home/myuser/myapp
Environment="PATH=/home/myuser/myapp/venv/bin"
ExecStart=/home/myuser/myapp/venv/bin/gunicorn --config gunicorn_config.py app:app
Restart=always
[Install]
WantedBy=multi-user.target
명령어:
1
2
3
4
5
6
7
8
9
10
11
# 서비스 시작
sudo systemctl start myapp
# 부팅 시 자동 시작
sudo systemctl enable myapp
# 상태 확인
sudo systemctl status myapp
# 재시작
sudo systemctl restart myapp
🎯 학습 목표 4: 실전 배포 체크리스트
배포 전 체크리스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ✅ 1. DEBUG 모드 끄기
app.config['DEBUG'] = False
# ✅ 2. SECRET_KEY 환경 변수로
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
# ✅ 3. 데이터베이스 설정
# SQLite → PostgreSQL/MySQL 변경
# ✅ 4. .env 파일 Git 제외
# .gitignore에 .env 추가
# ✅ 5. requirements.txt 생성
pip freeze > requirements.txt
# ✅ 6. 에러 로깅 설정
import logging
logging.basicConfig(filename='error.log', level=logging.ERROR)
# ✅ 7. HTTPS 설정
# Nginx 또는 클라우드 서비스 이용
# ✅ 8. CORS 설정 (필요 시)
from flask_cors import CORS
CORS(app, resources={r"/api/*": {"origins": "https://yourdomain.com"}})
# ✅ 9. Rate Limiting
from flask_limiter import Limiter
limiter = Limiter(app)
# ✅ 10. 정적 파일 서빙
# Nginx로 static 폴더 직접 서빙
requirements.txt 관리
1
2
3
4
5
# 현재 패키지 목록 저장
pip freeze > requirements.txt
# 다른 환경에서 설치
pip install -r requirements.txt
requirements.txt 예시:
1
2
3
4
5
6
Flask==3.0.0
gunicorn==21.2.0
python-dotenv==1.0.0
bcrypt==4.1.1
flask-cors==4.0.0
flask-limiter==3.5.0
로깅 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# app.py
import logging
from logging.handlers import RotatingFileHandler
import os
def setup_logging(app):
"""로깅 설정"""
if not app.debug:
# 로그 디렉토리 생성
if not os.path.exists('logs'):
os.mkdir('logs')
# 파일 핸들러 (10MB 넘으면 새 파일)
file_handler = RotatingFileHandler(
'logs/app.log',
maxBytes=10240000,
backupCount=10
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Application startup')
# 사용
app = create_app('production')
setup_logging(app)
# 로그 기록
@app.route('/api/data')
def get_data():
app.logger.info('Data requested')
try:
# ... 데이터 처리 ...
return jsonify(data)
except Exception as e:
app.logger.error(f'Error: {str(e)}')
return jsonify({'error': 'Internal error'}), 500
데이터베이스 마이그레이션
1
2
3
4
5
6
7
8
9
10
11
12
13
# SQLite → PostgreSQL 마이그레이션 예시
# 1. 데이터 백업
sqlite3 database.db .dump > backup.sql
# 2. PostgreSQL 설정
# .env 파일:
# DATABASE_URI=postgresql://user:password@localhost/dbname
# 3. 스키마 생성
python init_db.py
# 4. 데이터 복원 (필요 시)
프로젝트 구조 (최종)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
myflaskapp/
├── app.py # 메인 애플리케이션
├── config.py # 설정 파일
├── gunicorn_config.py # Gunicorn 설정
├── requirements.txt # 패키지 목록
├── .env # 환경 변수 (Git 제외)
├── .gitignore # Git 제외 파일
├── logs/ # 로그 폴더
│ └── app.log
├── templates/ # HTML 템플릿
│ ├── base.html
│ └── ...
├── static/ # 정적 파일
│ ├── css/
│ ├── js/
│ └── images/
└── venv/ # 가상 환경 (Git 제외)
💻 실전 예제: 배포 스크립트
배포 자동화 스크립트
deploy.sh:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
# Flask 애플리케이션 배포 스크립트
echo "🚀 배포 시작..."
# 1. Git Pull
echo "📥 최신 코드 가져오기..."
git pull origin main
# 2. 가상환경 활성화
echo "🐍 가상환경 활성화..."
source venv/bin/activate
# 3. 패키지 설치
echo "📦 패키지 설치..."
pip install -r requirements.txt
# 4. 데이터베이스 마이그레이션 (필요 시)
echo "🗄️ 데이터베이스 업데이트..."
# python migrate.py
# 5. Gunicorn 재시작
echo "🔄 서버 재시작..."
sudo systemctl restart myapp
# 6. 상태 확인
echo "✅ 상태 확인..."
sudo systemctl status myapp
echo "🎉 배포 완료!"
실행:
1
2
chmod +x deploy.sh
./deploy.sh
⚠️ 주의사항
1. Secret Key 생성
1
2
3
4
# 강력한 Secret Key 생성
import secrets
print(secrets.token_hex(32))
# → '6f8d2...' (64자)
2. HTTPS 필수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 프로덕션에서는 반드시 HTTPS 사용
# Let's Encrypt로 무료 SSL 인증서 발급 가능
# Nginx 설정 예시
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
}
}
3. 데이터 백업
1
2
3
# 정기적인 데이터베이스 백업
# crontab 설정
0 2 * * * /usr/bin/pg_dump mydb > /backups/db_$(date +\%Y\%m\%d).sql
🧪 연습 문제
문제: 배포 준비 완료하기
지금까지 만든 프로젝트를 배포 준비 상태로 만드세요:
- config.py 파일 생성
- .env 파일로 환경 변수 분리
- requirements.txt 생성
- 로깅 설정 추가
💡 힌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. config.py
class ProductionConfig:
DEBUG = False
SECRET_KEY = os.environ.get('SECRET_KEY')
# 2. .env
SECRET_KEY=...
DATABASE_URI=...
# 3. requirements.txt
pip freeze > requirements.txt
# 4. logging
logging.basicConfig(filename='app.log')
📝 요약
이번 Day 89에서 학습한 내용:
- 개발 vs 프로덕션: DEBUG, 보안, 성능 차이
- 환경 변수: .env 파일, os.environ
- WSGI 서버: Gunicorn, 프로세스 관리
- 배포 체크리스트: 로깅, HTTPS, 백업
핵심 명령어:
1
2
3
4
5
6
7
8
# 환경 변수 로드
export SECRET_KEY="..."
# Gunicorn 실행
gunicorn --workers 4 app:app
# 서비스 관리
sudo systemctl restart myapp
🔗 관련 자료
📚 다음 학습
Day 90: 🎯 미니 프로젝트 - 블로그 웹사이트 ⭐⭐⭐⭐⭐
드디어 Phase 9의 마지막 날입니다!
지금까지 배운 모든 것을 종합해 완전한 블로그 웹사이트를 만듭니다:
- 사용자 인증
- CRUD 기능
- REST API
- 댓글 시스템
- 검색 기능
Phase 9의 대망의 마무리, 함께 만들어봅시다! 🚀
“좋은 배포는 좋은 준비에서 시작됩니다!” 📦
Day 89/100 Phase 9: 웹 개발 입문 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.
