Vagrant+AnsibleでPHPのビルド環境を作る

「PHP のソースコードをデバッグしてみたら？」と悪魔の囁きが聞こえてきたので、まずはビルド環境作りから。

# やること

- Vagrant + VirtualBox で VM（CentOS 7.6）を作る
- Ansible を使って必要なパッケージのインストールや設定を行う[^1]
- PHP のソースコードを入手してビルドする

[^1]: Ansible 使う必然性はないのですが、なんとなく使ってみたかったので。

# 前提

この記事を書いた人のレベルは、以下のとおりです。

- 基本的な Linux コマンドの操作は知っている
    - 設定ファイルを編集するのに `vi` を使います
- Vagrant は仕事で少し触った程度
- Ansible は「なにそれ？おいしいの？」状態
- Git も仕事で少し触った程度

# VM を作る

[Ansibleをはじめる人に。](https://qiita.com/t_nakayama0714/items/fe55ee56d6446f67113c) に書いてある手順と、ほぼ同じです。（分かりやすい記事に感謝です）

## Vagrant と VirtualBox をインストールする

Vagrant と VirtualBox をインストールします。

- [Vagrant](https://www.vagrantup.com/)
- [VirtualBox](https://www.virtualbox.org/)

## ディレクトリを作る

VM 用のディレクトリを作ります。

```console
mkdir -p ~/vagrant/centos76
```

## Vagrantfile を生成する

`vagrant init` で Vagrantfile を生成します。

VM のもととなる BOX ファイルは `bento/centos-7.6` （[検索結果](https://app.vagrantup.com/bento/boxes/centos-7.6)）を使います。

```console
vagrant init bento/centos-7.6   --box-version 201812.27.0
```

## Vagrantfile を編集する

今回は VM を2つ作ります。

- `controller`
    - Ansible を実行する VM
    - IPアドレス: `192.168.33.10`

- `target`
    - Ansible の実行対象で、PHP をビルドする VM
    - IPアドレス: `192.168.33.20`

`~/vagrant/centos76` の下に生成された `Vagrantfile` を以下のように書き換えます。

```ruby:~/vagrant/centos76/Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.define "controller" do |node|
    node.vm.box = "bento/centos-7.6"
    node.vm.box_version = "201812.27.0"
    node.vm.hostname = "controller"
    node.vm.network "private_network", ip: "192.168.33.10"
    node.vm.network "forwarded_port",  id: "ssh", guest: 22, host: 2210
  end

  config.vm.define "target" do |node|
    node.vm.box = "bento/centos-7.6"
    node.vm.box_version = "201812.27.0"
    node.vm.hostname = "target"
    node.vm.network "private_network", ip: "192.168.33.20"
    node.vm.network "forwarded_port",  id: "ssh", guest: 22, host: 2220
  end
end
```

## VM を起動する

`vagrant up` で VM を起動します。

`vagrant status` を実行して、`controller` と `target` 両方とも起動していれば問題ありません。

```console
$ vagrant status
Current machine states:

controller                running (virtualbox)
target                    running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.
```

# Ansible を設定する
## Ansible をインストールする

`controller` に SSH でログインします。

```console
$ vagrant ssh controller
```

root ユーザーに切り替えて、`yum` で Ansible をインストールします。

```console
$ su
Password: (vagrant)

# yum install ansible
```

`ansible --version` を実行すると、以下のように表示されます。

```console
# ansible --version
ansible 2.4.2.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]
```

## SSH 接続を設定する

`controller` からさらに `target` へ SSH 接続できたほうが便利なので、いろいろ設定しておきます。

まずは、SSH の公開鍵を作って、`target` に登録しておきます。

```console
# ssh-keygen -t rsa
# ssh-copy-id root@192.168.33.20
```

また、毎回 IP アドレスを入力するのは面倒なので、`/etc/hosts` に `target` を追加して、名前解決できるようにしておきます。

```console
# vi /etc/hosts
```

```console:/etc/hosts
127.0.0.1       controller      controller
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.33.20   target
```

## inventory ファイルを作る

Ansible 用のディレクトリを作ります。今回は以下のようなディレクトリ構成にします。

```
/ansible/
   |- invengtory/
   |   |- hosts    : 対象ホストを記述した設定ファイル
   |
   |- target/
       |- test.yml : Playbook ファイル
```

まずは `/ansible/inventory/hosts` ファイルを準備します。

```console
# mkdir -p /ansible/inventory
# cd /ansible
# vi inventory/hosts
```

`targets` というグループのメンバーに `target` （`192.168.33.20`）を追加します。

```console:/ansible/inventory/hosts
[targets]
192.168.33.20
```

## Playbook ファイルを作る

Ansible は、**Playbook に理想的な状態を定義して対象ホストがそれを満たす状態を作る** という考え方のツールです。なので、Playbook ファイルが一番重要になります。

```console
# mkdir target
# vi target/test.yml
```

Playbook ファイルに設定する内容は後回しにして、先に Playbook ファイルを準備したあとの手順を書いておきます。

書いた Playbook ファイルは `ansible-playbook` コマンドを使って、対象ホスト（今回は `target`）へ反映します。

まずは、反映する前に、書いた Playbook ファイルが問題ないかを `--check` オプションを付けて確認します。

```console
# ansible-playbook --check -i inventory/hosts target/test.yml
```

エラーが出ないことを確認したら、以下のようにして反映します。

```console
# ansible-playbook -i inventory/hosts target/test.yml
```

## 対象ホストへSSH接続する

今は `controller` に SSH 接続している状態ですが、ここからさらに `target` へ SSH 接続します。

Playbook を作る際は、その内容で問題ないかを確かめるために、頻繁に対象ホストへ接続することになります。

一旦、`controller` を抜けるよりも、`controller` に入ったまま `target` へ接続できたほうが便利ですね。

図にすると、こんな感じです。

```
Mac -- (SSH) --> controller -- (SSH) --> target
```

`/etc/hosts` に設定しておいたので、実行するコマンドはとてもシンプルです。

```console
# ssh target
```

# PHP をビルドする
## 前提

PHP をビルドするために必要なパッケージについては [マニュアル](https://secure.php.net/manual/ja/install.unix.php) に記載があるので、確認しておきましょう。

## Playbook ファイルを作る

`target/test.yml` に以下のように定義します。

```yml:/ansible/target/test.yml
- hosts: targets
  user: root
  tasks:
    - name: Set Timezone
      timezone:
        name: Asia/Tokyo

    - name: Install gcc
      yum: name=gcc
    - name: Install autoconf
      yum: name=autoconf
    - name: Install automake
      yum: name=automake
    - name: Install ctags
      yum: name=ctags
    - name: Install gdb
      yum: name=gdb
    - name: Install git
      yum: name=git
    - name: Install bison
      yum: name=bison
    - name: Install libxml2-devel
      yum: name=libxml2-devel
    - name: Install sqlite-devel
      yum: name=sqlite-devel

    - name: Install epel-release
      yum: name=epel-release
    - name: Install re2c
      yum: name=re2c

    - name: Reinstall glibc-common
      shell: yum -y reinstall glibc-common
    - name: Set Locale
      shell: localectl set-locale LANG=ja_JP.utf8
```

Playbook ファイルを作ったあとは、`ansible-playbook` で `target` に反映します。

```console
# ansible-playbook -i inventory/hosts target/test.yml
```

## ソースコードを入手する

PHP のソースコードは [GitHub](https://github.com/php/php-src) にあるので、`git clone` で入手します。

```console
# ssh target
# git clone https://github.com/php/php-src.git
```

## configure する

今回はデバッグ目的なので `--enable-debug` オプションを付けて `./configure` を実行します。

```console
# cd php-src
# ./buildconf
# ./configure --enable-debug
```

## make する

`./configure` が成功したら、`make` を実行します。

```console
# make
```

以下のように表示されたらビルド成功です。

```console
Build complete.
Don't forget to run 'make test'
```

## make test する

「`make test` を実行するのを忘れないで！」と言われたので、`make test` もやっておきましょう。

```console
=====================================================================
TEST RESULT SUMMARY
---------------------------------------------------------------------
Exts skipped    :   50
Exts tested     :   26
---------------------------------------------------------------------

Number of tests : 16099             10784
Tests skipped   : 5315 ( 33.0%) --------
Tests warned    :    0 (  0.0%) (  0.0%)
Tests failed    :    1 (  0.0%) (  0.0%)
Expected fail   :   36 (  0.2%) (  0.3%)
Tests passed    : 10747 ( 66.8%) ( 99.7%)
---------------------------------------------------------------------
Time taken      :  602 seconds
=====================================================================

=====================================================================
EXPECTED FAILED TEST SUMMARY
---------------------------------------------------------------------
Test open_basedir configuration [tests/security/open_basedir_linkinfo.phpt]  XFAIL REASON: BUG: open_basedir cannot delete symlink to prohibited file. See also
bugs 48111 and 52176.
（省略）
=====================================================================

=====================================================================
FAILED TEST SUMMARY
---------------------------------------------------------------------
Timeout again inside register_shutdown_function [tests/lang/045.phpt]
=====================================================================

You may have found a problem in PHP.
This report can be automatically sent to the PHP QA team at
http://qa.php.net/reports and http://news.php.net/php.qa.reports
This gives us a better understanding of PHP's behavior.
If you don't want to send the report immediately you can choose
option "s" to save it.	You can then email it to qa-reports@lists.php.net later.
Do you want to send this report now? [Yns]:
```

**なんか失敗したらしい**

## PHP を実行する

気を取り直して、`php-src/sapi/cli` の中に `php` コマンドが作成されているので実行してみましょう。

```console
# sapi/cli/php --version
```

```console
PHP 8.0.0-dev (cli) (built: Feb  3 2019 10:59:53) ( NTS DEBUG )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
```

とりあえず、ちゃんと動いているらしいです。

## 更新をローカルに取り込む

今回ビルドしたソースコードは開発版なので、頻繁に更新がされます。

まず、以下のコマンドで最新のソースコードを入手します。

```console
# git fetch
# git merge origin/master
```

あとは、 `./configure --enable-debug` から再度実行するだけです。

# 躓きポイント
## devel に要注意

`./configure` を実行した際に、`libxml2` や `sqlite` がインストールされていないと表示されました。

しかし、`yum` で確認するとインストール済みとなっています。

いろいろ調べたところ、`libxml2-devel` と `sqlite-devel` をインストールする必要がありました。

## re2cがyumにない

`yum install re2c` を実行しても存在しないと表示されました。

[PHP7をCentOS7にインストールする手順](https://blog.takekoshi.net/php7-on-centos7/) によると、[EPEL](https://fedoraproject.org/wiki/EPEL/ja)（Extra Packages for Enterprise Linux）をインストールすると、`yum install re2c` が実行できるようになるとのことでした。

## ロケールが設定できない

これは PHP というよりは CentOS の問題ですが、`automake` を実行した際にロケール設定に失敗したと表示されます。

日本語のロケールが入っていないことが原因らしく、`glibc-common` を再インストールしてからロケールを設定すると、うまくいきました。

ただ、Playbook の [yum モジュール](https://docs.ansible.com/ansible/latest/modules/yum_module.html) には再インストールが見当たらなかったので、[shell モジュール](https://docs.ansible.com/ansible/latest/modules/shell_module.html) で行うようにしました。

（「冪等性」を考えると微妙な気もしますが、Ansible をまだよく理解できていないので、一旦、こうしておきます。）


# まとめ

いろいろ苦戦しましたが、どうにか PHP のビルド環境を作ることができました。次は GDB を使ってデバッグしてみたいです。（GDB の使い方から勉強しないと・・・）


# 参考

- Ansible
    - [Ansibleをはじめる人に。](https://qiita.com/t_nakayama0714/items/fe55ee56d6446f67113c) 

- Vagrant
    - [Vagrant + VirtualBoxでWindows上に開発環境をサクッと構築する](https://qiita.com/ozawan/items/160728f7c6b10c73b97e)

- PHP のビルド
    - [configure: error: xml2-config not found. Please check your libxml2 installation.](https://webkaru.net/linux/configure-error-xml2-config-found-please-check-libxml2-installation/)
    - [PHP7をCentOS7にインストールする手順](https://blog.takekoshi.net/php7-on-centos7/)
    - [CentOS7のlocaleを日本にする](https://qiita.com/silverskyvicto/items/5791740840296e6b6603)
    - [PHPソースコードリーディング入門(とっかかり編)](http://blog.anatoo.jp/entry/20111031/1319991834)

# 更新履歴
- 2019/02/03
    - `master` ブランチが 8.0.0 になったので、PHP のバージョン情報を更新しました。