とてもいい加減なLAMP環境の構築

LAMP環境を作ってみよう！ というこで、何も考えずノリと思い付きで作りました。

# VM の準備

vagrant を使います。
vagrant の説明は省きますが、過去に記事を書いています。
[Windows10 で手軽にVMを作る（Vagrant）](https://qiita.com/landwarrior/items/8dc29d33142c0897ba04)

自分の環境のまま書くと、以下のようにやりました。

```console
d:\VirtualMachines\Vagrant\lamp_test>vagrant init bento/centos-7.7
d:\VirtualMachines\Vagrant\lamp_test>vagrant up
```

本当は IP アドレスの割り当てもしてますが、 Vagrantfile の説明を飛ばすためにここでは `vagrant ssh` で VM に接続して続けています。

```console
d:\VirtualMachines\Vagrant\lamp_test>vagrant ssh
```

※ IP アドレスの割り当ては後ほど説明します

# Apache をインストール

インストール作業など、 root 権限が必要なのでまずは root ユーザーにスイッチします。
以降の作業はすべてこの root ユーザーで作業します。

```console
sudo su
```

root ユーザーになっているかどうかは、コンソール上の `@` の左側で判断できます。

```console
[root@localhost ~]#
```

でもって、何も考えず yum instal します。
`-y` は、インストールを常に許可するオプションです。

```console
yum install -y httpd
```

Apache はサービス名としては `httpd` ですね。
インストールされたバージョンを確認するためのコマンドと結果は以下の通りです。

```console
[root@localhost vagrant]# rpm -qa | grep httpd
httpd-tools-2.4.6-90.el7.centos.x86_64
httpd-2.4.6-90.el7.centos.x86_64
```

簡単に説明すると、 `rpm -qa` はインストールされている全てのパッケージを一覧表示させます。
それをパイプ(`|`)でつなげて、 `grep ` で httpd だけに絞っています。

# mysql をインストール

いきなりウソつきました。
CentOS7 では MySQL を入れるためにはリポジトリの追加が必要なので、デフォルトだと MariaDB になっちゃいますね。
リポジトリとは、パッケージの貯蔵庫、というイメージです。
標準で備えている貯蔵庫には MySQL がなく、別の貯蔵庫を参照できるように設定してその貯蔵庫から MySQL をもらってくるようにしないと入らない、ということですね。
今回は何も考えず、以下で入れます。

```console
yum install -y mariadb-server
```

インストールしたら、 DB サーバーが標準で扱う文字コードを変更しておきます。
以下のコマンドで設定ファイルを編集します。

```console
vi /etc/my.cnf.d/server.cnf
```

以下を、 `[server]` の配下の一番下にでも追記します。

```console:/etc/my.cnf.d/server.cnf
character-set-server=utf8
```

そして、起動設定です。

```console
systemctl enable mariadb
systemctl start mariadb
```

一応、サーバーの設定の個所があってるか見てみます。

```console
[root@localhost vagrant]# mysql -e "show variables like '%char%'"
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | utf8                       |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | utf8                       |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
```

`mysql` コマンドを単体で実行すると、 root ユーザーで mariadb に接続します。
`-e` オプションにより、 SQL を指定して実行させ、その結果を得ることができます。

`[mysqld]` 配下じゃないとだめなのかも？ と思いましたが、なんかうまくいっているので気にしないでおきます。
ここでは、 `show variables like '%char%'` という　SQL を実行し、 MariaDB 上の環境変数のうち `char` という文字列を含むもの（部分一致）を参照しています。

次に、テスト用の DB とユーザーを追加しておきます。

```console
mysql -e "create database mydb"
mysql -e "grant all privileges on *.* to test@localhost identified by 'test' with grant option"
```

同じく、 `create database` で `mydb` という名前のデータベースを作成し、 `grant` 文によりユーザーを作成しています。
ただし、新しい MySQL8 ではこの方法でユーザーが作成できず、事前に `create user` をしておく必要があるとか・・・
ここでは、上記の説明だけします。
`grant all privileges` で、ユーザーに全権限を付与します。
`on *.*` は、 `{DB名}.{テーブル名}` の書式で、 `*` はワイルドカードなのですべてが対象になります。
つまり、すべてのデータベースのすべてのテーブルに対して、全ての権限を付与していることになります。
`to test@localhost` は、そのまま `test@localhost` ユーザーに対して権限を付与していることになります。
書式は、 `{ユーザー名}@{接続元ホスト名}` で、 localhost 以外にも、全てのホストからアクセス可能にするためには `test@'%'` と記載してユーザー追加をもう一度実行します。
つまり、ユーザーは接続元ホストごとに作成する必要があるということです。
ホスト名は `%` を使用する場合はシングルクォートでくくる必要があったはず。
他にも、例えば `192.168.%` のように、特定のセグメントからの接続を許可することもできます。
`identified by 'test'` は、ユーザーのパスワードを指定しています。
`with grant option` は、確かこのユーザーがほかのユーザーに対しても同じ権限を付与可能にするための設定だったと思います。
間違ってたらすみません😛（調べてない）

# PHP をインストール

こちらも何も考えず、以下で入れました。

```console
yum install -y php php-pdo php-mysql
```

PDO を使うためにいろいろ調べたので、結果的に上記コマンドで必要なものが入ることが分かりました。
インストールされたものは、以下の通りです。

```console
[root@localhost ~]# rpm -qa | grep php
php-common-5.4.16-46.1.el7_7.x86_64
php-pdo-5.4.16-46.1.el7_7.x86_64
php-5.4.16-46.1.el7_7.x86_64
php-cli-5.4.16-46.1.el7_7.x86_64
php-mysql-5.4.16-46.1.el7_7.x86_64
```

うーん、古い。

PHP の設定をするため、 php.ini を書き換えます。

```php:/etc/php.ini
// コメントアウト（;）を削除して、以下のように書き換えタイムゾーンの設定をします
date.timezone = Asia/Tokyo
// 以下を末尾に追記して PDO で MariaDB にアクセス可能にします
extension=pdo_mysql.so
```

これで PDO で MariaDB へアクセスできるはず。
ちなみに、 PDO とは PHP から 各種 DB へ接続するためのドライバーみたいなものです。
Windows で言うところの ODBC みたいな。
PDO を使用する利点としては、 ODBC もそうだったと思いますが、 DB の種類によらず同じコードで DB を操作できることです。
接続文字列は変更する必要がありますが、その後 DB の操作は同じようでできます。
PDO を使わない場合、 MySQL 専用のコマンドを使ったりするため DB の種類を変更する時にコードを全て書き直す必要があります。
PDO なら、 MySQL から PostgreSQL に変えたとしても、接続文字列を変更するだけでコードの修正が必要なくなります。
ちなみに Perl では DBI というモジュールが PDO とほぼ同じですね。
書き方もそっくり。
他の言語は同じようなのあったかな・・・？

# Apache で PHP を使えるようにする

ちょっとハマりました。
まず、 httpd にモジュールを追加する場合、設定ファイルに以下のように `LoadModule` の記載をします。

```console
LoadModule {モジュールにつける名前} {モジュールのパス}
```

`モジュールにつける名前` の部分はもしかしたら名前に決まりがあるのかも。
自分の勘違いでなんでもいいと思っているだけだったら、ごめんなさい。

設定ファイルは、標準的なものは `/etc/httpd/conf/httpd.conf` になります。
この設定ファイルの中で、別の設定ファイルを読み込む設定がしてあります。
それが、以下になります。

```console
[root@localhost ~]# cat /etc/httpd/conf/httpd.conf | grep Include | grep -v "#"
Include conf.modules.d/*.conf
IncludeOptional conf.d/*.conf
```

`cat` コマンドでファイルの中身を標準出力に表示させ、それを `grep` で `Include` を含む文字列だけに絞り、かつもう一度 `grep` を使用し、 `-v` オプションで“含まないもの”を指定することになり、 `#` を含むものを除外しています。
相対パスなので、それぞれ `/etc/httpd/conf.modules.d/*.conf` と `/etc/httpd/conf.d/*.conf` を意味しているんだと思います。
相対パスから絶対パスを割り出す時、割とフィーリングで判断しています😛

今回はモジュールの追加を記載したいので、 `/etc/httpd/conf.modules.d/` 配下にありそうですね。

そして、ここで謎が。
httpd の設定ファイルで `LoadModule` を記載するものは複数あるんでしたっけ？ 完全に忘れた。
それっぽいものを一覧表示させてみたものが、以下になります。

```console
[root@localhost ~]# ls -l /etc/httpd/conf.modules.d/
total 32
-rw-r--r--. 1 root root 3739 Aug  6  2019 00-base.conf
-rw-r--r--. 1 root root  139 Aug  6  2019 00-dav.conf
-rw-r--r--. 1 root root   41 Aug  6  2019 00-lua.conf
-rw-r--r--. 1 root root  742 Aug  6  2019 00-mpm.conf
-rw-r--r--. 1 root root  957 Aug  6  2019 00-proxy.conf
-rw-r--r--. 1 root root   88 Aug  6  2019 00-systemd.conf
-rw-r--r--. 1 root root  451 Aug  6  2019 01-cgi.conf
-rw-r--r--. 1 root root  216 Nov  1 16:06 10-php.conf
```

一応説明すると、 `ls` コマンドは Windows の DOS コマンドで言うところの `dir` です。
ディレクトリ内を一覧表示するコマンドですね。
`-l` オプションで、詳細表示します。
引数として確認したいディレクトリを指定しています。

`10-php.conf` なるものがある・・・
中身はこんな感じ。

```console:/etc/httpd/conf.module.d/10-php.conf
<IfModule prefork.c>
  LoadModule php5_module modules/libphp5.so
</IfModule>
```

先に説明した通り、ここでは `php5_module` という名前で `modules/libphp5.so` を読み込んでいます。
ここは相対パスでの表記になっていますが、実際には `/usr/lib64/httpd/modules/libphp5.so` です。
ちなみにこれはフィーリングでは分からなかったので、 `find` コマンドを使用して以下のように調べました。

```console
[root@localhost ~]# find / -name libphp5.so
/usr/lib64/httpd/modules/libphp5.so
```

コマンドの書式は以下の通りです。

```console
find {ディレクトリ} -name {ファイル名（完全一致）}
```

今回はルードディレクトリである `/` を指定し（Windows でいう、 `C:\` だと思ってもらって大丈夫です）、探したいファイル名は `libphp5.so` としています。
なお、ファイル名は完全一致ですが、ワイルドカード（ `*` ）も使えますし、たしか正規表現も使えたと思います。

`10-php.conf` の話に戻ります。
上の方で示したものは `IfModule` ディレクティブというやつだったと思いますが、後ろに続く `prefork.c` というものがあれば、くくっている中身が実行されるようになっています。
つまり、 `prefork.c` があれば `LoadModule` が実行されます。
`prefork.c` なんてないと思っていたので `00-base.conf` にでも `LoadModule` の設定を書く必要があるのかと思っていたのですが、どうやら書かなくても上記のまま使えているようです。
知りませんでした・・・
つまり、何もしなくても httpd は PHP を扱えるようになっている、ということです。
PHP をインストールした時に一緒に設定されたのかしら。

続きまして、 `httpd.conf` の末尾に以下を追記します。

```conf:/etc/httpd/conf/httpd.conf
<IfModule php5_module>
    PHPINIDir "/etc/php.ini"
</IfModule>
<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>
```

先に説明した通り、 `php5_module` が読み込まれていれば `php.ini` の指定が有効になるようにしています。
`PHPINIDir` の設定がなくても、標準的な場所に php.ini があればそれを読み込むため問題にならないこともあります。
Window でいえば、 C:\Windows 配下など、あらかじめ環境変数 Path が参照している場所に php.ini があればそれを使用するのですが、例えば C:\Windows\php.ini を準備したけど、 C:\path\to\php.ini を読み込ませたい時に PHPINIDir の設定をする、ということです。
Linux だと /etc 配下に設定ファイルがあるので今回のケースでは書かなくても問題ないかもしれませんね。
脱線しましたが、もう一つの `FilesMatch` で、ファイルの末尾が `.php` で終わっているものは php のファイルであると判断させるための設定をしています。
つまり、拡張子が `.php` であればいいわけですね。

そして最後に、そもそも httpd はどこのファイルを参照してブラウザ上で表示するのかということですが。
設定は以下になります。

```console:/etc/httpd/conf/httpd.conf
～中略～
DocumentRoot "/var/www/html"
～中略～
```

`/etc/httpd/conf/httpd.conf` の中に `DocumentRoot` というものがあり、ここでディレクトリを指定しています。
標準では、 `/var/www/html` 配下に置いたファイルをブラウザで参照できるようになっています。
この配下に html ファイルや php のファイルを配置すれば、とりあえずはブラウザでアクセス可能になります。

ではでは、 httpd の起動設定をします。

```console
systemctl enable httpd
systemctl start httpd
```

# ブラウザで確認

の前に、 PHP のファイルを作成しておきます。

```console
touch /var/www/html/phpinfo.php
```

`touch` コマンドは、ファイルがなければ空ファイルを作成します。
ファイルがある場合は、ファイルの更新日時が touch した時刻になります。
そして `vi` で上記ファイルを編集し、以下を記載します。

```php:/var/www/html/phpinfo.php
<?php
phpinfo();
```

PHP の説明は省きます・・・
ファイルの準備ができたら、ブラウザでアクセスしてみましょう。
最初にすっ飛ばしましたが、自分の環境では `d:\VirtualMachines\Vagrant\lamp_test\Vagrantfile` にて、以下のように IP アドレスの設定を入れています。

```conf:d：\VirtualMachines\Vagrant\lamp_test\Vagrantfile
# 先頭のコメントアウトを外して IP アドレスを指定する
config.vm.network "private_network", ip: "192.168.33.111"
```

上記のように設定したので、ブラウザでアクセスするのは以下の URL になります。
`http://192.168.33.111/phpinfo.php`
PHP の設定情報が表示されたら成功です。

# PHP で DB から情報を取得してWebに表示する

まずはテーブルの追加とデータ登録です。
`-u` オプションでユーザー名を指定し、 `-p` オプションでパスワードを指定し、 `-D` オプションで接続する DB を指定し、 `-e` で実行する SQL を記載しています。

```console
mysql -utest -ptest -Dmydb -e "create table user (user_id int, user_name varchar(32), primary key (user_id));"
mysql -utest -ptest -Dmydb -e "insert into user values (1, 'test_user');"
```

テーブル追加とそのテーブルに対するデータ登録ですね。

そして、次はそこにアクセスする PHP のファイルを作成します。
といっても、 HTML に PHP のコードを埋め込んだものを使用します。

`vi /var/www/html/test.php` でファイルの生成と中身の記述をいっぺんにしちゃいましょう。

```php:/var/www/html/test.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>test page</title>
</head>
<body>
<h1>Hello, World!</h1>
<div>
<?php 
$db = 'mysql:dbname=mydb;host=127.0.0.1;charset=utf8';
$user = 'test';
$pass = 'test';
$dbh = new PDO($db, $user, $pass) or die('接続できませんでした(´･ω･｀)');
$st = $dbh->prepare('select * from user');
$st->execute();
$data = $st->fetchAll();
for ($i = 0; $i < count($data); $i++) {
  echo "user_id: {$data[$i]['user_id']}, user_name: {$data[$i]['user_name']}<br>";
}
?>
</div>
</body>
</html>
```

作成したら、ブラウザでアクセスしてみます。
自分の環境では、以下をブラウザで開きます。
`http://192.168.33.111/test.php`

ブラウザに `Hello, World!` と、 DB に登録したデータが出てきたら成功です。
データが 1 件しかないと寂しいので、 DB につないでデータを追加してみましょう。
以下のコマンドで DB に接続します。

```console
mysql -utest -ptest -Dmydb
```

追加するデータは、例えば以下にします。

```sql
insert into user values
(2, 'テスト太郎')
, (3, 'ほげ')
, (4, 'ふが')
, (5, 'foo')
, (6, 'bar')
;
```

SQL を実行して正常終了したら、キーボードで `Ctrl + D` を押すと exit できるのでお勧めです。
MySQL などだけではなく、CentOSからもログアウトできます。
ログアウトの共通コマンド、って感じなんですかね？ たまたま間違えて入力して発見したので、詳しくは知りません😛
追加したら、またブラウザでアクセスしてみます。
すると、ちゃんと全部出ると思います。

# おわり

思いついたまま駆け足で作成した記事に対して説明を盛りつけました。
内容が倍増したかも・・・
分からないことがあれば自身で調べていただくか、コメントを頂ければ分かる範囲で説明しますのでお気軽にどうぞ👍

# 付録

おわりといいつつ終わらない。
駆け足で LAMP 環境を作成しましたが、なぜ作ったかというと、インフラ周りの勉強のためです。
今回記事に書いた構築手順では出てきませんでしたが、ミドルウェアを複数使って構築しているので、当然それらについての知識も必要なわけです。
実際に運用されるシステムが障害を起こした場合、その調査も出来なければいけませんよね。
その説明を、付録として記載しておきます。

### Apache のログ

以下にあります。
`/var/log/httpd`
`httpd.conf` などでログファイルをカスタマイズしていなければ、 `access_log` と `error_log` があります。
`access_log` は、名前の通り、アクセスされたコンテキストパス（FQDN 以降の部分。例えば、 `http://localhost/test.php` にアクセスした場合は `/test.php` の部分）を調べる時などに使用します。
アクセス日時や接続元 IP アドレスも記載されるため、悪意のある攻撃を調べる際に見たりもします。
よく見るのは、海外の IP アドレスから /phpMyAdmin/index.php へのアクセスとかですかね。
phpMyAdmin は、 PHP 環境でよく使う、 MySQL にブラウザ経由でアクセスして DB 操作ができるツールです。
便利な反面、 ACL（アクセスコントロールリスト）といった、特定の IP アドレスからしかアクセスできないようにする設定などを入れておかないと誰でもログイン可能になってしまいます。
DoS攻撃対策などで1秒間に何回以上のアクセスがあったら遮断、という設定を Apache に入れていても、 DDoS攻撃なら複数の IP アドレスから攻撃されますし、さらにブルートフォース攻撃（総当たり攻撃）で突破されかねません。
おとなしくサーバー上で iptables などで ACL をするか、個人的にはもっと上位に UTM（統合脅威管理）機器を導入して、そもそもそういった不穏な通信を上位で遮断しておくべきかと思います。
・・・脱線した。
`access_log` は、ほかにもどのページにどれくらいのアクセスがあったかなど、 Analytics 的な使い方をしたりもします。
`error_log` は、名前の通り、エラーが発生したら記載されます。
ただし、 Apache 上で実行しているアプリケーションが標準出力に何かを出力したら、それは Apache が拾って `error_log` に書いてくれたりもするので、必ずしも error が発生しているとは限りません。
そこは、アプリケーションの実装が悪いので Apache のせいではありませんが・・・
アプリケーションが正しく動いてなさそう、と思ったらまずは見てみるのが `error_log` だと考えてもいいかもしれないですね。

### MySQL(MariaDB) のログ

`/var/log/mysql` もしくは `/var/log/mariadb` ですかね。
今回の記事の例では、 `/var/log/mariadb` でした。
デフォルトではクエリログ（実行された SQL が記載されるログ）は出ません。（出す場合は別ファイルです）
出す設定を入れるとパフォーマンスに影響するのでお勧めしません。
（ファイル I/O がボトルネックになり、アプリケーションの動作が遅くなる）
忘れてしまいましたが、たしか mysql だとインストール後の初回実行時に root ユーザーのパスワードがログファイルに記載されるため、ログファイルを見ないと root ユーザーのパスワードが分からなくてログイン不能に陥る、みたいなことがあったような。
今回の記事では mariadb だったので、 root ユーザーのパスワードを意識しなくても接続できました。
ちなみに、 `mysql` コマンドでローカルの DB にアクセスる場合、ソケット経由での接続になります。
ソケットとは何ぞや？ という感じですが、ファイル自体はデフォルトだと `/var/lib/mysql/mysql.sock` になります。
なお、ほかのホストから接続する場合や `-h` オプションでホスト指定をする場合は、 プロトコルは tcp で、接続に使うポートは 3306 になります。だったはず・・・（調べながら記載しているわけではないので自信ないです）
簡単に言うと、 localhost 接続の場合だけソケット経由での通信になる、ということですね。
オプションで `-h 127.0.0.1` を指定した場合はソケット経由にならないです。
MySQL や MariaDB のバージョンによっても違うかもしれないので、あくまで参考程度に。
DB のログは、経験上そこまで見たことはないですね。
OOM Killer（メモリが足りなくなって、 OS が勝手にメモリを食うアプリを強制停止してしまう処理）が発動して MySQL(MariaDB) が殺されたようなら見る価値あるかも。
あんまりいい説明じゃなくてすみません。

### PHP のログ

今回の例ではロガーオブジェクトを生成していないので、 PHP がデフォルトでどこかにログを吐く、ということはありません。
せいぜい、 Apache が良しなに拾ってくれて `/var/log/httpd/error_log` に記載される程度です。
ロギングはアプリケーション開発者の責務です。
適切なログ出力を追加したり、エラー発生時は障害内容やリカバリに必要な情報を適切にロギングする必要がありますね。
アプリケーションのエラーが一番発生しやすいのに、一番ロギングが難しい。
自分で実装しないといけないので、開発者に依存してしまうのが辛いところですね。
これは PHP に限らずすべてのアプリケーションで言えることです。

付録書くのに時間使い過ぎた・・・
また、思いついたら記載したいと思います。