WebARENAでUbuntuサーバを立てる(3)

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認証がかかります。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)