Django デプロイ時によくある10の間違いと解決策

「Djangoをデプロイするときに避けるべき10の誤り」について書かれた記事を読みました。

https://medium.com/@erik_48905/10-common-django-deployment-mistakes-and-how-to-avoid-them-7ca2faac8f62

しかし、全てが必ずしも誤りとも言えないという感想を持ちました。記事で挙げられた10点について**必ず対応すべきセキュリティ問題**と、**対応するとメリットがあるパフォーマンス最適化**の2つに分けて紹介していきたいと思います。

## 必ず対応すべきセキュリティ問題

セキュリティに関わる問題は優先度が最も高く、放置するとアプリケーションとユーザーデータが深刻なリスクにさらされます。以下の問題は必ず対処しましょう。

### S1. デフォルトの SECRET_KEY をそのまま使用する

#### リスク
Django のデフォルト SECRET_KEY を使用したり、バージョン管理システムにコミットしたりすることは、セキュリティ上の大きな問題です。この鍵はセッションデータの暗号化など重要な役割を果たすため、漏洩すると深刻な脆弱性につながります。

SECRET_KEY が漏洩すると、セッションハイジャック、CSRF保護の回避、署名付きデータの偽造など、複数の攻撃ベクトルが開かれます。これは最優先で対処すべきセキュリティリスクです。

#### 解決策
本番環境用に強力でランダムな鍵を生成し、コードベースから分離して管理しましょう。

```python
import secrets
print(secrets.token_urlsafe(50))
```

生成した鍵は環境変数として設定するか、[django-environ](https://github.com/joke2k/django-environ) のようなツールを使用して管理するのがおすすめです。シークレット情報をコード外部で保持することがポイントです。

Python 3.6以降では、上記の`secrets`モジュールを使用するのがベストプラクティスです。古いバージョンでは`os.urandom`を使用する方法もありますが、セキュリティ的には`secrets`モジュールの方が推奨されています。


### S2. 本番環境で DEBUG = True のままにする

#### リスク
DEBUG モードを有効にしたままだと、エラー発生時に詳細なトレースバックやシステム情報が表示されます。これは開発時には便利ですが、本番環境では攻撃者に貴重な情報を与えてしまう危険性があります。

DEBUG=True の状態では、例外が発生した際にファイルパス、変数値、インストールされているパッケージなど内部情報が漏洩します。攻撃者はこの情報を使ってさらに高度な攻撃を計画する可能性があります。**絶対に避けましょう**

#### 解決策
本番環境では必ず以下の設定を行いましょう：

```python
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com']
```

DEBUG を False に設定し、ALLOWED_HOSTS に正確なドメイン名を指定することで、不正なホストヘッダー攻撃からもアプリケーションを保護できます。

### S3. シークレットや設定情報をハードコーディングする

#### リスク
API キーやデータベース認証情報などを settings.py に直接記述すると、セキュリティリスクが高まります。コードリポジトリが何らかの形で露出した場合、これらの機密情報も同時に漏洩してしまいます。

ハードコードされた機密情報は、公開リポジトリの不注意なプッシュなどを通じて漏洩する可能性があります。一度漏洩すると、関連するすべてのサービスが危険にさらされます。


#### 解決策
環境変数を使用して機密情報を管理しましょう：

```python
import os
SECRET_KEY = os.getenv('DJANGO_SECRET_KEY')
```

### S4. セキュリティヘッダーの欠如

#### リスク
適切なセキュリティヘッダーが設定されていないと、XSS（クロスサイトスクリプティング）やクリックジャッキングなどの攻撃に対して脆弱になります。

セキュリティヘッダーは、ブラウザに対してWebアプリケーションがどのように扱われるべきかを指示する重要な防御層です。これらが欠如していると、クライアントサイドの攻撃に対する脆弱性が増大します。

#### 解決策
SecurityMiddleware を有効にし、以下の設定を行いましょう：

```python
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
```

:::note info
最新のDjango (少なくとも5.1以降)ではデフォルトで
```
SECURE_CONTENT_TYPE_NOSNIFF: True
X_FRAME_OPTIONS: 'DENY'
```
になっており、また`SECURE_CONTENT_TYPE_NOSNIFF`は廃止されています
「モダンブラウザはX-XSS-Protectionヘッダーをもはや尊重しない」というのが理由です
:::


### S5. 本番環境で runserver を使用する

#### リスク
Django の組み込みサーバー（runserver）は開発用であり、本番環境での使用には適していません。セキュリティが強化されておらず、多数の同時接続を適切に処理できないため、DDoS攻撃などに弱くなります。

`runserver`は単一スレッドで動作し、リクエストを順次処理します。そのため、DoS攻撃に非常に弱く、処理時間の長いリクエストが他のすべてのユーザーのレスポンスをブロックしてしまう可能性があります。

#### 解決策
Gunicorn や uWSGI などの WSGI サーバーを、Nginx や Traefik などのリバースプロキシの背後で使用しましょう：

```bash
gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
```

この構成により、不正なリクエストのフィルタリングや接続制限などのセキュリティ機能が追加され、アプリケーションの保護レベルが向上します。

## パフォーマンス最適化の検討

以上は必ず避けるべきセキュリティに関わる事項でした。

ここから紹介する項目はパフォーマンスや開発効率を向上させるための方法です。これらは必ずしも全て実装する必要はなく、プロジェクトの規模や要件に応じて導入を検討すればいいと私は考えています。

:::note info
Djangoの大きな利点の一つは、シンプルな構成でも迅速にデプロイできることにあります。

パフォーマンス最適化は「必要になったら対応する」アプローチでも問題ないでしょう。まずはシンプルな構成でアプリケーションをリリースし、ユーザーのフィードバックやアクセス状況を見ながら段階的に最適化を導入するのも有効な戦略です。
:::

### P1. 静的ファイルやメディアファイルを Django で配信する

#### 課題
Django の開発用サーバーは、本番環境での静的/メディアファイルの配信には適していません。パフォーマンスが低く非効率的であり、アプリケーションの応答性に影響します。

**1. Nginx や CDN を使用する（大規模アプリケーション向け）**

大規模なトラフィックを扱うアプリケーションでは、Nginx などのウェブサーバーや CDN を使用することが推奨されます。

Nginx の設定例：
```nginx
location /static/ {
    root /path/to/static;
}
location /media/ {
    root /path/to/media;
}
```

**2. クラウドストレージを利用する**

django-storages と組み合わせて Amazon S3 や Google Cloud Storage などのクラウドストレージサービスを利用する方法もあります。これは特に複数サーバーでの運用やスケーリングを考慮する場合に有効です。

いずれの方法でも、静的ファイルとメディアファイルの配信をDjangoアプリケーションから分離することで、アプリケーションのリソースを本来のビジネスロジック処理に集中させることができます。

:::note info
記事で言及されている上記以外にも、小規模なプロジェクトであれば[whitenoise](https://whitenoise.readthedocs.io/en/stable/django.html)も悪くない選択肢だと思います。
:::


:::note warn
**注意点**: メディアファイル（ユーザーがアップロードしたファイル）については、セキュリティ上の理由から別のドメインまたはストレージサービスで提供することをお勧めします。
:::

### P2. データベース選択と設定の検討

#### 課題
Django は初期設定で SQLite を使用しています。SQLite は優れたデータベースであり、多くの状況で十分な性能を発揮します。ただし、特定の条件下（高並行性、高書き込み負荷、水平スケーリングが必要な場合など）では制約があります。

#### 最適化方法
ご自身のプロジェクトのニーズに基づいて適切なデータベースを選択しましょう：

- **SQLite**: シンプルな構成、低～中程度のトラフィック、単一サーバー構成、主に読み取り操作が多いアプリケーションに適しています。コンテナ化環境で使用する場合は永続ストレージの設定に注意が必要です。

- **PostgreSQL/MySQL**: 高トラフィック、多数の同時書き込み、複雑なクエリ、水平スケーリングが必要な場合に検討します。

```python
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'myuser',
        'PASSWORD': 'mypassword',
        'HOST': 'localhost',
        'PORT': '5432',
    }
}
```

:::note info
元の記事ではSQLiteを避けるべきのような言及をしていましたが、SQLiteは多くの環境で使用されている堅牢なデータベースです。トラフィックが少ない〜中程度のWebアプリケーションであれば、SQLiteで対応できる場合もあります。
:::


:::note warn
ただしSQLiteを本番環境で使用する場合には注意点があります：
- 同時に多数の書き込みが発生するアプリケーションでは性能が低下します
- 複数のサーバーやコンテナからのアクセスには対応していません
- クラウド環境など一時的なファイルシステムを使う場合は、データベースファイルのバックアップを適切に設定する必要があります
:::

### P3. キャッシュ戦略がない

#### 課題
キャッシュを実装していないと、アプリケーションは毎回データベースにアクセスする必要があり、応答時間が遅くなります。特にトラフィックが多いサイトでは深刻なパフォーマンス問題を引き起こす可能性があります。



#### 最適化方法
Redis や Memcached などのキャッシュシステムを導入しましょう：

```python
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
    }
}
```

:::note info
キャッシュ戦略は、アプリケーションが実際に運用されパフォーマンスの問題が顕在化してから導入してもいいと思います。追加コストが必要になる場合もあるので、過度な最適化よりも、まずは基本的な機能を確実に実装することを優先したほうがいいでしょう。
:::

### P4. ロギングやモニタリングの欠如

#### 課題
ログがないと、本番環境で問題が発生した際に原因を特定することが非常に困難になります。また、システムの状態やパフォーマンスを継続的に監視できないため、潜在的な問題が顕在化するまで気づけません。

#### 最適化方法
Django のロギング設定を使用し、Sentry、Grafana、Prometheus などのツールと連携させましょう：

```python
LOGGING = {
    'version': 1,
    'handlers': {
        'file': {
            'level': 'ERROR',
            'class': 'logging.FileHandler',
            'filename': '/var/log/django_errors.log',
        },
    },
    'root': {
        'handlers': ['file'],
        'level': 'ERROR',
    },
}
```

効果的なロギングは問題解決の時間を大幅に短縮し、アプリケーションの信頼性向上に貢献します。特に重要なエンドポイントや処理については、より詳細なログを設定することをお勧めします。

:::note info
モニタリングについても、コストや規模を総合的に勘案して導入を検討すればよく、最初から必要なものではないと考えます。
:::

### P5. 手動デプロイ

#### 課題
手動でのデプロイはエラーが発生しやすく、一貫性を欠きます。手順の抜け漏れやミスが発生するリスクが高く、デプロイ時間も長くなりがちです。

#### 最適化方法
CI/CD パイプライン（GitHub Actions、GitLab CI、Jenkins など）を使用して、デプロイプロセスを自動化しましょう。

GitHub Action の例：

```yaml
name: Deploy Django
on: [push]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Deploy
        run: ./deploy.sh
```

自動化されたデプロイプロセスは、一貫性、再現性、信頼性を提供し、開発チームが他の重要なタスクに集中できるようになります。また、テストの自動実行も組み込むことで、品質保証プロセスも強化できます。

:::note info
小規模なプロジェクトや個人開発では、単純なデプロイスクリプトでも十分効果的です。完全なCI/CDパイプラインは素晴らしいですが、最初から複雑な自動化を導入する必要はありません。

まずは簡単なデプロイスクリプトから始めるのも良い方法です。例えば：

```bash
#!/bin/bash
# deploy.sh

set -e  # エラーが発生したら停止

echo "Pulling latest changes..."
git pull

echo "Installing dependencies..."
pip install -r requirements.txt

echo "Collecting static files..."
python manage.py collectstatic --noinput

echo "Running migrations..."
python manage.py migrate

echo "Restarting the application..."
# systemdを使用している場合
sudo systemctl restart myproject

echo "Deployment completed successfully!"
```

スクリプトとして保存しておくことで手順の標準化に役立ちます。
:::

## まとめ

Djangoの素晴らしいところはデータベースからフロントエンドまで一式揃ったウェブサイトが素早く作れることです。この利点を活かすために、セキュリティ上必ず押さえておくべきポイントだけ最初に対処しておき、パフォーマンスについては後からチューニングすればいいと思います。

この記事を通じて、Djangoでアイデアを素早く形にする体験のお役に立てれば嬉しいです