はじめに
前回の【Djangoで再開発】では、DjangoでスキーリゾートAI積雪予測アプリ「Snow Deep Predict」を再開発しました。
今回は、そのアプリケーションを AWS ALB + EC2 + RDS の本格的な構成で本番環境にデプロイし、複数アプリケーション共存環境での運用まで解説します。
前回記事: [DjangoでスキーリゾートAI積雪予測アプリを作ってみた【Djangoで再開発編】]
🏗️ インフラ構成
構成図
Internet
↓
AWS ALB (Application Load Balancer)
↓ HTTPS/HTTP
EC2 Instance
├── Nginx (リバースプロキシ)
│ ├── /snow_deep/ → Gunicorn (ポート8000)
│ ├── /bikefinder/ → 他のDjangoアプリ
│ └── /aiapp/ → Streamlitアプリ
└── PostgreSQL RDS
技術スタック
- ロードバランサー: AWS ALB
- Webサーバー: Nginx 1.28
- アプリケーションサーバー: Gunicorn
- データベース: PostgreSQL (AWS RDS)
- Frontend: Bootstrap 5.3 + Vanilla JavaScript (fetch API)
- Ajax通信: fetch API(jQueryなしの生JavaScript)
- プロセス管理: systemd
- SSL/TLS: Let's Encrypt (Certbot)
- ドメイン: 独自ドメイン + Route53
📋 前提条件
AWS環境
- EC2インスタンス(Amazon Linux 2023)
- RDS PostgreSQL インスタンス
- ALB設定済み
- Route53でドメイン設定済み
ローカル環境
- 開発済みのDjangoアプリケーション
- Git リポジトリ準備
🚀 デプロイ手順
1. EC2インスタンスの基本セットアップ
# システム更新
sudo yum update -y
# 必要パッケージインストール
sudo yum install -y python3 python3-pip nginx postgresql15 git
# Python3をpythonコマンドで使用
sudo alternatives --install /usr/bin/python python /usr/bin/python3 1
2. アプリケーションのデプロイ
# プロジェクトのクローン
cd /home/ec2-user
git clone https://github.com/your-repo/snow_deep_predict.git snow_deep
cd snow_deep
# 仮想環境作成
python3 -m venv venv313
source venv313/bin/activate
# 依存パッケージインストール
pip install --upgrade pip
pip install -r requirements_production.txt
requirements_production.txt の例:
Django==4.2.23
django-cors-headers==4.7.0
psycopg2-binary==2.9.9
python-dotenv>=1.0.0
gunicorn==21.2.0
3. 本番用設定ファイル作成
settings_production.py
# snow_predict/settings_production.py
import os
from pathlib import Path
from dotenv import load_dotenv
# .envファイル読み込み
load_dotenv()
BASE_DIR = Path(__file__).resolve().parent.parent
# セキュリティ設定
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
DEBUG = False
# ホスト設定(ALB対応)
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',') if os.environ.get('ALLOWED_HOSTS') else [
'hungryclimber.com',
'www.hungryclimber.com',
'localhost',
'127.0.0.1',
'.amazonaws.com', # ALB内部通信用
]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'prediction',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'snow_predict.urls'
# テンプレート設定
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# PostgreSQL RDS設定
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME', 'snowpredictdb'),
'USER': os.environ.get('DB_USER', 'postgres'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST'),
'PORT': os.environ.get('DB_PORT', '5432'),
'OPTIONS': {
'connect_timeout': 60,
}
}
}
# 静的ファイル設定(重要:URLパスに注意)
STATIC_URL = '/snow_deep/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
# ALB対応のセキュリティ設定
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = False # ALBでSSL終端するため
SECURE_REDIRECT_EXEMPT = [r'^health/$', r'^admin/$']
# CORS設定
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOWED_ORIGINS = [
"https://hungryclimber.com",
"http://hungryclimber.com",
]
# ログ設定
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '/var/log/django/snow_deep.log',
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
},
}
# 国際化
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
USE_I18N = True
USE_TZ = True
環境変数ファイル (.env)
# /home/ec2-user/snow_deep/.env
# Django基本設定
DJANGO_SECRET_KEY=your-50-character-secret-key-here
DJANGO_SETTINGS_MODULE=snow_predict.settings_production
DEBUG=False
# データベース設定
DB_NAME=snowpredictdb
DB_USER=postgres
DB_PASSWORD=your-rds-password
DB_HOST=your-rds-endpoint.ap-northeast-1.rds.amazonaws.com
DB_PORT=5432
# ドメイン設定
ALLOWED_HOSTS=hungryclimber.com,www.hungryclimber.com,.amazonaws.com,localhost,127.0.0.1
4. Gunicorn設定
# gunicorn_production.conf.py
import multiprocessing
# サーバーソケット
bind = "127.0.0.1:8000"
backlog = 2048
# ワーカー設定
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
# プロセス名
proc_name = 'snow_deep_gunicorn'
# ログ設定
accesslog = '/var/log/gunicorn/snow_deep_access.log'
errorlog = '/var/log/gunicorn/snow_deep_error.log'
loglevel = 'info'
# プロセス設定
preload_app = True
daemon = False
pidfile = '/tmp/snow_deep_gunicorn.pid'
# セキュリティ
limit_request_line = 4094
limit_request_fields = 100
limit_request_field_size = 8190
5. systemdサービス設定
# /etc/systemd/system/snow_deep.service
[Unit]
Description=Snow Deep Predict Django Application
After=network.target
[Service]
User=ec2-user
Group=ec2-user
WorkingDirectory=/home/ec2-user/snow_deep
Environment="PATH=/home/ec2-user/snow_deep/venv313/bin"
EnvironmentFile=/home/ec2-user/snow_deep/.env
ExecStart=/home/ec2-user/snow_deep/venv313/bin/gunicorn snow_predict.wsgi:application -c /home/ec2-user/snow_deep/gunicorn_production.conf.py
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
6. Nginx設定(複数アプリ共存)
# /etc/nginx/conf.d/hungryclimber.conf
server {
listen 80;
server_name hungryclimber.com www.hungryclimber.com 127.0.0.1 localhost;
# ルートはデフォルトのnginxページ
root /usr/share/nginx/html;
index index.html;
# ALBヘルスチェック
location = /health/ {
add_header Content-Type text/plain;
return 200 "OK\n";
}
# ===== Snow Deep Predict (Django) =====
# 静的ファイル配信
location ^~ /snow_deep/static/ {
alias /home/ec2-user/snow_deep/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin "*";
}
# アプリケーション本体
location ^~ /snow_deep/ {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
proxy_http_version 1.1;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 65s;
proxy_pass http://127.0.0.1:8000/;
}
# ===== 他のアプリケーション =====
# bikefinder (Django)
location ^~ /bikefinder/static/ {
alias /home/ec2-user/bikefindAPP/motorcycle_finder/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location ^~ /bikefinder/ {
proxy_pass http://unix:/run/gunicorn-bikefinder/gunicorn.sock;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# aiapp (Streamlit)
location ^~ /aiapp/ {
proxy_pass http://127.0.0.1:8501;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
7. フロントエンド調整(重要)
URLパスの修正が必要です:
// static/js/prediction.js
// 修正前:
fetch('/predict/', {
// 修正後:
fetch('/snow_deep/predict/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken()
},
body: JSON.stringify({
resort: resortId,
months: months
})
})
8. データベースセットアップ
# ログディレクトリ作成
sudo mkdir -p /var/log/django /var/log/gunicorn
sudo chown ec2-user:ec2-user /var/log/django /var/log/gunicorn
# 仮想環境アクティベート
source venv313/bin/activate
# マイグレーション実行
python manage.py makemigrations --settings=snow_predict.settings_production
python manage.py migrate --settings=snow_predict.settings_production
# スーパーユーザー作成
python manage.py createsuperuser --settings=snow_predict.settings_production
# 静的ファイル収集
python manage.py collectstatic --noinput --settings=snow_predict.settings_production
9. サービス起動
# systemdサービス登録・開始
sudo systemctl daemon-reload
sudo systemctl enable snow_deep.service
sudo systemctl start snow_deep.service
# Nginx設定テスト・開始
sudo nginx -t
sudo systemctl restart nginx
sudo systemctl enable nginx
🔍 動作確認
1. サービス状態確認
# Djangoアプリ状態確認
sudo systemctl status snow_deep.service
sudo journalctl -u snow_deep.service -f
# Nginx状態確認
sudo systemctl status nginx
sudo nginx -t
# ポート確認
sudo netstat -tlnp | grep :8000
sudo netstat -tlnp | grep :80
2. ヘルスチェック
# ローカルヘルスチェック
curl -I http://localhost/snow_deep/health/
curl -I http://localhost:8000/health/
# 本番ヘルスチェック
curl -I https://hungryclimber.com/snow_deep/health/
3. アプリケーション動作確認
# メインページアクセス
curl -I https://hungryclimber.com/snow_deep/
# 静的ファイル確認
curl -I https://hungryclimber.com/snow_deep/static/css/style.css
🐛 よくあるトラブルと解決法
1. ModuleNotFoundError: No module named 'django'
原因: 仮想環境が正しく設定されていない
解決法:
# systemdサービスで仮想環境のパスを明示的に指定
# /etc/systemd/system/snow_deep.service の ExecStart を確認
ExecStart=/home/ec2-user/snow_deep/venv313/bin/gunicorn snow_predict.wsgi:application -c /home/ec2-user/snow_deep/gunicorn_production.conf.py
2. OperationalError: connection to server failed
原因: PostgreSQL接続設定の問題
解決法:
# settings_production.py の DATABASES 設定を確認
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST'),
'PORT': os.environ.get('DB_PORT', '5432'),
# OPTIONS は削除または適切に設定
}
}
3. DisallowedHost at /
原因: ALLOWED_HOSTS設定が不適切
解決法:
# settings_production.py
ALLOWED_HOSTS = [
'hungryclimber.com',
'www.hungryclimber.com',
'.amazonaws.com', # ALB用
'localhost',
'127.0.0.1',
]
4. 静的ファイル404エラー
原因: Nginx設定または静的ファイル収集の問題
解決法:
# 静的ファイル再収集
python manage.py collectstatic --noinput --settings=snow_predict.settings_production
# Nginx設定確認
sudo nginx -t
# 権限確認
ls -la /home/ec2-user/snow_deep/staticfiles/
5. JavaScript SyntaxError(Ajax通信エラー)
原因: JSファイルのURLパスが間違っている、または fetch API の設定不備
解決法:
// prediction.js のURL修正とAjax設定
fetch('/snow_deep/predict/', { // パスプレフィックス追加
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken() // CSRF対応
},
body: JSON.stringify(data)
})
.then(response => response.json())
.catch(error => console.error('Ajax error:', error));
📊 ログ管理
1. ログファイル場所
# Djangoアプリログ
tail -f /var/log/django/snow_deep.log
# Gunicornログ
tail -f /var/log/gunicorn/snow_deep_access.log
tail -f /var/log/gunicorn/snow_deep_error.log
# systemdログ
sudo journalctl -u snow_deep.service -f
# Nginxログ
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
2. ログローテーション設定
# /etc/logrotate.d/snow_deep
/var/log/django/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 ec2-user ec2-user
}
/var/log/gunicorn/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 ec2-user ec2-user
postrotate
systemctl reload snow_deep.service
endscript
}
🔧 パフォーマンス最適化
1. Gunicorn最適化
# gunicorn_production.conf.py
workers = multiprocessing.cpu_count() * 2 + 1 # CPU数に応じて調整
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
max_requests = 1000
max_requests_jitter = 50
2. Nginx最適化
# /etc/nginx/nginx.conf
worker_processes auto;
worker_connections 1024;
# gzip圧縮
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# キープアライブ
keepalive_timeout 65;
keepalive_requests 100;
3. PostgreSQL最適化
-- RDSパラメータグループで設定
shared_preload_libraries = 'pg_stat_statements'
max_connections = 100
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
🔒 セキュリティ設定
1. SSL/TLS証明書 (Let's Encrypt)
# Certbot インストール
sudo yum install -y certbot python3-certbot-nginx
# 証明書取得
sudo certbot --nginx -d hungryclimber.com -d www.hungryclimber.com
# 自動更新設定
sudo crontab -e
0 12 * * * /usr/bin/certbot renew --quiet
2. ファイアウォール設定
# Security Group設定 (AWS Console)
# HTTP: 80 - ALBからのみ
# HTTPS: 443 - ALBからのみ
# SSH: 22 - 管理者IPからのみ
3. Django セキュリティ設定
# settings_production.py
SECURE_SSL_REDIRECT = False # ALBでSSL終端
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_FRAME_DENY = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
📈 監視・運用
1. ヘルスチェック実装
# prediction/views.py
def health_check(request):
"""ALB ヘルスチェック用エンドポイント"""
try:
# データベース接続確認
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
return HttpResponse(
"OK - Snow Deep Predict is running",
status=200,
content_type="text/plain"
)
except Exception as e:
return HttpResponse(
f"ERROR - {str(e)}",
status=503,
content_type="text/plain"
)
2. 基本的な監視スクリプト
#!/bin/bash
# /home/ec2-user/scripts/monitor.sh
# サービス状態確認
if ! systemctl is-active --quiet snow_deep.service; then
echo "$(date): Snow Deep service is down" >> /var/log/monitor.log
sudo systemctl restart snow_deep.service
fi
# ディスク使用量確認
DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt 80 ]; then
echo "$(date): High disk usage: ${DISK_USAGE}%" >> /var/log/monitor.log
fi
# ログファイルサイズ確認
find /var/log -name "*.log" -size +100M -exec echo "$(date): Large log file: {}" \; >> /var/log/monitor.log
3. 定期メンテナンス
# crontab -e
# 毎日午前3時にログローテーション
0 3 * * * /usr/sbin/logrotate /etc/logrotate.conf
# 毎週日曜日午前2時にシステム更新確認
0 2 * * 0 /usr/bin/yum check-update
# 毎時監視スクリプト実行
0 * * * * /home/ec2-user/scripts/monitor.sh
🚀 デプロイ自動化
1. デプロイスクリプト
#!/bin/bash
# deploy.sh
set -e
echo "Starting deployment..."
# Git更新
git pull origin main
# 仮想環境アクティベート
source venv313/bin/activate
# 依存関係更新
pip install -r requirements_production.txt
# マイグレーション
python manage.py migrate --settings=snow_predict.settings_production
# 静的ファイル収集
python manage.py collectstatic --noinput --settings=snow_predict.settings_production
# サービス再起動
sudo systemctl restart snow_deep.service
echo "Deployment completed successfully!"
2. CI/CD連携 (GitHub Actions例)
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to EC2
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ec2-user
key: ${{ secrets.KEY }}
script: |
cd /home/ec2-user/snow_deep
./deploy.sh
💰 コスト最適化
1. RDSコスト削減
- Reserved Instance: 1年契約で30-60%削減
- 適切なインスタンスサイズ: 過剰スペック避ける
- 自動バックアップ期間: 必要最小限に設定
2. EC2コスト削減
- Spot Instance: 開発環境で70-90%削減
- Auto Scaling: 負荷に応じた自動調整
- EBS最適化: gp3への移行でコスト削減
3. ALBコスト削減
- Target Group最適化: 不要なヘルスチェック削減
- リスナールール: 効率的なルーティング
🎯 まとめ
本記事では、DjangoアプリケーションをAWS ALB + EC2 + RDSの本格的な本番環境にデプロイする方法を詳しく解説しました。
学んだポイント
- 複数アプリ共存: Nginxでのパスベースルーティング
- 本番設定: セキュリティとパフォーマンスの両立
- Ajax通信: Vanilla JavaScript (fetch API) での非同期通信実装
- トラブルシューティング: よくある問題と対処法
- 運用・監視: 継続的な安定運用のための仕組み
ベストプラクティス
- ✅ 環境変数で機密情報管理
- ✅ 適切なログ設定とローテーション
- ✅ ヘルスチェックとモニタリング
- ✅ 自動デプロイとバックアップ
- ✅ セキュリティ設定の徹底
次のステップ
- Container化: Docker + ECS/EKSでの運用
- CDN導入: CloudFrontでの高速化
- monitoring強化: CloudWatch + Grafana
- CI/CD: GitHub Actions/GitLab CI
- Multi-AZ: 高可用性構成
🔗 関連リソース
- 前回記事: [DjangoでスキーリゾートAI積雪予測アプリを作ってみた【開発編】]
- Django公式: https://docs.djangoproject.com/
- AWS公式: https://docs.aws.amazon.com/
- Nginx公式: https://nginx.org/en/docs/
- Let's Encrypt: https://letsencrypt.org/
質問やフィードバックがあれば、コメントでお気軽にお声かけください!
この記事が Django アプリの本番運用の参考になれば幸いです。
#Django #AWS #デプロイ #本番運用 #JavaScript #Ajax #ALB #EC2 #RDS #Nginx #Gunicorn #インフラ