포스트

[Python 100일 챌린지] Day 89 - 배포 준비하기

[Python 100일 챌린지] Day 89 - 배포 준비하기

지금까지 localhost:5000에서만 테스트했습니다. 🏠 하지만 실제 사용자들이 접속하려면 인터넷에 배포해야 합니다!

개발 환경과 프로덕션 환경은 완전히 다릅니다:

  • 개발: 편의성 우선 (debug=True, 상세한 에러 메시지)
  • 프로덕션: 보안과 성능 우선 (debug=False, 에러 로깅)

오늘은 Flask 애플리케이션을 실제 서비스 환경에 배포하는 방법을 배웁니다. 환경 변수, WSGI 서버, 데이터베이스 마이그레이션까지!

내일 있을 최종 프로젝트 배포를 위한 준비입니다! 🚀

(30분 완독 ⭐⭐⭐⭐)

🎯 오늘의 학습 목표

  1. 개발 환경 vs 프로덕션 환경
  2. 환경 변수와 설정 관리
  3. WSGI 서버 설정하기
  4. 실전 배포 체크리스트

📚 사전 지식

  • [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

🧪 연습 문제

문제: 배포 준비 완료하기

지금까지 만든 프로젝트를 배포 준비 상태로 만드세요:

  1. config.py 파일 생성
  2. .env 파일로 환경 변수 분리
  3. requirements.txt 생성
  4. 로깅 설정 추가
💡 힌트
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에서 학습한 내용:

  1. 개발 vs 프로덕션: DEBUG, 보안, 성능 차이
  2. 환경 변수: .env 파일, os.environ
  3. WSGI 서버: Gunicorn, 프로세스 관리
  4. 배포 체크리스트: 로깅, 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 라이센스를 따릅니다.