【ペチオブ】仮想環境ハンズオン 第3回 Vagrant編

## シリーズ記事

1. [Linux基礎編](https://qiita.com/ucan-lab/items/683658dbeb6f81c4c73f)
2. [VirtualBoxハンズオン](https://qiita.com/ucan-lab/items/d06f9ab9946598b53421)
3. **Vagrantハンズオン**
4. [Dockerハンズオン](https://qiita.com/ucan-lab/items/6e6a7d7d40c7287b6af9)
5. [Docker Composeハンズオン](https://qiita.com/ucan-lab/items/6809c83380af9afac216)

## 今回の目的

今回の目的は、VirtualBox上にVagrantを使ってUbuntu OSのインストール、PHPの実行環境の構築を行います。

## YouTube版 【ペチオブ】仮想環境ハンズオン 第3回 Vagrant編

[![【ペチオブ】仮想環境ハンズオン 第3回 Vagrant編](http://img.youtube.com/vi/4n1Jd3WTt0U/0.jpg)](https://www.youtube.com/watch?v=4n1Jd3WTt0U)

動画を高評価、チャンネル登録いただけるとより多くの方へオススメ表示されるので良いと思った方はご協力いただけるとありがたいです🙏

## 完成版

https://github.com/ucan-lab/infra-handson-vagrant

完成版はGitHubにプッシュしてます。

## 準備

ハンズオン時間短縮のため、予めboxファイルをダウンロードまで行います。

### VirtualBox のインストール

https://www.virtualbox.org/wiki/Downloads

公式サイトからインストーラをダウンロードできます。
もしくはHomebrewからインストールしてください。

```
$ brew install virtualbox

$ VBoxManage -v
6.1.16r140961
```

### Vagrant のインストール

https://www.vagrantup.com/downloads.html

公式サイトからインストーラをダウンロードできます。
もしくはHomebrewからインストールしてください。

```
$ brew install vagrant

$ vagrant -v
Vagrant 2.2.14
```

### Vagrantプラグイン のインストール

```
$ vagrant plugin install vagrant-share vagrant-vbguest

$ vagrant plugin list
vagrant-share (1.1.11, global)
vagrant-vbguest (0.29.0, global)
```

※プラグインのインストール時、 `Failed to build gem native extension.` が発生する場合は、Xcode CLIをインストールするとうまくいくかもです。

```
$ xcode-select --install
```

### Vagrant Boxイメージ の追加

```
$ vagrant box add ubuntu/focal64

$ vagrant box list
ubuntu/focal64 (virtualbox, 20200326.0.0)
```

## VirtualBox とは

![VirtualBox](https://i.imgur.com/nyF2ZKR.png)

https://www.virtualbox.org

- 仮想化ソフトウェア
- 複数のゲストOSを管理・起動することができ、同時に起動できる
- エミュレートされる環境
  - ハードディスク
  - 光学ドライブ
  - グラフィック機能
  - ネットワーク機能
  - オーディオ機能

## Vagrant とは

![Vagrant](https://i.imgur.com/F5MXdMb.png)

https://www.vagrantup.com

Vagrantとは仮想マシンの環境構築を楽にしてくれるツールです。
構成情報を記述した設定ファイル (Vagrantfile) を元に、仮想環境の構築から設定までを自動的に行えます。

ちなみにVagrantfileはRubyで記述できる

## なぜ仮想環境で構築する必要があるのか

ホスト環境に依存せずに開発環境やテスト環境を構築するため

## 今回作る環境

- Ubuntu 20.04
- Apache 2.4
- MySQL 8.0
- PHP 7.4

## Ubuntu バージョン番号とコードネーム

Ubuntuはリリースごとにバージョン番号とコードネームが存在します。
バージョン番号はリリース時の年と月から作られる。

- Ubuntu 18.04 LTS (Bionic Beaver)
- Ubuntu 18.10 (Cosmic Cuttlefish)
- Ubuntu 19.04 (Disco Dingo)
- Ubuntu 19.10 (Eoan Ermine)
- Ubuntu 20.04 LTS(予定) (Focal Fossa)
  - 2020年4月23日リリース予定

## Ubuntu サポート

リリースには通常版と長期サポート版（LTS、Long Term Support）がありサポート期間が異なります。

通常版のサポート期間はリリースから9ヶ月です。
6〜9ヶ月ごとにバージョンアップしなければならない。

長期サポート版（LTS）は2年間隔でリリースされ、通常サポート期間はリリースから5年で、サポート期間も長いため安定を望むユーザーに向いている。

実務では最新のLTS版を使用することを推奨します。

# ハンズオン開始

## 作業ディレクトリの作成

```
$ mkdir vagrant-handson
$ cd vagrant-handson
```

## Vagrant Cloud

Vagrant Box ファイルの置き場です。
アカウント登録すれば誰でも自由にboxアップロード＆公開できます。

- https://app.vagrantup.com/ubuntu
- https://app.vagrantup.com/ubuntu/boxes/focal64

```
$ vagrant init -m ubuntu/focal64
```

- `-m`, `--minimal` Use minimal Vagrantfile template (no help comments).
  - ヘルプコメントを削除したテンプレートでVagrantfileを作成します。

```
$ cat Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"
end
```

```
$ vagrant up
```

## ゲストOS(Ubuntu)にSSH接続する

```
$ vagrant ssh
```

ログアウトしたい場合は exit

```
$ exit
```

`control` + `d` でも抜けられる

### 補足: SSH接続設定

```
$ vagrant ssh-config --host vagrant-handson
Host vagrant-handson
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /Users/ucan/handson/vagrant-handson/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL
```

この内容を `~/.ssh/config` に追記すれば `ssh vagrant-handson` で接続できます。 sshコマンドで接続した方が若干高速です。

```
$ ssh vagrant@127.0.0.1 -p 2222 -i /Users/ucan/vagrant-handson/.vagrant/machines/default/virtualbox/private_key
```

sshコマンドでユーザー名、ホスト名、ポート番号、秘密鍵を引数に渡しても接続できます。

とsshコマンドでも接続できることをご紹介しましたが `vagrant ssh` でokです。

## OSバージョン確認

```
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.1 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.1 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
```

## 最新のパッケージ情報の再取得

```
$ sudo apt update
```

## パッケージの検索

```
$ apt search php7.4
php7.4/focal-updates,focal-security 7.4.3-4ubuntu2.4 all
  server-side, HTML-embedded scripting language (metapackage)

... 省略
```

## パッケージの詳細

```
$ apt show php7.4
Package: php7.4
Version: 7.4.3-4ubuntu2.4
Priority: optional
Section: php
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Debian PHP Maintainers <team+pkg-php@tracker.debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 73.7 kB
Provides: php
Depends: libapache2-mod-php7.4 | php7.4-fpm | php7.4-cgi, php7.4-common
Homepage: http://www.php.net/
Download-Size: 9248 B
APT-Sources: http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages
Description: server-side, HTML-embedded scripting language (metapackage)
 This package is a metapackage that, when installed, guarantees that you
 have at least one of the four server-side versions of the PHP interpreter
 installed. Removing this package won't remove PHP from your system, however
 it may remove other packages that depend on this one.
 .
 PHP (recursive acronym for PHP: Hypertext Preprocessor) is a widely-used
 open source general-purpose scripting language that is especially suited
 for web development and can be embedded into HTML.

N: There is 1 additional record. Please use the '-a' switch to see it
```

## PHP インストール

```
$ sudo apt -y install php7.4
```

- `sudo` インストールにはroot権限が必要
- `-y` 問い合わせがあった場合はすべて「y」と答える

```
$ php -v
PHP 7.4.3 (cli) (built: Oct  6 2020 15:47:56) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.3, Copyright (c), by Zend Technologies
```

### セマンティック バージョニング

- [Semantic Versioning](https://semver.org/lang/ja/)
- バージョン番号の付与ルールの1つ
- x.y.z の形式にする
  - x メジャーバージョン(後方互換性を持たない変更)
  - y マイナーバージョン(後方互換性を保ちつつ機能追加)
  - z パッチバージョン(後方互換性を保ったバグ修正)

PHPでは、メジャーバージョンは年1、マイナーバージョンは月1、パッチバージョンは数日の頻度でアップデートされていきます。

セマンティクバージョニングに則っていないライブラリももちろんありますので、バージョン管理については各ライブラリの公式サイトを確認します。

## PHP MySQL拡張ライブラリのインストール

PHPからMySQLへ接続するためのライブラリをインストールします。
これがないとPHPからPDO接続を試みた場合に「Driver not found」エラーが発生します。

```
$ apt show php7.4-mysql
```

```
$ sudo apt -y install php7.4-mysql
```

```
$ php -m | grep mysql
mysqli
mysqlnd
pdo_mysql
```

`pdo_mysql` モジュールが有効になっていればokです。

- `-m` Show compiled in modules
  - コンパイル済みのモジュールを表示

## PHPの設定ファイル置き場

今回は変更しませんが、 `php.ini` でPHPの設定が行えます。

```
/etc/php/7.4/apache2/php.ini
/etc/php/7.4/apache2/conf.d/*.ini
/etc/php/7.4/cli/php.ini
/etc/php/7.4/cli/conf.d/*.ini
/etc/php/7.4/mods-available/*.ini
```

また、 `phpinfo();` 関数で設定内容を確認できます。

```
$ php -r "phpinfo();"
```

- `-r` Run PHP `<code>` without using script tags `<?..?>`
  - PHPのコードを文字列で渡してそのまま実行できる

## Apache インストール

PHP をインストールしたらApacheも一緒にインストールされます。

```
$ apache2 -v
Server version: Apache/2.4.41 (Ubuntu)
Server built:   2020-08-12T19:46:17
```

### Apache のステータスを確認する

```
$ systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2020-03-28 09:35:23 UTC; 48min ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 22642 (apache2)
      Tasks: 6 (limit: 1137)
     Memory: 10.4M
     CGroup: /system.slice/apache2.service
             ├─22642 /usr/sbin/apache2 -k start
             ├─22645 /usr/sbin/apache2 -k start
             ├─22646 /usr/sbin/apache2 -k start
             ├─22647 /usr/sbin/apache2 -k start
             ├─22648 /usr/sbin/apache2 -k start
             └─22649 /usr/sbin/apache2 -k start
```

- `enabled;` OS起動時に自動起動する設定
- `Active: active (running)` 起動している状態

```
$ curl -s http://localhost | grep "It works!"
```

- cURL（カール）とは、URLを使用してデータ転送するためのコマンドラインツールです
- `-s` 進捗を表示しない
- Apacheのデフォルトページに `It works!` の文字があればok
  - `curl`でHTMLが返ってくるが、パイプラインでgrepに渡して絞り込みしている
  - `curl -s http://localhost` と実際のHTMLが表示されます

## 設定ファイル、ログファイル

```
$ sudo ls -l /etc/apache2/
$ sudo ls -l /var/log/apache2/
```

Apacheの設定を変更したい時やエラーログを見たい時はここを見ます。

## ブラウザからApacheのデフォルトページを表示する

ubuntuからログアウトします。

```
$ exit
```

### Vagrantfile ネットワーク設定

https://www.vagrantup.com/docs/networking

- ポート転送(Forwarded Ports)
  - ホストマシンのポートにアクセスするとゲストマシンのポートに転送できます。
  - 他のサービスとポート番号がバッティングしないように注意すること。
  - `http://localhost:1234`
- プライベートネットワーク(Private Networks)
  - Vagrant内部のネットワークでゲストマシンにIPを割り当てます。
  - ゲスト間で通信も行えます。
  - `http://192.168.33.10`
- パブリックネットワーク(Public Networks)
  - 同一ネットワーク内のどの端末からでもゲストOSと通信が行える
  - iPhoneからアクセスしてみたい時などに便利です。
  - `http://192.168.0.10`

ネットワーク設定は3種類あります。今回はポート転送を使用します。

```
$ vim Vagrantfile

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"
  config.vm.network "forwarded_port", guest: 80, host: 8000
end
```

ポート転送設定で、Mac(host)の8000番ポートをUbuntu(guest)の80番ポートに割り当てています。

```
$ vagrant reload
```

- http://localhost:8000
  - `https` で接続すると `ERR_SSL_PROTOCOL_ERROR` となるので注意

![Apache It works!](https://i.imgur.com/dtzMvcp.png)

この画面が表示されればok
Apacheサーバーが正常に動作しており、ホスト側からブラウザを通してHTMLが表示できています。

#### IPアドレスとポート番号

[IPアドレスとポート番号について](https://qiita.com/tomokichi_ruby/items/8b6055d424604c184c9c)
[【初心者用】ウェルノウンポートの意味とよく使われるポート番号一覧](https://qiita.com/terufumi1122/items/c8489ed36406ae51c425)

`localhost` は自分自身を指すIPアドレスです。
`127.0.0.1` も同様に自分自身を指します。

ポート番号は 0 〜 65535 番まであります。
1つのサービスを1つのポート番号に割り当てることができます。
(すでに他のサービスが割り当てられてる場合は使用できない)

|ポート番号|サービス名|
|:-:|:-:|
|20|FTP(データ転送)|
|21|FTP(コントロール)|
|22|SSH|
|23|Telnet|
|25|SMTP|
|53|DNS|
|80|HTTP|
|110|POP3|
|443|HTTPS|

### ドキュメントルート

ドキュメントルートとは、Webサーバが外部に公開するファイルなどが置かれたディレクトリを指します。
デフォルトは `/var/www/html` となっています。

試しにhtml, phpファイルを配置してみます。

```
$ vagrant ssh
$ sudo bash -c "echo 'Hello World' > /var/www/html/hello.html"
$ sudo bash -c "echo '<?php phpinfo();' > /var/www/html/phpinfo.php"
```

`/var/www/html` ディレクトリはroot権限となっているので、sudoで書き込みしてます。

- http://localhost:8000/hello.html
- http://localhost:8000/phpinfo.php

## MySQL インストール

```
$ apt show mysql-server mysql-client
```

```
$ sudo apt -y install mysql-server mysql-client
```

```
$ mysql --version
mysql  Ver 8.0.22-0ubuntu0.20.04.3 for Linux on x86_64 ((Ubuntu))
```

```
$ systemctl status mysql
● mysql.service - MySQL Community Server
     Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2020-03-12 17:03:56 UTC; 1min 9s ago
   Main PID: 24346 (mysqld)
     Status: "Server is operational"
      Tasks: 38 (limit: 1137)
     Memory: 371.9M
     CGroup: /system.slice/mysql.service
             └─24346 /usr/sbin/mysqld
```

## 補足: MySQL 自動起動の有効/無効

デフォルトでは自動起動は有効になっています。

```
$ sudo systemctl enable mysql
$ sudo systemctl disable mysql
```

## MySQL設定ファイル

```
$ sudo vim /etc/my.cnf
```

```
[mysqld]
character_set_server = utf8mb4 # 文字コード
collation_server = utf8mb4_ja_0900_ai_ci # 照合順序
```

設定ファイルの変更を反映するにはMySQLを再起動する必要があります。

## MySQL起動、停止、再起動

```
$ sudo systemctl start mysql
$ sudo systemctl stop mysql
$ sudo systemctl restart mysql
```

## MySQL 初期設定

初期パスワードは今回は `secret` と入力します。

```
$ sudo mysql_secure_installation
Securing the MySQL server deployment.

Connecting to MySQL using a blank password.

VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?

[パスワード強度の設定をしますか？]
Press y|Y for Yes, any other key for No: [空Enter]
Please set the password for root here.

New password: [初期パスワードを入力]

Re-enter new password: [初期パスワードを再度入力]
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

[匿名ユーザーを削除しますか？]
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

[リモートでrootログインを禁止しますか？]
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.

[テスト用データベースを削除しますか？]
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

[特権テーブルをリロードしますか？]
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done!
```

## MySQL ログイン

```
$ sudo mysql -u root -p
```

- パスワードはさっき設定した `secret`
- rootユーザーでログインするにはsudoが必要

## MySQL SQL文について

- `> ` はSQL文とします。
- SQLの予約後は分かりやすくするため大文字で記述しています。
  - 大文字・小文字は区別しないので、どちらでも可です。

## MySQL ログアウト

```
> exit
```

## MySQL 設定確認

### ユーザー一覧を表示

```
> SELECT Host, User FROM mysql.user;
+-----------+------------------+
| Host      | User             |
+-----------+------------------+
| localhost | debian-sys-maint |
| localhost | mysql.infoschema |
| localhost | mysql.session    |
| localhost | mysql.sys        |
| localhost | root             |
+-----------+------------------+
```

### 文字コードの表示

```
> 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       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
```

### 文字コードの照合順序の表示

```
> SHOW VARIABLES LIKE '%collation%';
+-------------------------------+--------------------+
| Variable_name                 | Value              |
+-------------------------------+--------------------+
| collation_connection          | utf8mb4_0900_ai_ci |
| collation_database            | utf8mb4_0900_ai_ci |
| collation_server              | utf8mb4_0900_ai_ci |
| default_collation_for_utf8mb4 | utf8mb4_0900_ai_ci |
+-------------------------------+--------------------+
```

- `utf8mb4_ja_0900_ai_ci` MySQL8.0のデフォルトの文字コード
  - `utf8mb4` utf8で、マルチバイトを4バイトとする文字コードになります。
  - `ja` 日本語用
  - `0900` Unicode Collation Algorithm 9.0.0 をベースとする。
  - `ai` Accent Insensitiveの略称。アクセントの違いを無視する。
    - 「は」と「ぱ」は等しいと評価される。
  - `ci` Case Insensitiveの略称。大文字と小文字の違いを無視する。
    - 「あ」と「ぁ」は等しいと評価される。
- `utf8mb4_ja_0900_as_cs_ks` MySQL8.0から導入された照合順序
  - `as` Accent Sensitiveの略称。アクセントの違いを区別する。
    - 「は」と「ぱ」は異なると評価される。
  - `cs` Case Sensitiveの略称。大文字と小文字の違いを区別する。
    - 「あ」と「ぁ」は異なると評価される。
  - `ks` Case Sensitiveの略称。ひらがなとカタカナの違いを区別する。
    - 「あ」と「ア」は異なると評価される。

文字コードの照合順序は `utf8mb4_ja_0900_as_cs_ks` を推奨しますが、今回はデフォルトのまま進めます。

## MySQLのrootユーザーでデータベースの初期設定

### MySQL データベースの作成

```
> CREATE DATABASE shop;
```

### MySQL ユーザー作成

```
> CREATE USER phper@'%' IDENTIFIED BY 'secret';
```

### MySQL 権限付与

```
> GRANT ALL PRIVILEGES on shop.* to phper@'%';
```

### MySQL ユーザー一覧

```
> SELECT Host, User FROM mysql.user;
```

## vagrantユーザーでログイン

```
$ mysql -u phper -p
secret
```

```
$ mysql -u phper -psecret
```

- `-p` オプションを付けてログインできる
  - スペースなしでパスワードを指定する
- この方法はコマンド履歴に残るためセキュリティ上は非推奨

```
$ mysql -u phper -p shop
```

- データベースを指定してログインもできる。

## データベース一覧、使用するデータベースを選択、テーブル一覧

```
> SHOW DATABASES;
> USE shop;
> SHOW TABLES;
```

## テーブル作成

```
CREATE TABLE items (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(30) COMMENT '商品名',
  stock INT DEFAULT 0 COMMENT '在庫数'
) COMMENT='商品テーブル';
```

```
> DESC items;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int         | NO   | PRI | NULL    |       |
| name  | varchar(30) | YES  |     | NULL    |       |
| stock | int         | YES  |     | 0       |       |
+-------+-------------+------+-----+---------+-------+

> SHOW FULL COLUMNS FROM items;
+-------+-------------+--------------------+------+-----+---------+-------+---------------------------------+-----------+
| Field | Type        | Collation          | Null | Key | Default | Extra | Privileges                      | Comment   |
+-------+-------------+--------------------+------+-----+---------+-------+---------------------------------+-----------+
| id    | int         | NULL               | NO   | PRI | NULL    |       | select,insert,update,references |           |
| name  | varchar(30) | utf8mb4_0900_ai_ci | YES  |     | NULL    |       | select,insert,update,references | 商品名    |
| stock | int         | NULL               | YES  |     | 0       |       | select,insert,update,references | 在庫数    |
+-------+-------------+--------------------+------+-----+---------+-------+---------------------------------+-----------

> SHOW TABLE STATUS LIKE 'items';
+-------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+--------------------+
| Name  | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time         | Update_time | Check_time | Collation          | Checksum | Create_options | Comment            |
+-------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+--------------------+
| items | InnoDB |      10 | Dynamic    |    0 |              0 |       16384 |               0 |            0 |         0 |           NULL | 2020-03-13 04:56:10 | NULL        | NULL       | utf8mb4_0900_ai_ci |     NULL |                | 商品テーブル       |
+-------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+--------------------+
```

取得

```
> SELECT * FROM items;
```

挿入

```
> INSERT INTO items (name, stock) VALUE ("Apple", 10);
> INSERT INTO items (name, stock) VALUE ("Bad Apple", 1);
```

更新

```
> UPDATE items SET stock = stock + 1;
```

削除

```
> DELETE FROM items WHERE name = "Bad Apple";
```

```
> exit;
```

## Vagrantfile フォルダ共有

```
$ vim Vagrantfile

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"
  config.vm.network "forwarded_port", guest: 80, host: 8000
  config.vm.synced_folder "./shop", "/var/www/html"
end
```

```
$ mkdir shop
$ vagrant reload
$ cd shop
```

ディレクトリをマウントしてるので、ホスト側でコードを書きます。

```
$ echo "test" > test.html
```

http://localhost:8000/test.html

```
$ rm -f test.html
```

ブラウザで表示確認

## PHP 実装

今回の本題ではないですが、PHPからMySQLへ接続して画面表示できることを確認するため簡単なアプリを作ります。
商品の名前と在庫を登録できる簡易的なショップアプリを作ります。

https://github.com/ucan-lab/infra-handson-shop

コードはGitHubに置いてるので、ダウンロードしてください。

### db.php

```
$ vim db.php
```

```
<?php

class DB
{
    private $connection;

    public function __construct()
    {
        $dbname = getenv('DB_NAME');
        $host = getenv('DB_HOST');
        $charset = 'utf8mb4';
        $dsn = "mysql:dbname=$dbname;host=$host;charset=$charset";

        $username = getenv('DB_USER');
        $password = getenv('DB_PASS');
        $driver_options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES => false,
        ];

        $this->connection = new PDO($dsn, $username, $password, $driver_options);
    }

    public function getConnection(): PDO
    {
        return $this->connection;
    }
}
```

### index.php

```
$ vim index.php
```

```
<?php

require('db.php');

$db = new DB();
$stmt = $db->getConnection()->query('SELECT * FROM items');
$rows = $stmt->fetchAll();

foreach ($rows as $row) {
    echo $row['name'] . ': ' . $row['stock'] . '<br>';
}
?>
<p><a href="/add_item.php">add item</a></p>
<p><a href="/update_stock.php">update stock</a></p>
<p><a href="/clear_items.php">clear items</a></p>
```

### update_stock.php

```
$ vim update_stock.php
```

```
<?php

require('db.php');

$db = new DB();
$count = $db->getConnection()->exec('UPDATE items SET stock = stock + 1');
header("Location: /index.php");
```

### add_item.php

```
$ vim add_item.php
```

```
<?php

require('db.php');

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = $_POST['name'] ?? '';
    $stock = $_POST['stock'] ?? 0;

    $db = new DB();
    $stmt = $db->getConnection()->prepare('INSERT INTO items (name, stock) VALUES (?, ?)');
    $stmt->execute([$name, $stock]);

    header("Location: /index.php");
}
?>
<form method="POST">
    <input type="text" name="name" maxlength="30" required>
    <input type="number" name="stock" min="0" max="1000" required>
    <button type="submit">add</button>
</form>
```

### clear_items.php

```
$ vim clear_items.php
```

```
<?php

require('db.php');

$db = new DB();
$stmt = $db->getConnection()->query('DELETE FROM items');
header("Location: /index.php");
```

http://localhost:8000 => エラーが発生します。

```
$ vagrant ssh
$ sudo less /var/log/apache2/error.log

[Sat Mar 28 16:29:16.960263 2020] [php7:error] [pid 7886] [client 10.0.2.2:62442] PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000] [1045] Access denied for user ''@'localhost' (using password: YES) in /var/www/html/db.php:21\nStack trace:\n#0 /var/www/html/db.php(21): PDO->__construct()\n#1 /var/www/html/index.php(5): DB->__construct()\n#2 {main}\n  thrown in /var/www/html/db.php on line 21
```

- `shift` + `G` でファイル末尾へ移動できます。
- データベースからアクセス拒否されています。
  - ログイン情報が間違っているようです。
  - サーバー環境変数を設定してないためログイン情報が空です。

## 環境変数の設定

Apacheサーバーに環境変数を設定します。
(実際の現場でこのファイルを触ることはあまりないかも...)

```
$ sudo vim /etc/apache2/envvars
```

```
export DB_HOST=localhost
export DB_NAME=shop
export DB_USER=phper
export DB_PASS=secret
```

MySQLの接続情報を設定します。

```
$ sudo systemctl restart apache2
```

http://localhost:8000

![](https://i.imgur.com/mwCD1OA.png)

## オマケ: TablePlusでVagrantのMySQLコンテナのデータベースに接続する

```
$ vagrant ssh-config --host vagrant-handson >> ~/.ssh/config
```

```
$ tail -n 11 ~/.ssh/config
Host vagrant-handson
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /Users/ucan/work/vagrant-handson/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL
```

## オマケ: プロビジョニング(環境構築の自動化)

一度だけ環境構築をするのなら、問題ありませんがチームで開発する際に全員が同じ流れで環境構築をするのはナンセンスです。人間なので手順漏れ等起こしてしまいがちです。

ちなみに `vagrant box package` というコマンドがあり、カスタムした環境をboxファイルとして書き出して、配布することが可能です。

これはこれで便利ですが、ちゃんとbox管理が行われていないと中身に何が入っているのか分からずブラックボックスになりがちです。

そんなわけで環境構築の手順をコード化しようというのがプロビジョニングです。

### プロビジョニングツール

- [Che](https://www.chef.io/products/chef-infra)
- [Puppet](https://puppet.com)
- [Ansible](https://www.ansible.com)

環境構築の自動化ツールはありますが、これはさらに学習コストがのしかかってきますのでシンプルにみんな大好きシェルスクリプトを使って構築します。

### プロビジョニング手順

まずは、仮想環境をぶっ壊す！

```
$ vagrant destroy -f
```

- `-f` 確認なしに強制削除

#### Vagrantfile

```
$ vim Vagrantfile
```

- https://www.vagrantup.com/docs/provisioning

```
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"
  config.vm.network "forwarded_port", guest: 80, host: 8000
  config.vm.synced_folder "./shop", "/var/www/html"
  config.vm.provision :init, type: "shell", path: "init.sh"
end
```

#### init.sh

```
$ vim init.sh
```

```
#!/bin/bash

set -ux

apt-get update
apt-get -y install php7.4 php7.4-mysql mysql-server mysql-client

cat << EOS | sudo tee -a /etc/apache2/envvars
export DB_HOST=127.0.0.1
export DB_NAME=shop
export DB_USER=phper
export DB_PASS=secret
EOS

systemctl restart apache2

cat << EOS | sudo tee /etc/my.cnf
[mysqld]
character_set_server = utf8mb4 # 文字コード
collation_server = utf8mb4_ja_0900_as_cs # 照合順序
EOS

systemctl restart mysql

mysqld --initialize-insecure

mysql -u root << EOT
CREATE DATABASE shop;
CREATE USER phper@'%' IDENTIFIED WITH mysql_native_password BY 'secret';
GRANT ALL PRIVILEGES on shop.* to phper@'%';
SELECT Host, User FROM mysql.user;

CREATE TABLE shop.items (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(30) COMMENT '商品名',
  stock INT DEFAULT 0 COMMENT '在庫数'
) COMMENT='商品テーブル';

INSERT INTO shop.items (name, stock) VALUE ("Golden Apple", 12345);
EOT

echo "インストール完了！"
```

### 環境構築

```
$ vagrant up
```

初回起動時のみプロビジョニングが自動実行されます。

http://localhost:8000

![](https://i.imgur.com/SuOHewB.png)

## さいごに

Vagrant ハンズオンはいかがでしたでしょうか。

```
$ vagrant init centos/7
$ vagrant up
```

たった2コマンドで、仮想マシンの作成〜OSのインストールまで行えるようになりました。
オマケではプロビジョニングで環境構築の自動化まで行えて手順をコード化できました。

仮想マシンの仕組み上、
プロセッサ、メモリ、ストレージ等のハードウェアのシミュレートが必要でリソースをかなり消費します。
また、プロビジョニングでコード化されているとはいえ、
環境構築を作り直したり、共有するのにはとても時間がかかってしまいます。

Dockerハンズオンへ続く...

## 参考

- [PHPでデータベースに接続するときのまとめ](https://qiita.com/mpyw/items/b00b72c5c95aac573b71)