今更だけどAnsibleでUbuntuにLAMPを構築する

この記事はほぼAnsibleでやっていることを解説するだけの記事です。コード読める人はコード読んだほうがいいと思います。
なんと仮想環境は使いません！実行確認したバージョンは以下の通り。

||バージョン|
|:-:|:-:|
|Ansible|2.2.1.0|
|Ubuntu Server|14.04.4 LTS 64bit|
|Apache|2.4.7|
|MySQL|5.5.54|
|PHP|5.6.29|

作ったもの:[Yaruki00/ansible_ubuntu_lamp](https://github.com/Yaruki00/ansible_ubuntu_lamp)

# はじめに
今回作ったものは[phansible](http://phansible.com/)というサイトで生成できるplaybookを元にしています。と言うかほぼそのままです。
もともとはVagrantを使って仮想UbuntuにAnsibleでLAMPを構築するようになっているのですが、わざわざVagrant部分は削除してます。
仮想環境でやりたいよ、という人はphansibleでポチポチしてplaybookを生成してみてください。

# 中身はどうなっているの

## ディレクトリ構成

```
.
├── inventories
│   ├── dev
│   └── local
├── playbook.yml
├── roles
│   ├── apache
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       ├── vhost22.conf.tpl
│   │       └── vhost24.conf.tpl
│   ├── mysql
│   │   └── tasks
│   │       └── main.yml
│   ├── php
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   ├── configure.yml
│   │   │   ├── main.yml
│   │   │   ├── mod-php.yml
│   │   │   ├── pecl.yml
│   │   │   ├── php-cli.yml
│   │   │   └── php-fpm.yml
│   │   └── templates
│   │       └── extension.tpl
│   └── server
│       ├── tasks
│       │   └── main.yml
│       └── templates
│           └── timezone.tpl
└── vars
    └── all.yml
```

## 実行
本当はsshで実行したいのですが、sudoで上手く行かなくなってしまうので、UbuntuにAnsibleをインストールした後、ローカルで実行します。
まずはAnsibleをインストールします。

```
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible
```

そして、playbook.ymlがあるディレクトリに移動し、以下のコマンドを叩きます。

```
$ ansible-playbook -i inventories/local -c local playbook.yml
```

`-i`でインベントリファイルを指定します。`-c`は接続方法を指定するオプションで`-c local`でローカル接続になります。普通はsshが使われます。
ローカル接続の場合でもインベントリファイルは何かしら指定しないといけないみたいです。
その他オプションについては`ansible -help`なり調べるなりしてください。

## インベントリファイル
対象ホストの情報を書くファイルです。グルーピングしたり、変数を用意してホストまたはグループで使うことができます。
書き方は以下を参照。
[Ansibleのインベントリファイルでステージを切り替える](http://qiita.com/NewGyu/items/5de31d76d2488ab27ed6)
[Ansibleのインベントリファイル](http://qiita.com/RyoMa_0923/items/d54da43573849586f5a4)
ポートやユーザを指定することもできます。
[Ansibleでsshユーザを指定する](http://qiita.com/kiarina/items/397b12c56aea83669286)

ローカル接続の場合は特に書くことないです。

```yaml:inventories/local
[local]
localhost
```

## 変数ファイル
プレイブックやテンプレート内で使える変数を定義します。
phansibleで生成されるものでは、インストールするパッケージやユーザ情報などを記述しています。

```yaml:vars/all.yml
---
server:
    install: '1'
    packages: [git]
    timezone: Asia/Tokyo
    locale: ja_JP.UTF-8
apache:
    install: '1'
    docroot: '/var/www/html'
    servername: 'localhost'
mysql:
    install: '1'
    root_password: root
    database: db
    user: user
    password: password
php:
    install: '1'
    ppa: php
    packages: [php5.6-cli, php5.6-intl, php5.6-mcrypt, php5.6-curl, php5.6-fpm, php5.6-gd, php5.6-mbstring, php5.6-mysql]
    config:
      - regexp: '^;(mbstring.language) =.*'
        line: '\1 = Japanese'
      - regexp: '^;(mbstring.internal_encoding) =.*'
        line: '\1 = UTF-8'
      - regexp: '^;(mbstring.http_input) =.*'
        line: '\1 = pass'
      - regexp: '^;(mbstring.http_output) =.*'
        line: '\1 = pass'
      - regexp: '^;(mbstring.encoding_translation) =.*'
        line: '\1 = off'
```

## プレイブック
Ansibleに実行させる内容を記述します。
書き方は公式ページをどうぞ。
[Playbooks](http://docs.ansible.com/ansible/playbooks.html)

```yaml:playbook.yml
---
- hosts: all
  become: true
  vars_files:
    - vars/all.yml
  roles:
    - server
    - apache
    - mysql
    - php
```

vars_filesで先程定義した変数ファイルを指定します。
rolesというのがディレクトリのrolesに対応していて、4つの役割があることがわかります。

## ロール
roles以下は今回は3つのディレクトリがあります。tasksのみ必須で、後は無くても大丈夫です。

### ディレクトリ

#### タスク
tasks以下に配置します。mainという名前のものが最初に実行されます。

#### ハンドラ
handlers以下に配置します。タスクからnotifyで呼び出すことができます。

#### テンプレート
templates以下に配置します。タスクからtemplateで使うことができます。設定ファイルなどのテンプレートに使われます。

他にも幾つかあるので、必要に応じて適宜用意してください。
[AnsibleのRole入門](http://dev.classmethod.jp/server-side/ansible/introduction_about_role/)
[Ansibleのroleを使いこなす
](http://qiita.com/hnakamur/items/63f2d94badf89246e04a)

### server
名前が微妙な気もしますが、内容はUbuntuでできることをやってます。

1. apt-getのアップデート
2. 基本パッケージのインストール
3. その他のパッケージのインストール
4. タイムゾーン設定
5. 言語設定

といった流れです。

```yaml:roles/server/tasks/main.yml
---
- name: Update apt
  become: true
  apt: update_cache=yes

- name: Install System Packages
  become: yes
  apt: pkg={{ item }} state=latest
  with_items:
    - curl
    - wget
    - python-software-properties

- name: Install Extra Packages
  become: yes
  apt: pkg={{ item }} state=latest
  with_items: "{{server.packages}}"
  when: server.packages is defined

- name: Configure the timezone
  become: yes
  template: src=timezone.tpl dest=/etc/timezone

- name: More Configure the timezone
  become: yes
  file: src=/usr/share/zoneinfo/{{server.timezone}} dest=/etc/localtime state=link force=yes backup=yes

- name: Set default system language pack
  shell: locale-gen {{server.locale}}
  become: yes

- name: Update locale
  shell: update-locale LANG={{server.locale}}
  become: yes
```

### apache
Webサーバをインストールします。

1. 最新のApacheをインストール
2. モジュールをインストールして再起動
3. バージョン確認して変数に入れる
4. 変数のバージョンを見て、バーチャルホストの設定ファイル生成

といった流れです。バーチャルホストの設定ファイル生成がApache2.4と2.2向けに２つありますが、2.2はもはやいらなさそうな気がします。

```yaml:roles/apache/tasks/main.yml
---
- name: Install Apache
  become: yes
  apt: pkg=apache2 state=latest

- name: Install Apache Modules
  apache2_module: state=present name={{ item }}
  notify: restart apache
  with_items:
    - rewrite
    - vhost_alias
    - headers
    - expires
    - filter

- shell: apache2 -v
  register: apache_version

- name: Change default apache2.4 site
  become: yes
  template: src=vhost24.conf.tpl dest=/etc/apache2/sites-available/000-default.conf
  notify: restart apache
  when: apache_version.stdout.find('Apache/2.4.') != -1

- name: Change default apache2.2 site
  become: yes
  template: src=vhost22.conf.tpl dest=/etc/apache2/sites-available/default
  notify: restart apache
  when: apache_version.stdout.find('Apache/2.2.') != -1
```

### mysql
DBをインストールします。

1. ホスト名を変数に入れる
2. mysqlのインストール
3. パスワード設定
4. データベース作成
5. 不正なユーザがいないか確認
6. ユーザ作成

といった流れです。

```yaml:roles/mysql/tasks/main.yml
---
# Retrieve the current hostname, because {{ ansible_hostname }} still contains the old name
- shell: hostname
  register: current_hostname

- name: mysql | Install MySQL Packages
  become: yes
  apt: pkg={{ item }} state=latest
  with_items:
    - mysql-server
    - mysql-client
    - python-mysqldb

- name: mysql | Update root password for all root accounts
  mysql_user: name=root host={{ item }} check_implicit_admin=yes password={{ mysql.root_password }} login_user=root login_password={{ mysql.root_password }}
  with_items:
    - "{{ current_hostname.stdout | lower }}"
    - 127.0.0.1
    - ::1
    - localhost

- name: mysql | Create databases
  mysql_db: name={{ mysql.database }} state=present login_user=root login_password={{ mysql.root_password }}
  when: mysql.database is defined

- name: mysql | Ensure anonymous users are not in the database
  mysql_user: name='' host={{ item }} state=absent login_user=root login_password={{ mysql.root_password }}
  with_items:
    - localhost
    - "{{ current_hostname.stdout | lower }}"

- name: mysql | Create users
  mysql_user: name={{ mysql.user }} password={{ mysql.password }} priv={{ mysql.database }}.*:ALL state=present login_user=root login_password={{ mysql.root_password }}
```

### php
PHPをインストールします。

1. aptのリポジトリ追加
2. aptのアップデート
3. PHP5.6のインストール
4. パッケージのインストール
5. 次のプレイブックへ

といった流れです。Apacheを使う場合はphp-fpmのインストールは無くても大丈夫です。

```yaml:roles/php/tasks/main.yml
---
- name: Add ppa Repository
  become: yes
  apt_repository: repo=ppa:ondrej/{{ php.ppa }}

- name: Update apt
  become: yes
  apt: update_cache=yes

- name: Install php5.6
  become: yes
  apt: pkg=php5.6 state=latest

- name: Install php5-fpm
  sudo: yes
  apt: pkg=php5-fpm state=latest

- name: Install PHP Packages
  become: yes
  apt: pkg={{ item }} state=latest
  with_items: "{{php.packages}}"
  when: php.packages is defined

- include: configure.yml
- include: pecl.yml
```

`configure.yml`ではPHPの実装ごとに設定を行っていきます。Apacheを使う場合は`mod-php.yml`だけ実行されれば大丈夫です。

```yaml:roles/php/tasks/configure.yml
---
- stat: path=/etc/php/5.6/apache2/php.ini
  register: modphp

- stat: path=/etc/php/5.6/fpm/php.ini
  register: phpfpm

- stat: path=/etc/php/5.6/cli/php.ini
  register: phpcli

- include: php-fpm.yml
  when: phpfpm.stat.exists

- include: php-cli.yml
  when: phpcli.stat.exists

- include: mod-php.yml
  when: modphp.stat.exists
```

`mod-php.yml`では主に各モジュールを有効化します。

```yaml:roles/php/tasks/mod-php.yml
---
- name: ensure timezone is set in apache2 php.ini
  lineinfile: dest=/etc/php/5.6/apache2/php.ini
              regexp='date.timezone ='
              line='date.timezone = {{ server.timezone }}'

- name: enabling opcache
  lineinfile: dest=/etc/php/5.6/apache2/php.ini
              regexp=';?opcache.enable=\d'
              line='opcache.enable=1'

- name: enabling modules
  lineinfile:
    dest: /etc/php/5.6/apache2/php.ini
    backrefs: yes
    regexp: '{{item.regexp}}'
    line: '{{item.line}}'
  with_items: '{{php.config}}'
  when: php.config is defined
```

`pecl.yml`ではPECLで扱っているパッケージのインストールができます。今回は特に使っていませんが。

```yaml:roles/php/tasks/pecl.yml
- name: Install
  apt: pkg="php5-dev" state=present
  when: php.pecl_packages is defined

- name: Install Package
  shell: echo "\n\n\n\n\n\n\n\n\n" | pecl install {{ item }}
  register: pecl_result
  changed_when: "'already installed' not in pecl_result.stdout"
  failed_when: "pecl_result.stderr or ('ERROR' in pecl_result.stdout)"
  with_items: "{{php.pecl_packages}}"
  when: php.pecl_packages is defined

- name: Create extension .ini file
  template: >
    src="extension.tpl"
    dest="/etc/php5/mods-available/{{ item }}.ini"
    owner="root"
    group="root"
    mode=0644
  with_items: "{{php.pecl_packages}}"
  when: php.pecl_packages is defined

- name: Enable extension
  shell: php5enmod {{ item }}
  with_items: "{{php.pecl_packages}}"
  when: php.pecl_packages is defined
```

# おわりに
phansibleがほぼほぼ使えるものを作ってくれるので、少し修正するだけですぐに動かすことができました。今までは環境構築を割と手動でやっていたので、かなり楽になりました。
たぶん最近は仮想環境が主流だと思うのですが、VagrantやDockerと組合せても使えそうなので、積極的に使っていきたいですね！
環境もコードで管理しよう！