Amazon RDS(Aurora)で日本語文字列や絵文字を使えるようにする

RDSのデフォルトの設定のクラスタで、データベースとテーブルを特に文字コードを設定せずに作成した場合、
VARCHAR(文字列)型の列を作ってもそこに日本語などのマルチバイト文字を格納することはできません。

カラムか、テーブルか、データベース単位で設定してもいいのですが、パラメーターグループを使って設定する方法を紹介します。
Aurora(正確にはそれが互換性を持っているMySQL)では、文字コード(Character Set)と照合順序(Collation)をそれぞれ独立した設定として持っています。
照合順序というのはソート順やアルファベットの大文字小文字を同一視するかどうかといったルールのセットです。

文字コードは日本語を使いたいのであればUTF8を使えるように設定する必要がありますが、
歴史的な経緯により、MySQLのUTF8には、utf8とutf8mb4の2種類があります。
utf8には3バイトまでの文字しか含まれておらず、絵文字等の4バイト文字も使えるようにするためにはutf8mb4を設定する必要があります。

参考: 10.1.10.7 utf8mb4 文字セット (4 バイトの UTF-8 Unicode エンコーディング)

さて、とりあえず何も設定しなかった場合の設定を見ておきましょう。


MySQL [(none)]> SHOW VARIABLES LIKE 'char%';
+--------------------------+-------------------------------------------------------------------------+
| Variable_name            | Value                                                                   |
+--------------------------+-------------------------------------------------------------------------+
| character_set_client     | utf8                                                                    |
| character_set_connection | utf8                                                                    |
| character_set_database   | latin1                                                                  |
| character_set_filesystem | binary                                                                  |
| character_set_results    | utf8                                                                    |
| character_set_server     | latin1                                                                  |
| character_set_system     | utf8                                                                    |
| character_sets_dir       | /rdsdbbin/oscar-5.7.serverless_mysql_aurora.2.08.3.38.0/share/charsets/ |
+--------------------------+-------------------------------------------------------------------------+
8 rows in set (0.01 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'coll%';
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | utf8_general_ci   |
| collation_database   | latin1_swedish_ci |
| collation_server     | latin1_swedish_ci |
+----------------------+-------------------+
3 rows in set (0.01 sec)

character_set_client, character_set_connection, character_set_results
はデフォルトでは utf8 になっていますが、 character_set_database と、 character_set_server が、latin1 になっています。
絵文字含む日本語文字を使うためにはこれら5つをutf8mb4に設定する必要があります。

ここからがややこしいことなのですが、RDSのパラメーターグループではこの5つをそれぞれ設定できるようになっているのに、
character_set_client, character_set_connection, character_set_results にはそれが反映されません。
そして、 character_set_database には character_set_server に設定した値が入ります。
反映されない3つは、クライアント側で設定する必要があるようです。

また、collation の方も、 collation_connection には設定した値が反映されず、
collation_server に設定した値が、collation_server と collation_database に反映されます。

これを踏まえて設定していきます。まず、RDSのパラメーターグループには次のふたつを設定します。

character_set_server: utf8mb4
collation_database: utf8mb4_bin

この段階で、設定は次のようになります。


MySQL [(none)]> SHOW VARIABLES LIKE 'char%';
+--------------------------+-------------------------------------------------------------------------+
| Variable_name            | Value                                                                   |
+--------------------------+-------------------------------------------------------------------------+
| character_set_client     | utf8                                                                    |
| character_set_connection | utf8                                                                    |
| character_set_database   | utf8mb4                                                                 |
| character_set_filesystem | binary                                                                  |
| character_set_results    | utf8                                                                    |
| character_set_server     | utf8mb4                                                                 |
| character_set_system     | utf8                                                                    |
| character_sets_dir       | /rdsdbbin/oscar-5.7.serverless_mysql_aurora.2.08.3.38.0/share/charsets/ |
+--------------------------+-------------------------------------------------------------------------+
8 rows in set (0.01 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'coll%';
+----------------------+-----------------+
| Variable_name        | Value           |
+----------------------+-----------------+
| collation_connection | utf8_general_ci |
| collation_database   | utf8mb4_bin     |
| collation_server     | utf8mb4_bin     |
+----------------------+-----------------+
3 rows in set (0.00 sec)

次にクライアント側の設定です。
僕は、Amazon Linux 2 で標準のRDSになった、MariaDBを利用しています。
設定ファイルは、 /etc/my.cnf なのですが、
その中を見ると、 !includedir /etc/my.cnf.d となっていて、他のファイルを読み込んでいます。

/etc/my.cnf.d/client.cnf と言うファイルの中に、 [client]と言うセクションがあるのでそこに設定します。


$ sudo vim /etc/my.cnf.d/client.cnf

[client]
# 以下の1行を追加
default-character-set = utf8mb4

MySQL [(none)]> SHOW VARIABLES LIKE 'char%';
+--------------------------+-------------------------------------------------------------------------+
| Variable_name            | Value                                                                   |
+--------------------------+-------------------------------------------------------------------------+
| character_set_client     | utf8mb4                                                                 |
| character_set_connection | utf8mb4                                                                 |
| character_set_database   | utf8mb4                                                                 |
| character_set_filesystem | binary                                                                  |
| character_set_results    | utf8mb4                                                                 |
| character_set_server     | utf8mb4                                                                 |
| character_set_system     | utf8                                                                    |
| character_sets_dir       | /rdsdbbin/oscar-5.7.serverless_mysql_aurora.2.08.3.38.0/share/charsets/ |
+--------------------------+-------------------------------------------------------------------------+
8 rows in set (0.00 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'coll%';
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_bin        |
| collation_server     | utf8mb4_bin        |
+----------------------+--------------------+
3 rows in set (0.01 sec)

これで、ほぼ設定できました。
実用上これで困ることなく使うことができると思います。

後1点, collation_connection の設定が utf8mb4_bin にならずに、 utf8mb4_general_ci になってしまうのですが、
どなたか collation_connection の設定を utf8mb4_bin にする方法をご存知の方がいらしたら教えていただけないでしょうか。

そのセッションに限った設定であれば、
SET collation_connection = utf8mb4_bin;
で設定できるのですが、接続を切ると元に戻ってしまいます。

また、
SET GLOBAL collation_connection = utf8mb4_bin;
は RDS では実行できないようです。

先述の通り、パラメーターグループで設定しても反映されません。
あまり困ることもないのですが、ここだけ設定がズレていて気持ち悪いので可能であればutf8mb4_binに揃えたいです。

RDSのタイムゾーンを日本時間(Asia/Tokyo)にする

EC2の時刻設定の話を書いたついでに、RDSの時刻設定を変える方法も紹介しておきます。
(ちなみに僕が使ってるRDSは Amazon Aurora Serverless です。)

結論から言うと、RDSにはパラメーターグループと呼ばれる設定値のコンテナのようなものがあり、
それを使ってシステム環境変数を設定します。

ドキュメントはここかな。(これを読むより実際に動かした方がわかりやすい)
参考: DB パラメータグループを使用する

まず、デフォルトの設定を確認しておきましょう。
何も設定せずに、RDSのクラスタを立ち上げ、現在時刻を表示してみます。

これ実行したのは 2021-03-26 23:48:09 です。


MySQL [(none)]> SELECT NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2021-03-26 14:48:09 |
+---------------------+
1 row in set (0.01 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'time_%';
+---------------+-------------------+
| Variable_name | Value             |
+---------------+-------------------+
| time_format   | %H:%i:%s          |
| time_zone     | SYSTEM            |
| timestamp     | 1616770134.005551 |
+---------------+-------------------+
3 rows in set (0.01 sec)

time_zone は SYSTEM となっていますが9時間ずれていてUTCだとわかりますね。
これを修正していきます。

– RDSの管理画面の左ペインから、パラメーターグループを選択する
– パラメータグループの作成 を押下
– 以下のように項目選択
パラメータグループファミリー aurora-mysql5.7
タイプ DB Cluster Parameter Group
グループ名 任意
説明 任意
– 作成 を押下
– 作成したパラメーターグループを選択し、パラメーターグループアクションから編集を選択
– time_zone を探して、 Asia/Tokyo を選択
– 変更を保存
– データベースの一覧に戻り、設定を変更したいデータベースを選択
– 変更を押下
– DB クラスターのパラメータグループ に先ほど作ったパラメーターグループを選択し、続行を押下
– 変更を適用するタイミング は すぐに適用 を選択
– クラスターの変更 を押下

この後、セッションを切ってしばらく待った後繋ぎ直すと反映されています。


MySQL [(none)]> SELECT NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2021-03-26 23:59:23 |
+---------------------+
1 row in set (0.01 sec)

MySQL [(none)]> SHOW VARIABLES LIKE 'time_%';
+---------------+-------------------+
| Variable_name | Value             |
+---------------+-------------------+
| time_format   | %H:%i:%s          |
| time_zone     | Asia/Tokyo        |
| timestamp     | 1616770768.009869 |
+---------------+-------------------+
3 rows in set (0.00 sec)

Aurora Serverless と言うより RDS では、他のステム変数も同じようにパラメーターグループを使って編集できます。

EC2(Amazon Linux 2)の時刻設定を日本時間にする

初期設定ではUTCになっている、EC2の時刻設定を東京時間にします。
昔は設定ファイルを書き換えたりリンクを貼ったりと若干手間だったような覚えがあるのですが、
Amazon Linux 2 では、timedatectl という便利なコマンドがあり、これを使って設定ができるようです。

まず、
timedatectl list-timezones
で設定可能なタイムゾーンの一覧を取得できます。
東京時間があることを確認しておきましょう。


$ timedatectl list-timezones | grep Tokyo
Asia/Tokyo

次に、TimeZoneを設定し、確認します。


$ timedatectl set-timezone Asia/Tokyo
$ timedatectl status
      Local time: 木 2021-03-18 22:58:37 JST
  Universal time: 木 2021-03-18 13:58:37 UTC
        RTC time: 木 2021-03-18 13:58:37
       Time zone: Asia/Tokyo (JST, +0900)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: n/a

これで設定できました。

あとは念の為、サーバーを再起動しておきましょう。
$ sudo reboot

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("インスタンス停止完了")