夜明け前の最も暗いとき

技術的なやったことをメモするブログ

Python + Djangoで動画共有サイトを作る Part1

趣味で作っている動画がいっぱいになってきました。複数PCから視聴やタグ検索できると便利だと思ったのでWebサーバで動く動画共有サイトPythonDjangoを作ります。

目的

Webサイトを製作するにあたって以下の項目を考慮します。

  • サムネイルを設定・表示できる
  • タグで検索できる

また、DjangoをつかってWebフレームワークの使い方を習得します。 環境は以下の通りです。

Djangoに関しては以下の公式ドキュメントが参考になりますので一読を勧めます。

docs.djangoproject.com

開発環境の準備

今回は仮想マシン上にLinuxサーバ(CentOS)を構築し、Webサーバを動かします。そのため、仮想環境としてVirtualBoxをインストールします。また、CentOSのインストールイメージもダウンロードします。 仮想マシンは以下のスペックを割り当てました。

  • CPU:2コア
  • メモリ:2GB
  • HDD:15GB
  • IDEプライマリ:CentOS ISOファイル
  • ネットワーク:ブリッジ

f:id:jianlan:20190719155335j:plain

ネットワークはNATのままではホストから接続することができないため、必ず変更します。設定が完了したら仮想マシンを立ち上げ、CentOSをインストールします。

f:id:jianlan:20190719160210j:plain

HDDは仮想マシン作成時にに割り当てた領域をすべて使用します。

f:id:jianlan:20190719160226j:plain

DHCPの有効なLANではネットワークアドレスが自動的に設定されます。必要に応じて固定IPを設定することも可能です。

f:id:jianlan:20190719160234j:plain

今回はIPアドレスを手動で192.168.12.9に設定しました。(開発PCも192.168.12.0/24のセグメントにあります)

インストール完了後、再起動するとCentOSが起動します。CentOSはデフォルトでSSHサーバが有効なため、インストール中に設定したIPアドレスに対してSSHでログインが可能です。ゲストにログインしたらソフトウェアを最新の状態にします。*1

# yum check-update
# yum update

続いて、必要なソフトウェアをダウンロードします。Python3はデフォルトのリポジトリに存在しないため、IUS Community Projectのリポジトリを追加します。

# yum install https://centos7.iuscommunity.org/ius-release.rpm

Python3をインストールします。

# yum install gcc python36 python36-devel python36-pip

pipを使って今回使用するDjangoをインストールします。

# pip3 install django

下記コマンドで正しくインストールできたか確認します。

$ python3 -m django --version
2.2.3
$

続いて、サムネイル作成に使う動画処理ソフトウェアのffmpegをインストールします。

# rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
# rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-1.el7.nux.noarch.rpm
# yum install ffmpeg

今回はデータベースとしてmysql (mariadb)を使用します。インストールした後デーモンとして起動します。

# yum install mariadb mariadb-devel mariadb-server
# systemctl start mariadb
# systemctl status mariadb
● mariadb.service - MariaDB database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disabled)
   Active: active (running) since Sat 2019-08-03 22:30:49 JST; 13s ago
  Process: 4715 ExecStartPost=/usr/libexec/mariadb-wait-ready $MAINPID (code=exited, status=0/SUCCESS)
  Process: 4636 ExecStartPre=/usr/libexec/mariadb-prepare-db-dir %n (code=exited, status=0/SUCCESS)
 Main PID: 4714 (mysqld_safe)
   CGroup: /system.slice/mariadb.service
           tq4714 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
           mq4876 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --log-error=/var/log/mariadb/mariadb.lo...


# systemctl enable mariadb
Created symlink from /etc/systemd/system/multi-user.target.wants/mariadb.service to /usr/lib/systemd/system/mariadb.service.
#

Pythonで連携するため必要なパッケージをインストールします。

# pip3 install mysqlclient ffmpeg-python

インストールが完了したらDjangoのプロジェクトを作成します。

$ django-admin startproject mysite
$

mysiteフォルダが生成されるので、移動します。

$ cd mysite/
$ ls
manage.py  mysite
$

中にはmanage.pyとmysiteフォルダができています。mysqlを使うのでデータベースの設定をします。

 $ vi mysite/settings.py

デフォルトでは下記のようにsqliteを使う設定になっています。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

mysqlを使うために次のように書き換えます。

DATABASES = {
    'default': {
         'ENGINE': 'django.db.backends.mysql',
         'NAME': 'django_db',
         'USER': 'django',
         'PASSWORD': 'django-password',
         'HOST': 'localhost',
    }
}

また、今回のように開発PCとサーバが別の場合は接続できるようにALLOWED_HOSTSに'*'を加えます。

ALLOWED_HOSTS = ['*']

外部からDjangoへの接続を許可しました。Djangoの時刻はデフォルトでUTCになっているので日本標準時にします。

TIME_ZONE = 'Asia/Tokyo'

次にデータベースを作成します。下記コマンドからデータベースへ接続します。

$ mysql -u root
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 5.5.60-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>

データベースを作成します。作成するときにはUTF-8文字コードとして指定します。

MariaDB [(none)]> CREATE DATABASE django_db CHARACTER SET utf8;

Djangoで使用するためのユーザを作成します。

MariaDB [(none)]> CREATE USER 'django'@'localhost' IDENTIFIED BY 'django-password';
MariaDB [(none)]> GRANT ALL PRIVILEGES ON django_db.* TO 'django'@'localhost';
MariaDB [(none)]> SHOW GRANTS FOR 'django'@'localhost';
+---------------------------------------------------------------------------------------------------------------+
| Grants for django@localhost                                                                                   |
+---------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'django'@'localhost' IDENTIFIED BY PASSWORD '*E783B4785753FC31264A3BE0AABA7790D0A6080A' |
| GRANT ALL PRIVILEGES ON `django_db`.* TO 'django'@'localhost'                                                 |
+---------------------------------------------------------------------------------------------------------------+

MariaDB [(none)]> 

正常にユーザが作成されていればquitで抜けます。 Djangoで建てるWebサーバに外部からアクセスするにはfirewallに設定を追加する必要があります。

$ sudo firewall-cmd --state
running
$ sudo firewall-cmd --get-active-zones
public
  interfaces: enp0s3
$ sudo firewall-cmd --info-zone=public
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: ssh dhcpv6-client
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

$ sudo firewall-cmd --add-port 8000/tcp --zone=public --permanent
success
$ sudo firewall-cmd --reload
success
$ sudo firewall-cmd --info-zone=public
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: ssh dhcpv6-client
  ports: 8000/tcp
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

firewallの許可ポートに8000/tcpを追加しました。それでは、DjnagoのWebサーバを起動します。

$ python3 manage.py runserver 0:8000
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

August 03, 2019 - 13:54:08
Django version 2.2.3, using settings 'mysite.settings'
Starting development server at http://0:8000/
Quit the server with CONTROL-C.

適切に設定されていればエラー無く実行されます。ブラウザからサーバへアクセスしてロケットのアニメーションが再生していればOKです。

f:id:jianlan:20190803234218j:plain

Djangoで動画共有サイトを作る Part2へ続きます。

 

トラブルシューティング

パッケージのインストールエラー

pipのパッケージインストールで下記エラーが発生した場合は、必要なソフトウェアやライブラリが無い可能性があります。

Installing collected packages: mysqlclient, future, ffmpeg-python
  Running setup.py install for mysqlclient ... error
    ERROR: Command errored out with exit status 1:
     command: /usr/bin/python3.6 -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-gb0n8i9b/mysqlclient/setup.py'"'"'; __file__='"'"'/tmp/pip-install-gb0n8i9b/mysqlclient/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-appckohy/install-record.txt --single-version-externally-managed --compile
         cwd: /tmp/pip-install-gb0n8i9b/mysqlclient/
    Complete output (28 lines):
    running install
    running build
    running build_py
    creating build
    creating build/lib.linux-x86_64-3.6
    creating build/lib.linux-x86_64-3.6/MySQLdb
    copying MySQLdb/__init__.py -> build/lib.linux-x86_64-3.6/MySQLdb
    copying MySQLdb/_exceptions.py -> build/lib.linux-x86_64-3.6/MySQLdb
    copying MySQLdb/compat.py -> build/lib.linux-x86_64-3.6/MySQLdb
    copying MySQLdb/connections.py -> build/lib.linux-x86_64-3.6/MySQLdb
    copying MySQLdb/converters.py -> build/lib.linux-x86_64-3.6/MySQLdb
    copying MySQLdb/cursors.py -> build/lib.linux-x86_64-3.6/MySQLdb
    copying MySQLdb/release.py -> build/lib.linux-x86_64-3.6/MySQLdb
    copying MySQLdb/times.py -> build/lib.linux-x86_64-3.6/MySQLdb
    creating build/lib.linux-x86_64-3.6/MySQLdb/constants
    copying MySQLdb/constants/__init__.py -> build/lib.linux-x86_64-3.6/MySQLdb/constants
    copying MySQLdb/constants/CLIENT.py -> build/lib.linux-x86_64-3.6/MySQLdb/constants
    copying MySQLdb/constants/CR.py -> build/lib.linux-x86_64-3.6/MySQLdb/constants
    copying MySQLdb/constants/ER.py -> build/lib.linux-x86_64-3.6/MySQLdb/constants
    copying MySQLdb/constants/FIELD_TYPE.py -> build/lib.linux-x86_64-3.6/MySQLdb/constants
    copying MySQLdb/constants/FLAG.py -> build/lib.linux-x86_64-3.6/MySQLdb/constants
    running build_ext
    building 'MySQLdb._mysql' extension
    creating build/temp.linux-x86_64-3.6
    creating build/temp.linux-x86_64-3.6/MySQLdb
    gcc -pthread -Wno-unused-result -Wsign-compare -DDYNAMIC_ANNOTATIONS_ENABLED=1 -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -Dversion_info=(1,4,2,'post',1) -D__version__=1.4.2.post1 -I/usr/include/mysql -I/usr/include/python3.6m -c MySQLdb/_mysql.c -o build/temp.linux-x86_64-3.6/MySQLdb/_mysql.o
    unable to execute 'gcc': No such file or directory
    error: command 'gcc' failed with exit status 1
    ----------------------------------------
ERROR: Command errored out with exit status 1: /usr/bin/python3.6 -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-gb0n8i9b/mysqlclient/setup.py'"'"'; __file__='"'"'/tmp/pip-install-gb0n8i9b/mysqlclient/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /tmp/pip-record-appckohy/install-record.txt --single-version-externally-managed --compile Check the logs for full command output.

この場合は'gcc': No such file or directoryとあるので

# yum install gcc

GCCをインストールします。

Djangoの実行に必要なパッケージが不足

データベースが適切に設定されていないと以下のようなエラーになります。

$ python3 manage.py runserver 0:8000
Watching for file changes with StatReloader
Exception in thread django-main-thread:
Traceback (most recent call last):
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/mysql/base.py", line 15, in <module>
    import MySQLdb as Database
ModuleNotFoundError: No module named 'MySQLdb'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib64/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib64/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib64/python3.6/site-packages/django/utils/autoreload.py", line 54, in wrapper
    fn(*args, **kwargs)
  File "/usr/local/lib64/python3.6/site-packages/django/core/management/commands/runserver.py", line 109, in inner_run
    autoreload.raise_last_exception()
  File "/usr/local/lib64/python3.6/site-packages/django/utils/autoreload.py", line 77, in raise_last_exception
    raise _exception[1]
  File "/usr/local/lib64/python3.6/site-packages/django/core/management/__init__.py", line 337, in execute
    autoreload.check_errors(django.setup)()
  File "/usr/local/lib64/python3.6/site-packages/django/utils/autoreload.py", line 54, in wrapper
    fn(*args, **kwargs)
  File "/usr/local/lib64/python3.6/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/usr/local/lib64/python3.6/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/usr/local/lib64/python3.6/site-packages/django/apps/config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/usr/local/lib64/python3.6/site-packages/django/contrib/auth/models.py", line 2, in <module>
    from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
  File "/usr/local/lib64/python3.6/site-packages/django/contrib/auth/base_user.py", line 47, in <module>
    class AbstractBaseUser(models.Model):
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/base.py", line 117, in __new__
    new_class.add_to_class('_meta', Options(meta, app_label))
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/base.py", line 321, in add_to_class
    value.contribute_to_class(cls, name)
  File "/usr/local/lib64/python3.6/site-packages/django/db/models/options.py", line 204, in contribute_to_class
    self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
  File "/usr/local/lib64/python3.6/site-packages/django/db/__init__.py", line 28, in __getattr__
    return getattr(connections[DEFAULT_DB_ALIAS], item)
  File "/usr/local/lib64/python3.6/site-packages/django/db/utils.py", line 201, in __getitem__
    backend = load_backend(db['ENGINE'])
  File "/usr/local/lib64/python3.6/site-packages/django/db/utils.py", line 110, in load_backend
    return import_module('%s.base' % backend_name)
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/mysql/base.py", line 20, in <module>
    ) from err
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.
Did you install mysqlclient?

このケースでは丁寧に必要な措置が書かれているのでmysqlclientをインストールします。

Databaseエラー

runserverを実行後に下記のようなエラーが発生します。

$ python3 manage.py runserver 0:8000
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
Exception in thread django-main-thread:
Traceback (most recent call last):
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/base/base.py", line 217, in ensure_connection
    self.connect()
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/base/base.py", line 195, in connect
    self.connection = self.get_new_connection(conn_params)
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/mysql/base.py", line 227, in get_new_connection
    return Database.connect(**conn_params)
  File "/usr/local/lib64/python3.6/site-packages/MySQLdb/__init__.py", line 84, in Connect
    return Connection(*args, **kwargs)
  File "/usr/local/lib64/python3.6/site-packages/MySQLdb/connections.py", line 164, in __init__
    super(Connection, self).__init__(*args, **kwargs2)
MySQLdb._exceptions.OperationalError: (1045, "Access denied for user 'django'@'localhost' (using password: YES)")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib64/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib64/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib64/python3.6/site-packages/django/utils/autoreload.py", line 54, in wrapper
    fn(*args, **kwargs)
  File "/usr/local/lib64/python3.6/site-packages/django/core/management/commands/runserver.py", line 120, in inner_run
    self.check_migrations()
  File "/usr/local/lib64/python3.6/site-packages/django/core/management/base.py", line 453, in check_migrations
    executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
  File "/usr/local/lib64/python3.6/site-packages/django/db/migrations/executor.py", line 18, in __init__
    self.loader = MigrationLoader(self.connection)
  File "/usr/local/lib64/python3.6/site-packages/django/db/migrations/loader.py", line 49, in __init__
    self.build_graph()
  File "/usr/local/lib64/python3.6/site-packages/django/db/migrations/loader.py", line 212, in build_graph
    self.applied_migrations = recorder.applied_migrations()
  File "/usr/local/lib64/python3.6/site-packages/django/db/migrations/recorder.py", line 73, in applied_migrations
    if self.has_table():
  File "/usr/local/lib64/python3.6/site-packages/django/db/migrations/recorder.py", line 56, in has_table
    return self.Migration._meta.db_table in self.connection.introspection.table_names(self.connection.cursor())
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/base/base.py", line 256, in cursor
    return self._cursor()
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/base/base.py", line 233, in _cursor
    self.ensure_connection()
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/base/base.py", line 217, in ensure_connection
    self.connect()
  File "/usr/local/lib64/python3.6/site-packages/django/db/utils.py", line 89, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/base/base.py", line 217, in ensure_connection
    self.connect()
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/base/base.py", line 195, in connect
    self.connection = self.get_new_connection(conn_params)
  File "/usr/local/lib64/python3.6/site-packages/django/db/backends/mysql/base.py", line 227, in get_new_connection
    return Database.connect(**conn_params)
  File "/usr/local/lib64/python3.6/site-packages/MySQLdb/__init__.py", line 84, in Connect
    return Connection(*args, **kwargs)
  File "/usr/local/lib64/python3.6/site-packages/MySQLdb/connections.py", line 164, in __init__
    super(Connection, self).__init__(*args, **kwargs2)
django.db.utils.OperationalError: (1045, "Access denied for user 'django'@'localhost' (using password: YES)")

ユーザが存在しないかパスワードが間違っているため、データベースに接続できないエラーです。設定ファイルを確認してください。

サーバにつながらない

runserverを実行した後にブラウザから接続できない場合は以下の通りに切り分けてください。 まず、ネットワーク上の別PCからpingを実行します。

$ ping 192.168.12.9 -c 4
PING 192.168.12.9 (192.168.12.9) 56(84) bytes of data.
64 bytes from 192.168.12.9: icmp_seq=1 ttl=62 time=1.43 ms
64 bytes from 192.168.12.9: icmp_seq=2 ttl=62 time=1.75 ms
64 bytes from 192.168.12.9: icmp_seq=3 ttl=62 time=1.88 ms
64 bytes from 192.168.12.9: icmp_seq=4 ttl=62 time=1.90 ms

--- 192.168.12.9 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 1.431/1.744/1.904/0.189 ms

サーバから応答がない場合はネットワークの設定ミスの可能性があります。 /etc/sysconfig/network-scripts/などの設定ファイルを確認してみてください。

ping応答がある場合は続けてポートスキャンをします。

$ nmap 192.168.12.9 -Pn

Starting Nmap 6.40 ( http://nmap.org ) at 2019-08-03 23:20 JST
Nmap scan report for 192.168.12.9
Host is up (0.89s latency).
Not shown: 998 filtered ports
PORT     STATE SERVICE
22/tcp   open  ssh
8000/tcp open  http-alt

Nmap done: 1 IP address (1 host up) scanned in 58.85 seconds

正しくサーバが起動しているときは8000が表示されます。 されない場合はDjangoを起動しているサーバへログインしてネットワーク状態を確認します。

$ ss -nl | grep 8000
tcp    LISTEN     0      10        *:8000                  *:*

LISTENが表示されているばあはファイアウォールの設定を見直してください。 されていない場合は、Djangoの設定を再度確認してください。

*1:コマンドは"#"がrootで実行、"$"が一般ユーザで実行を表しています。状況に応じてsu -もしくはsudoでrootとして実行する必要があります。