MQTTで集めたデータを見える化する環境を作ります。できるだけお手軽に済ませたいので、Pythonを中心に構築します。
WebフレームワークはFlaskを使用、MQTTのsubscriberもPythonで記述します。
自動アップデートが上手く行かない?パッケージがある
その前に、自動アップデートがかかるはずだけど、なぜかかかってないのがあるようなので、解決しておきます。
ubuntu@i-xxxxxxx:~$ sudo apt-get upgrade Reading package lists... Done Building dependency tree Reading state information... Done Calculating upgrade... Done The following packages have been kept back: linux-headers-generic linux-headers-virtual linux-image-virtual linux-virtual netplan.io 0 upgraded, 0 newly installed, 0 to remove and 5 not upgraded.
ぐぐってみると、これらのアップデートされないでいるものは、一旦手動でインストールしてやると良いようです。ですので、
ubuntu@i-xxxxxxxx:~$ sudo apt-get install linux-headers-generic linux-headers-virtual linux-image-virtual linux-virtual netplan.io
としてインストールしてやります。そうすると、
ubuntu@i-xxxxxxxx:~$ sudo apt-get update ubuntu@i-xxxxxxxx:~$ sudo apt-get upgrade Reading package lists... Done Building dependency tree Reading state information... Done Calculating upgrade... Done 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
ということで、無事に未アップグレードのものはなくなりました。
PythonフレームワークのFlaskをインストール
python3のバージョンと、pipのインストール状況を確認します。
ubuntu@i-xxxxxxxx:~$ python3 -V Python 3.6.9 ubuntu@i-xxxxxxxx:~$ pip3 Command 'pip3' not found, but can be installed with: sudo apt install python3-pip
pip3はインストールされていないようなのでインストールします。
ubuntu@i-xxxxxxxx:~$ sudo apt-get install python3-pip
さらに、 paho-mqtt と Flask と uwsgi をインストールします。
ubuntu@i-xxxxxxxx:~$ sudo pip3 install --upgrade pip The directory '/home/ubuntu/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag. The directory '/home/ubuntu/.cache/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag. Collecting pip Downloading https://files.pythonhosted.org/packages/36/74/38c2410d688ac7b48afa07d413674afc1f903c1c1f854de51dc8eb2367a5/pip-20.2-py2.py3-none-any.whl (1.5MB) 100% |████████████████████████████████| 1.5MB 373kB/s Installing collected packages: pip Found existing installation: pip 9.0.1 Not uninstalling pip at /usr/lib/python3/dist-packages, outside environment /usr Successfully installed pip-20.2 ubuntu@i-xxxxxxxx:~$ sudo pip3 install paho-mqtt Flask uwsgi WARNING: The directory '/home/ubuntu/.cache/pip' or its parent directory is not owned or is not writable by the current user. The cache has been disabled. Check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag. Collecting paho-mqtt Downloading paho-mqtt-1.5.0.tar.gz (99 kB) |████████████████████████████████| 99 kB 8.5 MB/s Collecting Flask Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB) |████████████████████████████████| 94 kB 10.2 MB/s Collecting uwsgi Downloading uWSGI-2.0.19.1.tar.gz (803 kB) |████████████████████████████████| 803 kB 14.3 MB/s Collecting Jinja2>=2.10.1 Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB) |████████████████████████████████| 125 kB 14.6 MB/s Collecting Werkzeug>=0.15 Downloading Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB) |████████████████████████████████| 298 kB 14.5 MB/s Requirement already satisfied: click>=5.1 in /usr/lib/python3/dist-packages (from Flask) (6.7) Collecting itsdangerous>=0.24 Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB) Requirement already satisfied: MarkupSafe>=0.23 in /usr/lib/python3/dist-packages (from Jinja2>=2.10.1->Flask) (1.0) Building wheels for collected packages: paho-mqtt, uwsgi Building wheel for paho-mqtt (setup.py) ... done Created wheel for paho-mqtt: filename=paho_mqtt-1.5.0-py3-none-any.whl size=74652 sha256=394e638f10ab0cb8d0d512c52f217ed35563786aaa228c7847b7c54475059e77 Stored in directory: /tmp/pip-ephem-wheel-cache-y6u_yal3/wheels/b1/1e/85/172dc41f02d413a702530402d1fe56651bd662e72de5cec4af Building wheel for uwsgi (setup.py) ... done Created wheel for uwsgi: filename=uWSGI-2.0.19.1-cp36-cp36m-linux_x86_64.whl size=513476 sha256=07ef6a9ab1113760d943945f2b0c71d207af6a6069c160715f062239783341c6 Stored in directory: /tmp/pip-ephem-wheel-cache-y6u_yal3/wheels/3a/7a/c1/492f02e0cde1e39f8b75c79dc5ef3b7e2c93b02e3a7eaabe0c Successfully built paho-mqtt uwsgi Installing collected packages: paho-mqtt, Jinja2, Werkzeug, itsdangerous, Flask, uwsgi Attempting uninstall: Jinja2 Found existing installation: Jinja2 2.10 Uninstalling Jinja2-2.10: Successfully uninstalled Jinja2-2.10 Successfully installed Flask-1.1.2 Jinja2-2.11.2 Werkzeug-1.0.1 itsdangerous-1.1.0 paho-mqtt-1.5.0 uwsgi-2.0.19.1 ubuntu@i-xxxxxxxx:~$
Flaskのスクリプトを配置
Flaskをインストールしていると、Flask自体だけではなく、WSGI(Web Service Gateway Interface)とかでてきたり、nginxとの絡みが出てきたりでよくわかりません。いろいろぐぐった中で、各モジュールの説明でわかりやすいと思ったのはこちらになります。
まあ、結局よくわかってないんですが、とにかくインストールしていきます。
/var/www の下には以下の構成でスクリプトを配置します。Flaskアプリは app ディレクトリの下で動かします。
ブザウザでアクセスした際に、以下のようになることを目指します。
//example.com/ 通常のWebコンテンツ //example.com/app/ Flaskアプリのトップ //example.com/app/appA/ アプリA //example.com/app/appB/ アプリB //example.com/app/appC/ アプリC
このように複数のアプリを分割して置く場合には、BluePrintを使って分割するのが一般的なようですので、それにあわせてディレクトリ構成は以下のようにします。
/var/www/html/ 通常のWebコンテンツ /var/www/app/ FlaskアプリのPythonコード /var/www/app/appA/ FlaskアプリのアプリAのPythonコード /var/www/app/appB/ FlaskアプリのアプリBのPythonコード /var/www/app/appC/ FlaskアプリのアプリCのPythonコード /var/www/app/templetes Flaskアプリのトップのテンプレートディレクトリ /var/www/app/templetes/appA/ FlaskアプリのアプリAのテンプレートディレクトリ /var/www/app/templetes/appB/ FlaskアプリのアプリBのテンプレートディレクトリ /var/www/app/templetes/appC/ FlaskアプリのアプリCのテンプレートディレクトリ
各アプリのディレクトリ(/var/www/app/appA/ など)には以下のものを置くことにしました。
①MQTTでデータを受信するPythonスクリプト(appA_sub.pyなど。これは /etc/systemd/system/appA.service を作って、systemdで起動させます)
②そのスクリプトからのデータもここに一時保存します
③uWSGIでデータを受信するためのソケット
④表示のためのFlaskのアプリ(appA.py)
⑤uWSGIの設定ファイル(appA.ini)。この appA.ini へは /etc/uwsgi/vassals/appA.ini からシンボリックリンクを貼って uwsgi Emperorでまとめて起動させます。
# ln -sf /var/www/app/appA/appA.ini /etc/uwsgi/vassals/appA.ini
データ受信するスクリプトを用意
MQTTで受信するPythonスクリプトを起動します。
ubuntu@i-xxxxxxxx:~$ cd /var/www/app ubuntu@i-xxxxxxxx:/var/www/app$ sudo -u www-data python3 sub.py
表示するためのスクリプト(Flaskアプリ)を用意
まず、上位ディレクトリのためのコードです。
# -*- coding:utf-8 -*- from flask import Flask, render_template app = Flask(__name__) # for BluePrint from appA.appA import appA ★ app.register_blueprint(appA) ★ @app.route('/app/') def data(): : : html = render_template('index.html', : : ) return html
次にサブディレクトリのためのコードです。
# -*- coding:utf-8 -*- from flask import Flask, render_template from flask import Blueprint # for BluePrint # for BluePrint appA = Blueprint('appA',__name__, url_prefix='/app') ★ @appA.route('/appA') # for BluePrint ★ def data(): : : with open('appA/appA.xxxx',mode='rb') as f: ★ ..... html = render_template('appA/appA.html', # for BluePrint : : ) return html
表示するためのスクリプト(Flaskアプリ)を呼び出すための設定
/etc/nginx/site-available/default を編集して、以下の内容を追記します。これで、https://FQDN/app/ にアクセスしたときには uwsgi 経由で Flask にリバースプロキシされます。(で表現はいいのかな?)
server { : location ~ ^/app/(.*)$ { include uwsgi_params; uwsgi_pass unix:/var/www/app/web.sock; } : }
nginx の設定ファイルの構文チェックを行ってから再起動して設定ファイルを反映させます。
root@i-xxxxxxxx:~# nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful root@i-xxxxxxxx:~# service nginx restart
uwsgi の設定ファイルを /var/www/app/uwsgi.ini として作成します。
設定ファイルの記述方法はこちらが参考になりました。
[uwsgi] ## uwsgi の設定ファイル # ln -sf /var/www/app/app.ini /etc/uwsgi/vassals/app.ini # としてリンクを貼って使用する。 chdir = /var/www/app/ wsgi-file = /var/www/app/app.py callable = app touch-reload = /var/www/app/app.py socket = /var/www/app/web.sock chmod-socket = 666 logto = /var/log/uwsgi/app.log enable-threads = true process = 100 thread = 2 vacuum = true
サブディレクトリ用(appA.ini)はこんな感じです。
[uwsgi] # ln -sf /var/www/app/appA/appA.ini /etc/uwsgi/vassals/appA.ini # としてリンクを貼って使用する。 ## %n はこのファイルの拡張子なしの部分に置き換えられる # アプリケーションのベースディレクトリ chdir = /var/www/app/%n # wsgi-file = /var/www/app/%n/%n.py callable = app # 以下のファイル(ソースファイル)が変更されたらuWSGIをリロードする touch-reload = /var/www/app/%n/%n.py #socket socket = /var/www/app/%n/web.sock chmod-socket = 666 #log logto = /var/log/uwsgi/%n.log # enable-threads = true process = 100 thread = 2 vacuum = true
ログフォルダを作成します。
ubuntu@i-xxxxxxxx:/var/www/app$ sudo mkdir -p /var/log/uwsgi ubuntu@i-xxxxxxxx:/var/www/app$ sudo chown -R www-data.www-data /var/log/uwsgi
uwsgiを起動します。
ubuntu@i-xxxxxxxx:~$ cd /var/www/app ubuntu@i-xxxxxxxx:/var/www/app$ sudo -u www-data uwsgi uwsgi.ini
ブラウザで https://example.com/app/ にアクセスして動作確認します。なお、この時点ではどこからもMQTTでデータが送られてきていないので、データ自体は表示されません。
関連するファイルのオーナーをすべて www-data.www-data に変更しておきます。
root@i-xxxxxxxx:/var/www# cd /var/www root@i-xxxxxxxx:/var/www# chown -R www-data.www-data *
自動起動の設定
再起動時に自動起動するように設定します。
①uwsgiの自動起動
uwsgi Emperor を設定します。uwsgi Emperor は複数の uwsgi をまとめて起動する仕組みのようです。こちらのサイトを参考にさせていただきました。
uwsgiの設定ファイルを集めます。実際には、/etc/uwsgi/vassals/ からシンボリックリンクを張る形です。
ubuntu@i-xxxxxxxx:~$ sudo mkdir -p /etc/uwsgi/vassals ubuntu@i-xxxxxxxx:~$ sudo ln -sf /var/www/app/uwsgi.ini /etc/uwsgi/vassals/app.ini ubuntu@i-xxxxxxxx:~$ sudo ln -sf /var/www/app/app/appA.ini /etc/uwsgi/vassals/appA.ini
最初のシンボリックリンクは「ln -sf /var/www/(アプリ名)/uwsgi.ini /etc/uwsgi/vassals/(アプリ名).ini」の形で張ります。
Emperorの設定ファイルを /etc/uwsgi/emperor.ini に作成します。
[uwsgi] emperor = /etc/uwsgi/vassals uid = www-data gid = www-data logto = /var/log/uwsgi/uwsgi.log touch-logreopen = /var/log/uwsgi/touch-logreopen master = true vacuum = true ignore-sigpipe = true ignore-write-errors = true disable-write-exception = true
/etc/systemd/system/uwsgi.service を作成して、systemdで起動停止ができるようにします。
[Unit] Description=uWSGI Emperor After=syslog.target [Service] ExecStartPre=-/bin/mkdir -p /var/log/uwsgi ExecStartPre=-/bin/chown -R www-data.www-data /var/log/uwsgi ExecStart=/usr/local/bin/uwsgi --ini /etc/uwsgi/emperor.ini RuntimeDirectory=uwsgi Restart=always KillSignal=SIGQUIT Type=notify StandardError=syslog NotifyAccess=all [Install] WantedBy=multi-user.target
サービスとして起動します。
ubuntu@i-xxxxxxxx:/var/www/app$ sudo systemctl enable uwsgi.service ubuntu@i-xxxxxxxx:/var/www/app$ sudo systemctl start uwsgi.service
②MQTTのsubscriberの自動起動
こちらもsystemdを使って自動起動させます。
スクリプトは /var/www/app/sub.py にあるので、これを起動時に自動実行させるようにします。
/etc/systemd/system/app.service として設定ファイルを作成します。
[Unit] Description=https://example.com/app/ [Service] WorkingDirectory=/var/www/app ExecStart=/usr/bin/python3 /var/www/app/sub.py User=www-data Group=www-data [Install] WantedBy=multi-user.target
同様にサブディレクトリ用を/etc/systemd/system/appA.service として設定ファイルを作成します。
[Unit] Description=https://example.com/app/appA [Service] WorkingDirectory=/var/www/app/appA ExecStart=/usr/bin/python3 /var/www/app/appA/appA_sub.py User=www-data Group=www-data [Install] WantedBy=multi-user.target
サービスとして起動して、起動状態を確認します。
ubuntu@i-xxxxxxxx:~$ sudo systemctl enable app ubuntu@i-xxxxxxxx:~$ sudo systemctl start app ubuntu@i-xxxxxxxx:~$ sudo systemctl status app ubuntu@i-xxxxxxxx:~$ sudo systemctl enable appA ubuntu@i-xxxxxxxx:~$ sudo systemctl start appA ubuntu@i-xxxxxxxx:~$ sudo systemctl status appA
これで残るはMQTTでデータを送信する側の作成となります。
BASIC認証をかける
受信したデータは公開するものが望ましくないものもあるかもしれません。ですので、受信したデータごとにBASIC認証ができるようにいしてみます。今回はモジュールとして、Flask-HTTPAuthを使ってみることにしました。こちらのドキュメントを参考にしながら進めていきます。
まず、ライブラリをインストールします。
ubuntu@i-xxxxxxxx:/var/www/app$ sudo pip3 install Flask-HTTPAuth WARNING: The directory '/home/ubuntu/.cache/pip' or its parent directory is not owned or is not writable by the current user. The cache has been disabled. Check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag. Collecting Flask-HTTPAuth Downloading Flask_HTTPAuth-4.1.0-py2.py3-none-any.whl (5.8 kB) Requirement already satisfied: Flask in /usr/local/lib/python3.6/dist-packages (from Flask-HTTPAuth) (1.1.2) Requirement already satisfied: itsdangerous>=0.24 in /usr/local/lib/python3.6/dist-packages (from Flask->Flask-HTTPAuth) (1.1.0) Requirement already satisfied: Jinja2>=2.10.1 in /usr/local/lib/python3.6/dist-packages (from Flask->Flask-HTTPAuth) (2.11.2) Requirement already satisfied: click>=5.1 in /usr/lib/python3/dist-packages (from Flask->Flask-HTTPAuth) (6.7) Requirement already satisfied: Werkzeug>=0.15 in /usr/local/lib/python3.6/dist-packages (from Flask->Flask-HTTPAuth) (1.0.1) Requirement already satisfied: MarkupSafe>=0.23 in /usr/lib/python3/dist-packages (from Jinja2>=2.10.1->Flask->Flask-HTTPAuth) (1.0) Installing collected packages: Flask-HTTPAuth Successfully installed Flask-HTTPAuth-4.1.0 ubuntu@i-xxxxxxxx:/var/www/app$
www.py を以下のように修正(追加)します。本来は、パスワードリストの右側の部分はハッシュ値を直接記述すべきでしょう。
from flask_httpauth import HTTPBasicAuth ← 追加 from werkzeug.security import generate_password_hash, check_password_hash ← 追加 app = Flask(__name__) auth = HTTPBasicAuth() ← 追加 users = { ← パスワードのリストを追加 "ユーザー名": generate_password_hash("パスワード") } @auth.verify_password ← パスワードのチェックを行う def verify_password(username, password): if username in users and \ check_password_hash(users.get(username), password) : return username @app.route('/app/') @auth.login_required ← 追加(認証を入れたいところだけ) def data(): : :(以下略)
以上の修正を行い、uwgsi を再起動するとBASIC認証がかかります。