EC2(Amazon Linux 2)でJupyter Notebookをサービスとして動かす

前回の記事でJupyterサーバーを構築しましたが、使うたびにいちいちログインしてJupyterを起動するのは手間です。
そこで、EC2インスタンスを起動したら自動的にJupyterも立ち上がるように設定しようと思います。

昔やったときは、Linuxが起動するときに実行されるスクリプトである、
/etc/rc.local
ファイルに、以下のJupyterの起動コマンドを書き込んでいました。
su - ec2-user jupyter notebook &

ただ、最近はrc.localを使うのではなく、Jupyter Notebookをサービスとして動かすのがトレンドのようなので今回はその方法でやってみます。
この方法だと、systemctlコマンドで管理できるようになるので便利そうです。

サービスファイルの中身についてのマニュアルはこちらのようです。
参考: systemd.service

まず、whichコマンドでjupyterのフルパスを確認しておきます。


$ which jupyter
~/.pyenv/shims/jupyter

~ は /home/ec2-user なので、実際のフルパスは
/home/ec2-user/.pyenv/shims/jupyter
ですね。

続いて、ユニットファイルを作成します。


sudo vim /etc/systemd/system/jupyter.service

# 中身は以下の通り
[Unit]
Description=Jupyter Notebook

[Service]
ExecStart=/home/ec2-user/.pyenv/shims/jupyter notebook
Restart=always
User=ec2-user
Group=ec2-user

[Install]
WantedBy=multi-user.target

他のサイトを見てると、[Service]のところに、
Type=simple
と入れてる人も多いですが、simpleはデフォルトなので省略しても良さそうです。(ExecStartを指定して、TypeとBusNameをいずれも指定しない時のデフォルトがsimple)
ExecStartに先ほど確認したJupyterのフルパスを指定します。
UserとGroupは指定しないとrootになってしまうようなので、ec2-userを指定します。
Restart=always は何らかの理由でサービスが終了したときに自動的に再起動する設定です。

ファイルを保存して閉じたら、サービスとして認識されていることを確認します。


$ systemctl list-unit-files --type=service | grep jupyter
jupyter.service                               disabled

あとは、起動することを確認します。


$ sudo systemctl start jupyter
$ sudo systemctl status jupyter

この段階で、ブラウザからもアクセスして使えることを確認しておきましょう。
ここまで上手くいったら、あとは自動的に起動するように設定して完成です。


$ sudo systemctl enable jupyter
$ systemctl list-unit-files --type=service | grep jupyter
jupyter.service                               enabled

EC2でJupyterサーバーを構築する

今となってはGoogleのColaboratoryがあるのであまりニーズがないのですが、
EC2でJupyter notebook環境を構築する方法についてまとめておきます。

前提ですが、OSはAmazon Linux 2を利用し、Pythonの仮想環境はpyenvで作ります。
また、ポートはデフォルトの8888番を使用し、アクセスにはパスワードをかけます。
また、作業ディレクトリは/var/notebookとします。

手順1. インスタンスを立てる

Amazon Linux 2 のAMIを選択し、EC2インスタンスを立てます。
このときセキュリティグループではssh接続に使用する22番ポートと、
Jupyter Notebookに接続するポート(デフォルトでは8888番)を開けておきます。

手順2. pyenv必要なモジュールのインストール

pyenv を使うためのモジュールを順番に入れていきます。
最初にyumを最新化し、git、次にpyenvのドキュメントで指定されているモジュール群を入れます。
FIXME: you may need to install xz to build some CPython version
とありますが、xzを入れておかないと新しめのバージョンのpandasをインポートしたときに、
lzma moduleがないと言う旨の警告が出るのでxzも入れておきます。


# yumを最新の状態にする
$ sudo yum update
# gitのインストール
$ sudo yum install git
# 必要なモジュールのインストール
$ sudo yum install gcc zlib-devel bzip2 bzip2-devel readline-devel
$ sudo yum install sqlite sqlite-devel openssl-devel tk-devel libffi-devel
$ sudo yum install xz xz-devel

手順3. pyenv本体のインストール

ドキュメントの手順にそってインストールします。
リポジトリをcloneし、.bash_profileに設定を入れていきます。
入れたら.bash_profileを再度読み込み、インストールの結果確認としてバージョンを表示します。


$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
$ 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

# .bash_profileを読み込む
$ source .bash_profile
# バージョンの確認
$ pyenv --version
pyenv 1.2.23-75-g80e418ec

手順4. Pythonのインストール

pyenvでインストールできるバージョンを確認し、インストールします。
今回は3.8.8を入れることにしました。
このため、notebookは別途インストールします。
ここで、Anacondaを選ぶと、次のステップのnotebookのインストールを飛ばすことができます。


# インストールできるバージョンの一覧を確認する
$ pyenv install -l
# 3.8.8 をインストールする
$ pyenv install 3.8.8
# 3.8.8 に切り替え
$ pyenv global 3.8.8
$ pyenv rehash
# pythonのバージョンを確認
$ python --version 
Python 3.8.8

手順5. Jupyter Notebookのインストール

pipを最新化し、Jupyterをインストールします。
Anacondaを使う場合はこの手順は不要です。


$ pip --version
pip 20.2.3 from /home/ec2-user/.pyenv/versions/3.8.8/lib/python3.8/site-packages/pip (python 3.8)
$ pip install --upgrade pip
$ pip --version
pip 21.0.1 from /home/ec2-user/.pyenv/versions/3.8.8/lib/python3.8/site-packages/pip (python 3.8)

# Jupyter Notebookのインストール
$ pip install notebook

手順6. パスワードトークンの取得

設定ファイルにセットするため、パスワードのハッシュを入手しておきます。
次のコードを実行すると、パスワードを確認含めて2回聞かれますのでそれぞれ入力します。
そして、出力された文字列をどこかに記録しておきます。
昔は sha1:xxxxxxx みたいな文字列でしたが、最近は argon2:xxxxxxx のようです。
アルゴリズムが変わったみたいですね。


$ python -c 'from notebook.auth import passwd;print(passwd())'
Enter password:
Verify password:
argon2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

手順7. 作業ディレクトリ作成

notebookのホームディレクトリとなる作業ディレクトリを作成します。
どこに作ってもいいと思うのですが、自分は/var配下に作るのが好みなので/var/notebookとしています。
本当は専用のユーザーを作った方がいいと思うのですが、自分はそのままec2-userで動かすので、
作ったディレクトリにはec2-userが書き込みができるように権限設定しておきます。


$ cd /var
$ sudo mkdir notebook
$ sudo chown ec2-user:ec2-user notebook

手順8 設定ファイル生成とバックアップ

Jupyter Notebookの設定ファイルを作成し、バックアップをとっておきます。
メッセージに表示されている通り、
/home/ec2-user/.jupyter/jupyter_notebook_config.py
と言うファイルが生成されます。


$ jupyter notebook --generate-config
Writing default config to: /home/ec2-user/.jupyter/jupyter_notebook_config.py
$ cd .jupyter/
$ cp jupyter_notebook_config.py jupyter_notebook_config.py.org

手順9 必要な設定を行う

vimか何かで設定ファイルを開き、必要な設定を施していきます。
vim jupyter_notebook_config.py

必要なのは以下の4行です。


c.NotebookApp.ip = '0.0.0.0'
c.NotebookApp.open_browser = False
c.NotebookApp.notebook_dir = '/var/notebook'
c.NotebookApp.password = 'argon2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

c.NotebookApp.ip の設定は ‘0.0.0.0’ の代わりに ‘*’でも構いません。
デフォルトはlocalhostになっていますが、これにより外部からのアクセスが許可されます。
c.NotebookApp.open_browser = False
は起動時にブラウザを立ち上げない設定です。
c.NotebookApp.notebook_dir はホームディレクトリの設定なので、先ほど作った/var/notebookを設定します。
c.NotebookApp.password
には先ほど保存しておいたパスワードトークンを設定してください。

手順10 起動と接続テスト

これで、準備は整いましたのでnotebookを立ち上げます。


$ jupyter notebook

あとは、
http://{ec2のパブリックIP}:8888
にアクセスすると、Jupyter Notebookのログイン画面が表示されます。
パスワードトークン生成時に入力したパスワードでログインできたら成功です。

この状態だと、ec2インスタンスを起動するたびに、sshで入ってJupyterを起動する必要があり若干手間なので、
次の記事ではインスタンス起動時に自動的にJupyterが立ち上がるように設定する方法を紹介する予定です。

Amazon Translate を試してみた

ちょっとAmazon Translateに興味が湧いて試してみたのでそのメモです。
結論ですが、非常に簡単に翻訳ができることがわかりました。

準備として、 boto3 が使えるようにしておく必要があります。(アクセスキーやシークレットキーの準備。自分は環境変数に入れています)
また、利用するIAMにAmazon Translateの権限を付与しておく必要があります。

例文はいつも通り「走れメロス」から拝借しています。

翻訳対象のテキストはこちらです。


text = """
メロスは激怒した。
必ず、かの邪智暴虐の王を除かなければならぬと決意した。
メロスには政治がわからぬ。
メロスは、村の牧人である。
笛を吹き、羊と遊んで暮して来た。
けれども邪悪に対しては、人一倍に敏感であった。
きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此このシラクスの市にやって来た。
メロスには父も、母も無い。
女房も無い。
十六の、内気な妹と二人暮しだ。
"""

Amazon Translate をboto3から使う方法は非常に簡単で、client を生成して、
translate_textテキストに、ほんやくしたいテキスト、元の言語、翻訳先の言語を指定するだけです。
今回は 日本語 -> 英語 を指定しました。

ちなみにドキュメントはこちらです。


import boto3
client = boto3.client('translate')

result = client.translate_text(
    Text=text,
    SourceLanguageCode="ja",
    TargetLanguageCode="en",
)

print(result["TranslatedText"])
"""
Melos got furious.
He determined that he must exclude the king of wicked violence.
Melos does not know politics.
Melos is a village shepherd.
I played a whistle and played with the sheep.
However, for evil, people were more sensitive to evil.
Today, the unknown Melos left the village, crossed the field over the mountains, and came to this city of Silax, where the city of Silax is gone.
Melos has no father or mother.
There is no wife.
I live two with sixteen shy sisters.
"""

あっという間にできましたね。
「笛を吹き、羊と遊んで暮して来た。」の部分の主語が「I(私)」になってしまっていたり、邪悪に対して敏感なのがメロスではなく人々(people)になっていたり、
元々の文の主語が省略されていた部分については少し誤訳があるように感じますが、翻訳サービスとしてはやむを得ない部分もあるでしょう。

今回のコードでは、元の言語をSourceLanguageCode="ja"と指定しましたが、ここは”auto”とすることもできます。


result = client.translate_text(
    Text=text,
    SourceLanguageCode="auto",
    TargetLanguageCode="en",
)

この場合、 Amazon Comprehend を使って自動的に元のテキストの言語を推定して翻訳してくれます。
元の言語を何と推定したかは、結果のオブジェクトから取得できます。きちんと日本語(ja)になったようです。


print(result["SourceLanguageCode"])
"ja"

一度に翻訳できるテキストの長さは、5000byteまでです。5000文字ではないので特に僕ら日本人は注意が必要です。
試しに、元のテキスト(191文字)を20回繰り返して長文を作って翻訳にかけてみます。


long_text = text*20
print(len(long_text))
# 3820

try:
    result = client.translate_text(
        Text=long_text,
        SourceLanguageCode="ja",
        TargetLanguageCode="en",
    )
except client.exceptions.TextSizeLimitExceededException as e:
    print(e)

"""
An error occurred (TextSizeLimitExceededException) when calling the TranslateText operation:
Input text size exceeds limit.
Max length of request text allowed is 5000 bytes while in this request the text size is 11020 bytes
"""

出力されたエラーメッセージを読んでいただけるとわかりますが、5000バイトが上限なのに、11020バイト渡されたと言うエラーになっていますね。
しかし、リクエストしたテキストは3820文字です。

明らかに短いテキストを翻訳にかける場合は問題ないですが、そうでなければ、
translate_text を呼び出す前にチェックを入れるか、
上のコードみたいに例外処理を加えた方が良いでしょう。

LightsailのWordPressの開発環境を立てる

このBlogはWordPressの設定を最低限度にしか変更せずに運用しています。(記事執筆時時点)
ただ、それでも少々修正したい部分は発生しており、その一部はどうも管理画面では修正できず、PHPファイルを修正する必要があるようです。
(具体的に挙げと、フッターの、 「プライバシーポリシー Proudly powered by WordPress」 の部分など)

僕自身が普段はPHPを書いておらず、WordPressに不慣れなのに本番環境を直接いじって修正するのはリスクが高すぎるので、
開発環境が欲しいなと思ったので調べてみました。

このBlogはLightsailで動いているので、同じようにLightsailでWordpressインスタンスを立てて、
今入れているテーマやプラグインを追加していくのも方法の一つです。

ただ、そのようなことををしなくてもこのサーバーをそのままコピーできるらしいことがわかりました。

手順は以下の通りです。
1. Lightsail の管理画面に入り、このBlogが動いているインスタンスを選択。
2. スナップショットを選択し、手動スナップショットをとる。(自動スナップショットを取っている人はこの手順は不要)
3. 取得したスナップショットから、「新規インスタンスを作成」

これで、記事等も全部入った状態でインスタンスのコピーが立ち上がります。

あとは、
http://{IPアドレス}/
でアクセスしたら、開発環境にアクセスできて万歳、となると思っていたのですが、そうはならず、
何度試しても、本番環境(このBlog)にリダイレクトされてしまいました。

原因は以前設定したリダイレクト設定です。
参考: httpのアクセスをhttpsにリダイレクトする

開発サーバーにSSHログインし、bitnami.confに参考記事で追記した3行を消し、apacheを再起動すると、開発環境にアクセスできるようになります。

お金がかかるので、必要な修正の検討が終わったら作った開発環境とスナップショットは消しておきましょう。

Amazon Linux 2 (EC2)にphpMyAdminを導入

MySQLやその互換のDBを管理するphpMyAdminという便利なツールがあります。
僕が私用で使っている Aurora Serverless の管理もphpMyAdminで行おうとしたら、予想よりも苦戦したのでそのメモです。
参考: Amazon Aurora Serverlessを使ってみる

昔、Amazon Linux (無印)のインスタンスを建てたときは、ApachとPHPを入れて、phpMyAdminのソースファイルを配置しただけで
当時使っていた同じインスタンス内のMySQLの管理をすぐできるようになりました。
しかし当時の手順を元に、Amazon Linux 2で同様の操作を行ったら全然うまくいきませんでした。

改めて手順を探してみると、「チュートリアル: Amazon Linux 2 に LAMP ウェブサーバーをインストールする
というドキュメントが用意されており、これを参考にすることでうまくいきました。
(このチュートリアルでは、同サーバーにMariaDBを入れますが、僕はRDSのAurora Serverlessを使うので、MariaDB関係の手順は省略します。)

EC2インスタンス作成

最初にインスタンスを作ります。
DBをEC2インスタンスにインストールしている場合はそのインスタンスを使えば良いのでこの手順は不要です。

Amazon Linux 2 の AMIを選択し、通常通りインスタンスを作成します。
sshログインと、Webアクセスができるように、22番と80番のポートが開いたセキュリティグループを設定しておきます。

インスタンスができたら、sshで入って、yumをアップデートします。


$ sudo yum update -y

PHPのインストール

最初のハマりポイントがここです。
普通に、 
$ yum install -y php
とすると、バージョン 5.4.16 の非常に古いPHPが入ってしまいます。

チュートリアルに沿って、 amazon-linux-extras と言うのを使って 7系のPHPをインストールします。


$ sudo amazon-linux-extras install -y php7.2

# 入ったバージョンの確認
$ php -v
# 以下出力
# PHP 7.2.34 (cli) (built: Oct 21 2020 18:03:20) ( NTS )
# Copyright (c) 1997-2018 The PHP Group
# Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

Apacheのインストール

PHPの次は Apache のインストールと起動設定です。
Amazon Linux 2になって、無印の時とコマンドが変わっているので注意が必要です。


# Apache のインストール
$ sudo yum install -y httpd
# 起動
$ sudo systemctl start httpd
# サーバー起動時に自動的に起動するようにする
$ sudo systemctl enable httpd

以前は、起動は
$ service httpd start
で、自動起動は、
$ chkconfig httpd on
でしたね。

この時点でWebサーバーは立ち上がっているので、ブラウザを起動し、
http://{サーバーのIPアドレス}
にアクセスできることを確認します。

PHPの動作確認

ApacheでPHPが動くことを確認します。

昔は、Apacheの設定ファイル( /etc/httpd/conf/httpd.conf ) に
<FilesMatch \.php$>
SetHandler applocation/x-httpd-php
</FilesMatch>
など書いて設定しないと動かなかった覚えがあるのですが、それをしなくても動きます。

さて、動作確認に進みましょう。
ドキュメントルート(つまり、 /var/www/html)に移動し、phpinfo.phpというテキストファイルを生成します。


$ cd /var/www/html
$ vim phpinfo.php

# phpinfo.php に以下の内容を書き込む。
<?php phpinfo(); ?>

ブラウザから
http://{サーバーのIPアドレス}/phpinfo.php
にアクセスし、PHPのバージョンやシステム情報などのテーブルが表示されることが確認できたらPHPは動作しています。

phpMyAdminのインストール

ここから、phpMyAdmin本体のインストールです。

まず、phpの依存モジュールをインストールします。
(これはyumでいいらしいです。不思議です。)


# 必要な依存ファイルをインストール
$ sudo yum install php-mbstring -y
# Apache を再起動
$ sudo systemctl restart httpd
# php-fpm を再起動
$ sudo systemctl restart php-fpm

次に、phpMyAdminの本体ファイルをダウンロードして配置します。
ec2-userはドキュメントルート配下に書き込み権限を持っていないと思うので、
権限設定を適切にするか、面倒であればルートになって作業しましょう。


# ドキュメントルートに移動
$ cd /var/www/html
# phpMyAdminのファイルを取得する。
$ wget https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.tar.gz
# ファイル展開
$ mkdir phpMyAdmin && tar -xvzf phpMyAdmin-latest-all-languages.tar.gz -C phpMyAdmin --strip-components 1
# 不要ファイル削除
$ rm phpMyAdmin-latest-all-languages.tar.gz

以前は、 phpMyAdminのダウンロードページに訪問して、
最新バージョンを調べて、
wget https://files.phpmyadmin.net/phpMyAdmin/5.0.4/phpMyAdmin-5.0.4-all-languages.tar.gz
みたいに、バージョン指定して落としていたのですが、 latest で取れたんですね。
これは知りませんでした。

あとはブラウザで、
http://{IPアドレス}/phpMyAdmin/
にアクセスし、ログイン画面が表示されればインストールできています。

同サーバー内のDBの管理をするのであればこのまま使えます。

RDSへの接続を設定する

さて、冒頭の通り、僕が管理したいサーバーはAurora Serverlessなので、その設定を行います。
方法は設定ファイルにエンドポイントを指定するだけです。
まず、設定ファイルを作成します。
デフォルトでは設定ファイルは存在しておらず、config.sample.inc.phpと言うテンプレートを、
config.inc.php という有効なファイル名にコピーして使います。


# デフォルトの設定ファイルをコピーして設定ファイルを生成する
cd /var/www/html/phpMyAdmin
cp config.sample.inc.php config.inc.php

/var/www/html/phpmyadmin/config.inc.php の以下の行を書き換える
# 元の記述
$cfg['Servers'][$i]['host'] = 'localhost';
# 修正後の記述
$cfg['Servers'][$i]['host'] = '{RDSのエンドポイント}';

ログイン確認

以上の操作が全て終わったら、実際にphpMyAdminにログインして確認します。

ユーザーとパスワードは RDSのものを使います。
Aurora Serverless なので、停止状態の場合は起動に少し時間がかかりますが、数分程度待てば無事にログインできます。

EC2の不要なインスタンスの消し方

とても単純な話なのですがかなり迷ったので備忘録的に残しておきます。

「インスタンスの削除」的なメニューがあるもんだと思ってずっと探していましたが、
実際には 「インスタンスの終了」 がそれにあたります。 (削除という文言がないのでなかなか気づきませんでした。)

消したいインスタンスを右クリックし、「インスタンスの状態」 => 「インスタンスの終了」 と選択することで消せます。
消してもしばらくはコンソール上に残るようです。

「終了保護」が設定されていると、終了ができないので、本当に消したい場合は事前に外しておきましょう。

boto3でEC2インスタンスを起動してIPアドレスを取得する

EC2のインスタンスを操作するとき、(会社ではCLIのバッチファイルを手元に持ってるのですが)私物の環境ではいつもコンソールにログインして起動/停止していたので、
これもバッチ化することにしました。
AWS CLIではなく、 Pythonライブラリの boto3で作ってみます。

職場の環境と異なり、自分の学習環境のEC2はIPアドレスを固定していません。(お金かかるから。)
なので、毎回コンソールから起動して、IPアドレスを確認し、そのIPアドレスに接続する、という作業を行っていました。
これを短縮するため、起動したらIPアドレスを取得して表示できるようにします。

ドキュメントはこの辺りが参考になります。
ec2.html#instance

以下のように、ec2インスタンスのオブジェクトを用意します。


import boto3

ec2 = boto3.resource('ec2')
instance = ec2.Instance('{インスタンスID}')

そして、startで起動してwait_until_running()で起動完了を待ち、
public_ip_addressを取得すれば良いです。

完成したバッチファイルの中身は次のようになります。
これを.pyファイルとして保存して、実行できるように権限を744にします。


#!/usr/bin/env python
import boto3
instance_id = "{インスタンスID}"

ec2 = boto3.resource('ec2')
instance = ec2.Instance(instance_id)
print("インスタンス起動開始")
instance.start()
instance.wait_until_running()
print("インスタンス起動完了")
ip = instance.public_ip_address
print(f"IPアドレス: {ip}")

実行するときちんと、
インスタンス起動開始
インスタンス起動完了
IPアドレス: xx.xxx.xxx.xxx
が表示されました。

ついでですが、停止バッチは次のようになります。


#!/usr/bin/env python
import boto3
instance_id = "{インスタンスID}"

ec2 = boto3.resource('ec2')
instance = ec2.Instance(instance_id)
print("インスタンス停止開始")
instance.stop()
instance.wait_until_stopped()
print("インスタンス停止完了")

Amazon Aurora Serverlessを使ってみる

ずっと前から気になっていたまま、使ったことがなかったのですが重い腰を上げて Aurora Serverless を触ってみることにしました。

用途は仕事ではなく、自分の趣味と勉強でとあるデータを蓄積するDBです。
AWSのアカウント持っていてDBが必要なら、RDSを使うのが順当なのですが、
通常のRDSは個人で使うにはちょっとお高いので、これまではEC2に導入したMySQLを使っていました。
そこにAurora の Serverless が登場ということで、自分の分析作業時のみの課金で使えるとなれば非常にお得な気がします。
完全に移行できるかとコストはトータルどうなるか、といった懸念は残りますが、まずは一度触ってみることにしました。

Aurora Serverless にはいくつか制限事項があるようです。
それを満たせるように事前に準備を進めていきます。

詳細はこちらを参照: Aurora Serverless の制約事項

1. VPC (Virtual Private Cloud)

Aurora Serverless にパブリックなIPアドレスを割り当てることはできず、おなじVPC内からしかアクセスできないそうです。
なので、ローカルの端末から踏み台等使わずに直接繋ぐことはできなさそうですね。
また、EC2インスタンスををクライアントにする場合は、おなじVPCの中に置いておく必要があります。
(僕はVPCはデフォルトの1個しか使っていないので、この制限は特に意識することはありませんでした。
VPCを複数使っている人は注意が必要です。)

2. AWS PrivateLink エンドポイント

僕がこの辺りの用語に疎いので、ドキュメントをそのまま引用します。
各 Aurora Serverless DB クラスターには、2 つの AWS PrivateLink エンドポイントが必要です。VPC 内の AWS PrivateLink エンドポイントが制限に達した場合、その VPC にそれ以上 Aurora Serverless クラスターを作成することはできません。
要は、VPCにサブネットを2つ以上用意しておく必要があります。
これも自分の場合は元々東京リージョンに3個持っていたので特に作業の必要はありませんでした。

3. セキュリティグループ

ドキュメントの通り、3306番のポートを利用するので、「インバウンド」で、3306番ポートが開通したセキュリティグループが必要になります。
セキュリティグループの設定画面で「タイプ」に「MYSQL/Aurora」を選択すると自動的に必要な設定が入るので作っておきます。

4. クライアント

実際の利用にはDBに接続するクライアントが必要です。
MySQLか、その互換のMariaDBクライアントが入ったインスタンスを同じVPC内に立てておきましょう。

さて、準備が終わった後、実際に DBを作成するのですが、これは簡単でした。
手順にすると多いですが画面に沿って進むだけなのでほぼ迷いません。

1. AWSのコンソールにログインし、RDSの管理画面に移動する。
2. [データベースの作成]をクリック
3. 標準作成を選択 (簡単作成でも良い)
4. エンジンのタイプ は [Amazon Aurora]を選択
5. エディション は [MySQLとの互換性を持つ Amazon Aurora]
6. MySQLのバージョンを選ぶ。
  これは、ドキュメントで指定されているバージョンでないと、次の選択肢からサーバレスが消えるので注意が必要です。

7. データベースの機能 は [サーバーレス]
8. DBクラスター識別子 は何か名前をつける。
9. マスターユーザー名 を指定 (デフォルトは admin)
10. パスワードの設定。 (自分はパスワードの自動作成にしました)
11. キャパシティーの設定を変更。(予算を抑えるため気持ち低めにしました。)
12. [データベースの作成]をクリック

ここまで進むとDBの作成が始まり、数分でDBが出来上がります。
出来上がったら、[認証情報の詳細を表示] から、 adminのパスワードを入手します。
また、同時に エンドポイント もわかるのでこれも記録しておきます。

DBが作成できたら、クライアントの環境から以下のコマンドで接続できます。


mysql -h {エンドポイント} -u {ユーザー名} -p

Enter password: 
と聞かれるのでパスワードを入力する。

ポートはデフォルトの3306を使っているので、 -P 3306 はつける必要ありません。(つけても大丈夫ですが。)

これで、通常のDBとして、使用できるようになりました。

もしDBが作成できているのに接続できなかった場合、セキュリティグループを見直してみてください。
(僕は用意していたセキュリティグループと違うグループが付与されていてしばらく詰まりました。)

あとは、残る課題はお値段ですね。
しばらく使ってみて、どのくらいの金額になるのか様子をみようと思います。

※ 以下、追記
2~3日ほど放置した後、様子を見たらメキメキと課金されていました。触っていない間も稼働しっぱなしだったようです。

設定項目の中に、「数分間アイドル状態のままの場合コンピューティング性能を一時停止する」
というがあり、これにチェックを入れないと、期待してた使っていない間は停止する効果が得られないようです。
てっきりデフォルトだと思っていたので危なかったです。
(請求金額アラートに救われました)

Amazon Comprehend でエンティティ認識

Amazon Comprehend シリーズの3記事目です。今回はエンティテイ認識をやってみます。
キーフレーズ抽出とかなりかぶるのですが、抽出した要素に対して人物なのか場所なのか時間なのかのフラグがつく点がメリットと言えるでしょう。

使うboto3のメソッドは、対象の文章が1つなら、detect_entities() で、
複数(ただし25個まで)なら batch_detect_entities()です。

比較のために前回の記事と全く同じテキストに対して実行してみました。


# サンプルテキスト。
# これで括弧内のテキストが連結されて1つの文字列になる。
text= (
    "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
    "メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。"
    "けれども邪悪に対しては、人一倍に敏感であった。"
    "きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。"
)

import boto3
comprehend = boto3.client("comprehend")
result = comprehend.detect_key_phrases(Text=text, LanguageCode="ja")

import boto3
comprehend = boto3.client("comprehend")

entities = comprehend.detect_entities(Text=text, LanguageCode="ja")
for entity in entities["Entities"]:
    print(entity)

"""
{'Score': 0.9998800754547119, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 0, 'EndOffset': 3}
{'Score': 0.9998433589935303, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 36, 'EndOffset': 39}
{'Score': 0.9998514652252197, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 49, 'EndOffset': 52}
{'Score': 0.7808283567428589, 'Type': 'QUANTITY', 'Text': '人一倍', 'BeginOffset': 90, 'EndOffset': 93}
{'Score': 0.6928854584693909, 'Type': 'DATE', 'Text': '未明', 'BeginOffset': 104, 'EndOffset': 106}
{'Score': 0.9995416402816772, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 106, 'EndOffset': 109}
{'Score': 0.506858766078949, 'Type': 'LOCATION', 'Text': '十里', 'BeginOffset': 124, 'EndOffset': 126}
{'Score': 0.9970796704292297, 'Type': 'LOCATION', 'Text': 'シラクス', 'BeginOffset': 132, 'EndOffset': 136}
{'Score': 0.6032651662826538, 'Type': 'LOCATION', 'Text': '市', 'BeginOffset': 137, 'EndOffset': 138}
"""

キーフレーズ抽出に比べて抽出された単語は少ないですが、 PERSON とか、 LOCATION といったTypeが付与されています。
ドキュメントによると、Typeは次の値を取りうる様です。

– ‘PERSON’
– ‘LOCATION’
– ‘ORGANIZATION’
– ‘COMMERCIAL_ITEM’
– ‘EVENT’
– ‘DATE’
– ‘QUANTITY’
– ‘TITLE’
– ‘OTHER’,

Amazon Comprehend でキーフレーズ抽出

前回の記事に続いて Amazon Comprehend の話です。
今度はキーフレーズ抽出をやってみます。

ドキュメントは同じところを参照します。
Comprehend — Boto3 Docs 1.14.32 documentation

Amazon Comprehend の特徴ページによると、
キーフレーズ抽出 API は、キーフレーズまたは会話のポイント、およびそれがキーフレーズであることを裏付ける信頼性スコアを返します。
とのことです。

早速やってみましょう。使うboto3のメソッドは、
detect_key_phrases() か、batch_detect_key_phrases()です。
それぞれ、単一テキストを対象とするか、テキストのリストを対象とするかの違いです。
ほとんど同じなので、今回は1テキストだけやってみることにしました。
サンプルにはいつのもメロスの最初の方の文章を使います。(全テキストで実行したら5000byteの制限によりエラーになりました。)


# サンプルテキスト。
# これで括弧内のテキストが連結されて1つの文字列になる。
text= (
    "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
    "メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。"
    "けれども邪悪に対しては、人一倍に敏感であった。"
    "きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。"
)

import boto3
comprehend = boto3.client("comprehend")
result = comprehend.detect_key_phrases(Text=text, LanguageCode="ja")

for key_phrase in result["KeyPhrases"]:
    print(key_phrase)

"""
{'Score': 0.9999992847442627, 'Text': 'メロス', 'BeginOffset': 0, 'EndOffset': 3}
{'Score': 0.5104176998138428, 'Text': 'かの', 'BeginOffset': 12, 'EndOffset': 14}
{'Score': 0.9680508971214294, 'Text': '邪智暴虐の王', 'BeginOffset': 14, 'EndOffset': 20}
{'Score': 0.9999995231628418, 'Text': 'メロス', 'BeginOffset': 36, 'EndOffset': 39}
{'Score': 0.9999402761459351, 'Text': '政治', 'BeginOffset': 41, 'EndOffset': 43}
{'Score': 0.9999988079071045, 'Text': 'メロス', 'BeginOffset': 49, 'EndOffset': 52}
{'Score': 0.9999657869338989, 'Text': '村の牧人', 'BeginOffset': 54, 'EndOffset': 58}
{'Score': 0.9999977350234985, 'Text': '笛', 'BeginOffset': 62, 'EndOffset': 63}
{'Score': 0.9999823570251465, 'Text': '羊', 'BeginOffset': 67, 'EndOffset': 68}
{'Score': 0.9975282549858093, 'Text': '人一倍', 'BeginOffset': 90, 'EndOffset': 93}
{'Score': 0.8310525417327881, 'Text': 'きょう未明', 'BeginOffset': 101, 'EndOffset': 106}
{'Score': 0.9997578263282776, 'Text': 'メロス', 'BeginOffset': 106, 'EndOffset': 109}
{'Score': 0.9997960925102234, 'Text': '村', 'BeginOffset': 110, 'EndOffset': 111}
{'Score': 0.9999336004257202, 'Text': '野', 'BeginOffset': 116, 'EndOffset': 117}
{'Score': 0.9991182088851929, 'Text': '十里', 'BeginOffset': 124, 'EndOffset': 126}
{'Score': 0.9819957613945007, 'Text': '此のシラクスの市', 'BeginOffset': 130, 'EndOffset': 138}
"""

ものすごく簡単に使えましたね。
一応重要な名詞は拾えてる様な気がしますし、王様は「邪智暴虐の王」として抽出できています。
ただ抽出したテキストだけなど意味がわからないので使い道に悩むところです。
BeginOffset と、 EndOffset は、その該当テキストを切り出すスライスに使える様です。

こんな感じです。


print(text[14: 20])
# 邪智暴虐の王

print(text[54: 58])
# 村の牧人