Djangoで、静的ファイルを別サーバーから配信する

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

Python - Django
2018年11月22日21:15に更新(約21日前)
2018年10月15日20:18に作成(約59日前)

旧ブログ移行記事です。

概要

CentOS7でDjangoを動かすシリーズシリーズの一つです。 今回は、Djangoでstatic・mediaなファイルを別サーバーから配信していきます。

サーバー1

Gunicorn等で動かしているDjangoアプリケーションがあるサーバーです。ファイルアップロードされるとSFTPを使ってサーバー2にファイルを置きにいくので、場合に寄ってはサーバー2にSSHで接続するための秘密鍵等も必要になります。 規模が大きいと、このサーバー1が何台もあるかもしれませんね。

サーバー2(media.narito.ninja)

静的ファイルを/var/www/...だとか/usr/share/nginx/...に置き、Nginx等のWebサーバーがそれらを配信させています。

ローカル環境(開発環境)

mediaファイルは一度設定すれば後はおしまいなのですが、staticファイルに関しては修正や追加の可能性を考える必要があります。つまり、staticファイルのデプロイも考えなければなりません。そこで、開発環境python manage.py collectstaticをするとサーバー2にそれらが送信される仕組みにします。これで修正や変更も簡単です。

mediaファイル

mediaファイルの流れとしては、サーバー1でファイルアップロード処理(DjangoのImageFieldやFileField)があると、サーバー2(media.narito.ninja)に接続してそれを保存するという流れになります。 そのためにdjango-storagesSFTPStorageを使いますが、内部でparamikoを使うので、インストールしておきましょう。サーバー1にログインしてインストールします。

sudo pip3.7 install django-storages
sudo pip3.7 install paramiko

paramikoのサンプル

paramikoを少し使ってみましょう。 サーバー2(media.narito.ninja)に接続できるかどうかを先に試しておきます。 SFTPなので、サーバー2にSSHで接続できるようにしておきましょう。

ユーザーが「hello」、ポートが「20201」、公開鍵認証でパスフレーズが「aiueo」、秘密鍵のパスはローカルの「~/.ssh/media/id_rsa」という例です。sudoコマンドをつけて実行する際は、os.environ['HOME']の値に注意してください。迷ったら、/home/user/.ssh/media/id_rsa のように直接書いても良いかもしれません。 このサンプルコードでエラーがでなければ、恐らくSFTPStorageも問題なく利用できます。

import os
import paramiko

client = paramiko.SSHClient()
client._policy = paramiko.WarningPolicy()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# ~/.ssh/media/id_rsa
key_path = os.path.join(os.environ['HOME'], '.ssh', 'media', 'id_rsa')

cfg = {
        "hostname": 'media.narito.ninja',
        "username": 'hello',
        "port": 20201,
        "pkey": paramiko.RSAKey.from_private_key_file(key_path, 'aiueo'),
}


client.connect(**cfg)

~/.ssh/configファイルの設定を利用するなら、以下のようにしましょう。ちょっと長くなりますね。

import os
import paramiko

client = paramiko.SSHClient()
client._policy = paramiko.WarningPolicy()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 以下、~/.ssh/config ファイルを読み込む
ssh_config = paramiko.SSHConfig()
user_config_file = os.path.expanduser(
    os.path.join(os.environ['HOME'], '.ssh', 'config')
)
with open(user_config_file, 'r', encoding='utf-8') as file:
    ssh_config.parse(file)

# Hotname media の設定を読み込む
user_config = ssh_config.lookup('media')
"""user_configは以下のような辞書になる
{
    'hostname': 'media.narito.ninja',
    'user': 'hello',
    'port': '20201',
    'identitiesonly': 'yes',
    'identityfile': ['/home/hello/.ssh/media/id_rsa']
}

"""
key_path = user_config['identityfile'][0]
cfg = { 
    "hostname": user_config["hostname"],
    "username": user_config["user"],
    "port": int(user_config["port"]),
    "pkey": paramiko.RSAKey.from_private_key_file(key_path, 'aiueo'),
}

client.connect(**cfg)

settings.py

サーバー1のsettings.pyに、以下のように書きます。SFTPStorageを使う場合の各変数の名前は決まっているので、安易に消したりすると動かなくなります。

# 静的ファイル配信サーバーが違うドメイン(サブドメイン)になる場合はURLをフルで書く
MEDIA_URL = 'https://media.narito.ninja/media/'
DEFAULT_FILE_STORAGE = 'storages.backends.sftpstorage.SFTPStorage'
SFTP_STORAGE_HOST = 'media.narito.ninja'
SFTP_STORAGE_ROOT = '/var/www/media/'  # ドキュメントを見ると、最後にスラッシュをつけるのを忘れないで、とのこと
import paramiko
key_path = os.path.join(os.environ['HOME'], '.ssh', 'media', 'id_rsa')
# hostnameキーは必要なし。SFTPStorage内部でSFTP_STORAGE_HOSTをhostnameとして使います。
SFTP_STORAGE_PARAMS = {
        "username": 'hello',
        "port": 20201,
        "pkey": paramiko.RSAKey.from_private_key_file(key_path, 'aiueo'),
}

これで、サーバー1でアップロードされたファイルは全てサーバー2(media.narito.ninja)に保存され、メディアファイルのURLは全てhttps://media.narito.ninja/media/1.png のようになるので、サーバー2から配信されます。

staticファイル

流れとしては、ローカル環境(開発環境)でcollectstatic をしたあと、できあがったstaticディレクトリをサーバー2(media.narito.ninja)に送信します。

settings.py

サーバー1のsettings.pyを修正します。STATIC_URLを以下のようにすると、テンプレート内の{% static %}のパスが自動で置き換わります。これで、staticなファイルは全てhttps://media.narito.ninja/static/app/base.css のようになり、サーバー2から配信されます。

STATIC_URL = 'https://media.narito.ninja/static/'

staticファイルのデプロイのため、ローカル環境(開発環境)のsettings.pyも修正します。

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

fabfile.py

Fabricを使って、collectstatic〜サーバー2(media.narito.ninja)へ送信を行います。なので、fabfile.pyをローカル環境(開発環境)に置きます。 Fabricの使い方は、DjangoアプリケーションをFabric3を使ってデプロイする(pipでアップデート)を見てください。簡単ですよ。

以下は1例です。venvで仮想環境化しており、pythonのパス等がちょっと面倒になっています。

import os
from fabric.api import local, env
from fabric.contrib import project

# ~/.ssh/configにある、「Hotname media」 の設定を使う
env.hosts = ['media']
env.use_ssh_config = True

# 仮想環境があるディレクトリ
env.directory = '/home/narito/myblog/'

# venvの中にあるPythonを使う!
env.python = os.path.join(env.directory, 'bin', 'python')

# Djangoプロジェクト本体はここ
env.project = os.path.join(env.directory, 'project')
env.managepy = os.path.join(env.project, 'manage.py')

# ローカルの、できあがったstaticの場所。STATIC_ROOTと同じ場所
env.local_static_root = os.path.join(env.project, 'static')

# リモートの、staticディレクトリを置く場所
env.remote_static_root = '/var/www'


def collectstatic():
    # /home/narito/myblog/bin/python /home/narito/myblog/project/manage.py collectstatic のようになる
    collectstatic_cmd = '{} {} collectstatic'.format(env.python, env.managepy)
    local(collectstatic_cmd)  # ローカルでcollectstaticの実行

    # これでフォルダの内容を同期してくれる
    project.rsync_project(
        remote_dir=env.remote_static_root,
        local_dir=env.local_static_root,
        delete=True,
    )

あとは、以下のコマンドを実行するだけです。

fab collectstatic

さくらVPSで、サブドメインを別マシンに割り当て

メインのドメインをhttps..でアクセスできるようにしていると、配信用サーバーもhttps...に対応させないと面倒なことになります。 なので、対応を忘れないようにしましょう。

さくらVPSを利用してLet's Encryptを使う方は、IPアドレスやさくらVPS初期ドメイン(tk2-222-22222.sakura.ne.jpみたいなの)ではSSL対応できないので、既に持っているドメインのサブドメインを別のマシンに割り当て、それをSSL対応すると簡単かと思います。

さくらVPSならば、「ゾーンの編集」から簡単に済ませることができます。 ネームサーバとゾーンの管理

@のAにあるIPアドレスと、mediaのAにあるIPアドレスを別にするだけでできます。 ゾーンの設定

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

記事にコメントする