こうしーん

おまとめ三行

こんなんどうだろう的なお話
わずか二行のコードで実装できる
本番環境でやるのは推奨できないかも
CakePHPを……と言うかPHPでウェブサイトを動かしている場合、サイトに変更を加えたければコードを修正してファイルを更新するだけで即座に反映されます。でもDjangoの場合はそうはいかないです。

以前テスト環境の構築の際にやったように、コマンドを叩いてサーバーを起動している場合は更新は即座に反映されます。

$ python3 manage.py runserver 0:80

でもmod_wsgiを使用してApacheで起動している場合は、実際やってみると分かるんですが、ファイルを上書きしてから画面を更新しても特に何も変化がない。うっかり間違ってファイルを更新しちゃっても大丈夫って意味では安心なのかもしれませんが、それでもPHPでの開発に慣れきってる人間からすると、更新の反映に一手間かかるのはちょっちわずらわしい。

じゃあどうすれば更新されるかって話なんですが、パターンはいくつかあります。

一番分かりやすいのはApacheの再起動です。

$ systemctl restart httpd.service

これで更新は反映されます。でもたまにしか更新しないならともかく、ちょくちょくファイルを書き換えるような場合、そのたびにApacheを再起動するのはめんどいですよね。できれば自動的に更新が反映されるようにしたい。

今回はそのやり方について、こんなんどうだろう的なお話をしたいと思います。



更新の条件

そもそもApacheを再起動せずとも更新が反映されるのはどういう条件の時なのか。

Djangoをインストールしてプロジェクトを作成するとフォルダの中に「wsgi.py」というファイルができますが、mod_wsgiを使用している場合(他の場合も同様かもしれないけど)、このwsgi.pyが更新されると全体の更新も反映される仕組みになっているようです。この更新ってのはファイルのタイムスタンプさえ上書きできれば良いので、wsgi.pyの中身を書き換える必要はありません。

ってことは何らかの方法でこのwsgi.pyを更新できれば良いわけですね。簡単なのはFTPソフトでwsgi.pyファイルを開いて、何の変更も加えずに保存するやり方。これでファイルのタイムスタンプが上書きされるので更新が反映されます。

あるいはSSHでサーバーに接続し、touchコマンドを実行しても良いです。

$ touch /var/normnois/normnois/wsgi.py

例えばこんな感じ。touchはファイルのタイムスタンプを更新するコマンドです。だからFTPでファイルを開いて何もせずに保存した場合と同様の結果が得られる。

しかしこれらはいずれも毎回手動で行わなきゃいけないので、手間的にはApacheの再起動とそんなに変わらないですね。サーバーを止めずに更新できるかどうかくらいの違いしかない。しかもApacheの再起動なんて数秒程度ですからね。

これを何とか自動化する手段はないだろうか。



uwsgiを使う

自動化したいと考えている人はやはりたくさんいるようで、検索するとすぐにいくつか方法が見つかります。

そのうちの一つが「uswgi」というモジュールを使う方法。uwsgiをインストールしてこちょこちょっと設定すると自動更新が可能になるようです。でも僕は実際に試してないので、すみませんがここでは実践している方の紹介だけ。

django + uwsgi で AutoReload

iniファイルを作成したりする必要があるみたいですが、その辺りの作業に慣れてる人ならそこまで大変ではないかもしれないです。



inotifywaitを使う

「inotifywait」というコマンドを使うとファイルの更新などを監視することができます。これを使えば任意のファイル更新を検知して、そのタイミングで何らかの処理を実行することができる。先ほどのtouchコマンドを実行したりとかね。

これもすみませんが僕自身は試してないので、やり方だけ紹介します。

[Linux] inotifywaitを使ってファイル更新時に任意のコマンドを実行する

サーバーにinotify-toolsというモジュールをインストールしてあれやこれやを設定しなければなりませんが、これもサーバーに触り慣れてる人ならそんなに難しくはないのかも。



ひたすらwsgi.pyを更新し続ける

上記のやり方は検索すれば情報がいっぱい出てくるのですが、あえてこれらを避けてもっと簡単にwsgi.pyを更新する方法もあるんじゃないかと思いまして……こんなやり方を考えてみました。

#settings.pyに以下の二行を書く
import subprocess
subprocess.run(['touch', '/var/www/normnois/normnois/wsgi.py'])

「subprocess.run()」っていうのはPHPで言うexec()みたいな感じです。pythonの中からコマンドを実行できます。

ようはsettings.pyが読み込まれるたびにtouchコマンドを実行しちゃおうというやり方。

settings.pyはwsgi.pyが更新されると読み込まれるわけですから、このファイルの中にwsgi.pyの更新処理を入れておくことにより「settings.pyが読み込まれる → wsgi.pyが更新される → 次回もsettings.pyが読み込まれる → wsgi.pyが更新される」というループが延々と発生し続けるのではないかと。

一応自分で試した限りでは、これでwsgi.pyは毎回更新されてます。ファイルの書き込み権限がないようであれば、スーパーユーザーで実行しても良い。

subprocess.run(['sudo', 'touch', '/var/www/normnois/normnois/wsgi.py'])

settings.pyじゃなくて__init__.pyとかに書いても同様の動きになると思いますし、ミドルウェアにこの機能を実装しても同じことができると思うんですが、僕自身がまだミドルウェアの使い方をよく理解していないので今回はパスします。

何にせよこれなら余計なツールのインストールも必要ないし、わずか二行のコードで実装できるのでお手軽簡単楽勝イーズィーです。






uwsgiやinotifywaitのやり方は検索するとたくさん情報が出てくるんですが、最後に書いたsettings.pyにtouchコマンドを書いちゃうというやり方を実践している人はあまり見かけないんですよね……もしかしたら何かまずい理由があるのかもしれないですね。まあDjangoはサーバー起動時やwsgi.pyの更新時にpythonファイルをキャッシュしてると思うんですが、毎回touchコマンドを実行することでキャッシュを無効化しているような状態になるから、その分処理が重くなってサーバーの負荷が上がり、ページの読み込みにも毎回ちょっと時間がかかる(1、2秒程度のロスですが)というデメリットがあります。だから本番環境でやるのは推奨できないかもですね。uwsgiとかinotifywaitを使ったやり方はたぶん本番環境にも対応できるから、ちゃんと実装するならそっちを使えってことなんでしょう。

とはいえ僕の場合、開発環境では毎日のようにファイルを更新しますが、本番環境の方は月に一度更新作業をするかどうかという程度なので、開発環境にだけ今回のやり方を適用して本番環境は手動でwsgi.pyを更新するとかでも今のところは事足りるので、とりあえずはこの方法で開発に勤しむことにします。