Azure VM の Ubuntu 20.04(LTS) で Python3 と MongoDB を利用する（ホロライブの動画配信予定を収集 その２の続き）

## はじめに

前回（[https://qiita.com/kerobot/items/fa728f665d559b969e1c]()）は、ホロライブの配信予定や動画情報をホロジュールと Youtube から収集した情報を MongoDB に登録できるようにしました。
今回は、一通りの仕組みを Azure VM の Ubuntu に配置して定期的に実行されるようにしてみます。

これまで Windows 10 で構築していた仕組み、Poetry + pyenv + Python3 + WebDriver + MongoDB を Azure VM の Ubuntu に導入して、cron で定期的に実行します。

## 最終的にやりたいこと

ここ最近、毎日のように閲覧している バーチャル YouTuber プロダクション「ホロライブ」などの、動画配信予定を定期的にデータベースに格納し、それを Web API で参照して閲覧する Android アプリを作りたい。

1. 動画配信予定を収集するためのプログラムの作成（完了）
2. 収集した動画配信予定を格納するためのデータベースの作成と収集の自動化（前回＋自動化が今回）
3. 格納した動画配信予定を参照するための Web API の作成
4. Web API を参照して動画配信予定を閲覧する Android アプリの作成

## やったこと

今回は、やりたいこと 2 の「収集した動画配信予定を格納するためのデータベースの作成と収集の自動化」のうち、続きとして収集の自動化を行いました。

1. 環境の準備
    * Azure VM の Ubuntu を 18.04(LTS) から 20.04(LTS) へアップグレード
    * Python3 と pip3 が利用できることを確認

2. 環境の構築
    * 必要なパッケージのインストール
    * pyenv のインストール
    * pyenv を利用して Python 3.8.6 のインストール
    * Poetry のインストール
    * MongoDB のインストール

3. プログラムの配置
    * MongoDB へユーザーを作成
    * プログラムの配置と設定
    * geckodriver のインストール
    * .env の作成
    * プログラムの動作確認
    * cron の設定

## 環境の準備

下記構成の環境を用意しました。

* Azure VM の Ubuntu 18.04(LTS) ※Python3 と git 導入済
* Windows 10 1909
* WSL Ubuntu 20.04.1 LTS (GNU/Linux 4.4.0-18362-Microsoft x86_64)
* PowerShell 7.0.3 + Microsoft Azure CLI 2.14.0

Ubuntu 20.04(LTS) では、ベースシステムに含まれる Python が Python 3.8　になるため、Ubuntu 18.04(LTS) から Ubuntu 20.04(LTS) へアップグレードしました。

* [Azure VM の Ubuntu を 18.04(LTS) から 20.04(LTS) へアップグレードしたら Grub が壊れた→復旧](https://qiita.com/kerobot/items/5bcd1e4ccf0ba47436ef)

結果的にアップグレードは成功し、Ubuntu 20.04.1 LTS (GNU/Linux 5.4.0-1031-azure x86_64) となりました。

念のためアップグレード後に、パッケージを更新しておきます。

```bash
$ sudo apt update
$ sudo apt upgrade
```

Python のバージョンを確認してみます。

```bash
$ python -V
Python 2.7.18
```

```bash
$ python3 -V
Python 3.8.5
```

python3 コマンドでは 3.8.5 ですが、python コマンドでは 2.7.18 のようです。

pip3 がインストールされていなかったのでインストールしておきます。

```bash
$ sudo apt install python3-pip
$ pip3 -V
pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8)
```

こんな状態から環境構築を進めてみます。

## 環境の構築

### 必要なパッケージのインストール

pyenv などの実行に必要なパッケージをインストールしておきます。

実際には、pyenv を利用して Python をインストールした際に発生したエラーや Poetry を利用してパッケージをインストールした際のエラーから、必要なパッケージを調べて導入したものです。

```bash
# エラー1 can't decompress data; zlib not available
# zlib1g-dev をインストール
$ sudo apt install zlib1g-dev

# エラー2 Missing the OpenSSL lib
# libssl-dev をインストール
$ sudo apt install libssl-dev

# エラー3 ModuleNotFoundError: No module named '_ctypes'
# libffi-dev をインストール
$ sudo apt install libffi-dev

# エラー4 Poetry を利用したパッケージインストール（仮想環境の構築）が失敗する場合
# python3-venv をインストール
$ sudo apt install python3-venv
```

### pyenv のインストール

Python の実行環境を管理するための [pyenv](https://github.com/pyenv/pyenv) を github からインストールします。

```bash
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
```

~/.bash_profile へ、pyenv の PATH や pyenv init を追加します。

```bash
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bash_profile
```

~/.bashrc を読み込むための記述を ~/.bash_profile の最後に記述しておきます。

```bash
$ test -r ~/.bashrc && . ~/.bashrc
```

~/.bash_profile を適用しておきます。

```bash
$ source ~/.bash_profile
```

pyenv のバージョンを確認しておきます。

```bash
$ pyenv --version
pyenv 1.2.21
```

### pyenv を利用して Python 3.8.6 のインストール

プログラムの実行に利用する Python 3.8.6 を pyenv でインストールします。

```bash
# インストール可能な Python の確認
$ pyenv install --list
# Python 3.8.6 のインストール
$ pyenv install 3.8.6
```

いくつか警告がありますが、Python 3.8.6 をインストールできました。

```bash
Downloading Python-3.8.6.tar.xz...
-> https://www.python.org/ftp/python/3.8.6/Python-3.8.6.tar.xz
Installing Python-3.8.6...
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
WARNING: The Python sqlite3 extension was not compiled. Missing the SQLite3 lib?
Installed Python-3.8.6 to /home/mcuser/.pyenv/versions/3.8.6
```

エラーが発生した場合は、必要なパッケージがインストールされているかを確認します。

pyenv を利用して、グローバル環境の Python のバージョンを切り替えておきます。

```bash
$ pyenv global 3.8.6
# ローカル環境の切り替えはプロジェクトのディレクトリを作った後に行う。
# $ pyenv local 3.8.6
```

グローバル環境の Python のバージョンが切り替わったことを確認しておきます。

```bash
$ pyenv versions
  system
* 3.8.6 (set by /home/mcuser/.python-version)
```

pyenv に管理されている Python の場所を確認します。

```bash
$ which python
/home/ユーザー/.pyenv/shims/python
```

グローバル環境の Python のバージョンを確認します。

```bash
$ python -V
Python 3.8.6
$ python3 -V
Python 3.8.6
```

参考までに、pyenv のアンインストール方法は下記となります。

```bash
# ~/.bash_profile から、PATH や pyenv init の記述を手作業で削除したうえでディレクトリを削除する。
$ rm -rf $(pyenv root)
```

### Poetry のインストール

Python のパッケージやプロジェクトを管理するための [Poetry](https://github.com/python-poetry/poetry) をインストールします。

```bash
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
```

```text
Retrieving Poetry metadata

# Welcome to Poetry!

This will download and install the latest version of Poetry,
a dependency and package manager for Python.

It will add the `poetry` command to Poetry's bin directory, located at:

$HOME/.poetry/bin

This path will then be added to your `PATH` environment variable by
modifying the profile files located at:

$HOME/.profile
$HOME/.bash_profile

You can uninstall at any time by executing this script with the --uninstall option,
and these changes will be reverted.

Installing version: 1.1.4
  - Downloading poetry-1.1.4-linux.tar.gz (57.03MB)

Poetry (1.1.4) is installed now. Great!

To get started you need Poetry's bin directory ($HOME/.poetry/bin) in your `PATH`
environment variable. Next time you log in this will be done
automatically.

To configure your current shell run `source $HOME/.poetry/env`
```

~/.bash_profile と ~/.profile へ、Poetry の PATH を自動で書き込んでくれます。

```bash
# 自動で書き込んでくれるのでこれらの設定は不要。
echo 'export PATH="$HOME/.poetry/bin:$PATH"' >> ~/.profile
echo 'export PATH="$HOME/.poetry/bin:$PATH"' >> ~/.bash_profile
```

~/.bash_profile はこんな感じにしておきました。

```bash
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
if command -v pyenv 1>/dev/null 2>&1; then
  eval "$(pyenv init -)"
fi

export PATH="$HOME/.poetry/bin:$PATH"

test -r ~/.bashrc && . ~/.bashrc
```

~/.bash_profile を適用しておきます。

```bash
$ source ~/.bash_profile
```

Poetry のバージョンを確認しておきます。

```bash
$ poetry --version
Poetry version 1.1.4
```

Poetry が作成する Python 仮想環境の場所をプロジェクトディレクトリ内に設定します。

```bash
$ poetry config --list
$ poetry config virtualenvs.in-project true
```

参考までに、Poetry のアンインストール方法は下記となります。

```bash
# ~/.bash_profile や ~/.profile から、PATH の記述を自動で削除してくれます。
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_UNINSTALL=1 python -
```

### MongoDB のインストール

取集した情報を登録するための [MongoDB](hhttps://www.mongodb.com/) をインストールします。

まずは、パッケージ管理システムで使用する公開鍵をインポートしておきます。

```bash
$ wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
```

パッケージ管理システムで利用する MongoDB のリストファイルを作成します。

```bash
$ echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
```

パッケージ管理システムのデータベースを更新します。

```bash
$ sudo apt update
```

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

```bash
sudo apt install -y mongodb-org
```

データディレクトリ、ログディレクトリ、設定ファイルの場所はそれぞれ下記となっていました。

```text
データディレクトリ
/var/lib/mongodb
ログディレクトリ
/var/log/mongodb
設定ファイル
/etc/mongod.conf
```

MongoDB がサービスとして登録されるので、状態の確認や開始・停止は下記のように行います。

```bash
$ sudo systemctl status mongod
$ sudo systemctl start mongod
$ sudo systemctl stop mongod
$ sudo systemctl restart mongod
```

MongoDB のバージョンを確認します。

```bash
$ mongo --version
MongoDB shell version v4.4.1
Build Info: {
    "version": "4.4.1",
    "gitVersion": "ad91a93a5a31e175f5cbf8c69561e788bbc55ce1",
    "openSSLVersion": "OpenSSL 1.1.1f  31 Mar 2020",
    "modules": [],
    "allocator": "tcmalloc",
    "environment": {
        "distmod": "ubuntu2004",
        "distarch": "x86_64",
        "target_arch": "x86_64"
    }
}
```

MongoDB の Mongo shell を利用して admin ユーザーを作成しておきます。

```bash
$ mongo
> use admin
> db.createUser( { user:"admin", pwd:"password", roles:[{ "role" : "root", "db" : "admin" }] } );
> exit
```

MongoDB に admin ユーザーが作成されたことを確認します。

```bash
$ mongo
> use admin
> db.system.users.find();
> exit
```

MongoDB の認証設定を有効にするために、mongod.conf を編集します。

```bash
sudo cp /etc/mongod.conf /etc/mongod.conf.org
sudo vi /etc/mongod.conf
```

この記述を行います。

```bash
# mongod.conf
security:
  authorization: enabled
```

MongoDB のサービスを再起動します。

```bash
$ sudo systemctl restart mongod
```

MongoDB の認証設定が有効になっていることを確認します。

```bash
$ mongo
> use admin
switched to db admin
# 認証無しだとエラー
> db.system.users.find();
Error: error: {
        "ok" : 0,
        "errmsg" : "command find requires authentication",
        "code" : 13,
        "codeName" : "Unauthorized"
}
# 認証ありだと成功
> db.auth("admin", "password");
1
> db.system.users.find();
{ "_id" : "admin.admin", "userId" : ...
> exit
```

Mongo shell の起動と同時にデータベースを指定して認証を行う場合

```bash
$ mongo localhost:27017/admin -u admin -p
MongoDB shell version v4.4.1
Enter password:
...
> exit
```

## プログラムの配置

### MongoDB へユーザーを作成

プログラムから MongoDB を利用する際のユーザーを作成します。

```bash
$ mongo localhost:27017/admin -u admin -p
MongoDB shell version v4.4.1
Enter password:
...
> use holoduledb
> db.createUser( { user:"owner", pwd:"password", roles:[{ "role" : "dbOwner", "db" : "holoduledb" }] } );
> exit
```

作成したユーザーで接続できることを確認します。

```bash
$ mongo localhost:27017/holoduledb -u owner -p
MongoDB shell version v4.4.1
Enter password:
...
> exit
```

### プログラムの配置と設定

github からプログラムを取得して配置します。

```bash
$ cd ~
$ git clone https://github.com/kerobot/holocrawler.git holocrawler
$ cd holocrawler
```

念のため、プログラムの実行に必要な Python のバージョンを確認しておきます。

```bash
$ cat pyproject.toml
...
python = "^3.8"
...
```

pyenv を利用して、ローカル環境の Python のバージョンを切り替えます。

```bash
$ pyenv local 3.8.6
```

ローカル環境の Python と pip のバージョンを確認します。

```bash
$ python -V
Python 3.8.6
$ pip -V
pip 20.2.1 from /home/mcuser/.pyenv/versions/3.8.6/lib/python3.8/site-packages/pip (python 3.8)
```

Poetry を利用して、Python 仮想環境やパッケージをインストールします。

```bash
$ poetry install
```

Python 仮想環境の pip を更新しておきます。

```bash
$ /home/mcuser/holocrawler/.venv/bin/python -m pip install --upgrade pip
Collecting pip
  Downloading pip-20.2.4-py2.py3-none-any.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 5.4 MB/s
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 20.2.2
    Uninstalling pip-20.2.2:
      Successfully uninstalled pip-20.2.2
Successfully installed pip-20.2.4
```

### geckodriver のインストール

プログラムから利用する geckodriver をインストールしておきます。

```bash
$ sudo apt install firefox-geckodriver
```

geckodriver のバージョンを確認します。

```bash
$ geckodriver --version
geckodriver 0.27.0
```

### .env の作成

YouTube Data API v3 の APIキーや、MongoDB の接続情報などを設定しておくためのファイルを作成します。

```bash
$ cp .env.sample .env
```

```text
HOLODULE_URL = "<Holodule URL>"
API_KEY = "<Youtube Data API Key>"
API_SERVICE_NAME = "youtube"
API_VERSION = "v3"
MONGODB_USER = "<user>"
MONGODB_PASSWORD = "<password>"
MONGODB_HOST = "<localhost:27017>"
```

### プログラムの動作確認

プログラムを実行してみます。

```bash
$ cd ~/holocrawler
$ poetry run python main.py
```

データベースを確認してみます。

```bash
$ mongo localhost:27017/holoduledb -u owner -p
MongoDB shell version v4.4.1
Enter password:
...
> show collections
holodules
> db.holodules.find();
{ "_id" : ObjectId("5fa6b670ff1b800180c43355"), "key" : "HL0303_20201106_000100", "video_id" : "HIiuFLswKsM", "datetime" : "20201106 000100", "name" : "不知火フレア", "title" : "【マインクラフト/Minecraft】真夜中でも楽しいマイクラ☽Midnight fun minecraft【ホロライブ/不知火フレア】", "url" : "https://www.youtube.com/watch?v=HIiuFLswKsM", "description" : "ホロ ライブ所属３期生の不知火フレア(Flare Shiranui)だよ～！磯〇～～～！地下室にプチエンド作ろうぜ！！------------------------------------------" }
{ "_id" : ObjectId("5fa6b671ff1b800180c43356"), "key" : "HL0002_20201106_000900", "video_id" : "3KAobkipFrY", "datetime" : "20201106 000900", "name" : "ロボ子さん", "title" : "【ＵＮＯ】帰ってきちゃ！深夜の夜更かし #UNOLIVE　💤【ホロライブ/ロボ子さん】", "url" : "https://www.youtube.com/watch?v=3KAobkipFrY", "description" : "#UNOLIVE　 #ＵＮＯ　#ホロライブメンバー@フブキCh。白上フブキ @Polka Ch. 尾丸ポルカ @Watame Ch. 角巻わため @Roboco Ch. - ロボ子 おすすめ翻訳！字幕" }
...
> exit
```

問題なくプログラムが動作しているようです。

### cron の設定

cron を利用してプログラムが定期実行されるようにしてみます。

まずは、cron の状態を確認します。

```bash
$ sudo service cron status
$ または /etc/init.d/cron status
● cron.service - Regular background program processing daemon
     Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2020-11-06 19:58:59 JST; 2 days ago
       Docs: man:cron(8)
   Main PID: 90947 (cron)
      Tasks: 1 (limit: 4671)
     Memory: 7.7M
     CGroup: /system.slice/cron.service
             └─90947 /usr/sbin/cron -f
```

crontab コマンドでカレントユーザーの cron を設定します。

```bash
$ crontab -e
# $ sudo crontab -e これは root の cron になります。
```

1日に4回、5:30と11:30と17:30と23:30に実行されるようにしてみます。

```bash
# 分 時 日 月 曜日 コマンド
30 5,11,17,23 * * * /home/mcuser/holocrawler.sh > /home/mcuser/holocrawler.log 2>&1
```

crontab コマンドでカレントユーザーの cron を確認します。

```bash
$ crontab -l
```

cron から実行されるシェルスクリプトを作成します。

```bash
$ cd ~
$ touch holocrawler.sh
$ sudo chmod 755 holocrawler.sh
$ vi holocrawler.sh
```

```bash
#!/bin/bash

SHELL=/bin/bash
PYENV_ROOT=$HOME/.pyenv
POETRY_ROOT=$HOME/.poetry
PATH=$PYENV_ROOT/bin:$POETRY_ROOT/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

if command -v pyenv 1>/dev/null 2>&1; then
  eval "$(pyenv init -)"
fi

cd ~/holocrawler
$POETRY_ROOT/bin/poetry run python main.py
```

念のため、cron を再起動しておきます。

```bash
$ sudo service cron restart
$ または /etc/init.d/cron restart
```

必要に応じてログ（syslog）を確認します。

```bash
$ less /var/log/syslog | grep CRON
```

## おわりに

cron の設定をした後、しばらくしてから MongoDB のコレクションを確認したところ、定期的に配信情報が登録されていることが確認できました。

これで、やりたいこと 2 の「収集した動画配信予定を格納するためのデータベースの作成と収集の自動化」は完了です。

引き続き、MongoDB のドキュメントを参照するための Web API を作っていきますが、単純な Web API では面白くないので、Azure API Apps や Azure Functions や Graph API などを試してみたいなと思います。