VagrantのプロビジョンファイルをシェルスクリプトからAnsibleに書き換えてみました

# 背景

現在担当しているリプレース案件では、現ソースコードの動作を確認するため別途の開発環境を立ち上げることが必要です。そのため、VagrantでUbuntuのイメージをベースにして環境を作りました。

PHPやデータベースはVagrantのUbuntuのイメージに入っていないので、プロビジョン層でインストールしました。当時は、最もやりやすいシェルスクリプトでプロビジョンをやり、必要なコマンドを一つの`.sh`ファイルにまとめて、Vagrant環境を最初起動するときに自動で実行されるようにしました。

しかし、環境を作るにあたりいくつかの問題が見つかりました。[冪等性](https://ja.wikipedia.org/wiki/%E5%86%AA%E7%AD%89)が保証できない上、長時間触らないと書いたコードは徐々に忘れてしまいます。
そこで、シェルスクリプトから簡単に整理できるAnsibleのプレイブックに書き換えてみました。

# 前提

当たり前ですが、`ansible`と`vagrant`を環境に入れることが必要です。macOS上では、すごく便利なhomebrewで簡単にインストールできます。

```bash
$ brew install vagrant
$ brew install ansible
```

# 目標

本記事では、以下のプロビジョンをやります。

1. Apache をインストール
2. PHP 5.6 をインストール
3. PostgreSQL 9.2 をインストール
4. PostgreSQL ユーザーとデータベースを生成
5. DB dumpファイルをインポート
6. `/vagrant`から`/var/www`にsymlinkを生成

# Before（シェルスクリプト）

上記の目標を達成するための、書き換え前のシェルスクリプトをみてみましょう。

```bash
#!/usr/bin/env bash

###########################################################
# Config
###########################################################

# DB設定
DB_ROOT_PASS='root'
DB_USER='user'
DB_PASS='pass'
DB_NAME='db'

# Install Apache
apt-get update
apt-get install -y apache2

# Install PHP 5.6
add-apt-repository ppa:ondrej/php
apt update
apt install -y php5.6 php5.6-pgsql php5.6-mbstring php5.6-gd
apt install -y libapache2-mod-php5.6 libapache2-mpm-itk
a2enmod php5.6

# Install postgreSQL 9.2
wget -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
echo deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main | tee /etc/apt/sources.list.d/postgresql.list
apt-get update
apt-get install -y postgresql-9.2 postgresql-contrib-9.2

# DBとユーザーを生成
sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH SUPERUSER CREATEDB ENCRYPTED PASSWORD '${DB_PASS}'"
sudo -u postgres createdb ${DB_NAME}

# DB dumpファイルをインポート
sudo -u postgres psql -f db.dump -d ${DB_NAME}

# /vagrantから/var/wwwにsymlinkを生成
rm -rf /var/www
ln -fs /vagrant /var/www


# restart apache
service apache2 restart
# restart postgresql
service postgresql restart
# done
echo "Provisioning completed."

```

# 書き換えましょう！

## 1. playbook.ymlファイルの用意
Ansibleでは、全ての設定やコマンドはYAMLファイルにて管理します。基本の構成は以下のようです：

```yaml
---
- hosts: all
  become: yes
  become_user: root

  tasks:
    - name: Sample task 1
    - name: Sample task 2
    - ...
```



## 2. シェルスクリプトを移植
`playbook.yml`が出来ていたら、まず既存のコマンドはそのままで移植します。書き方はこんな感じです：

```yaml
---
- hosts: all
  become: yes
  become_user: root

  tasks:
    - name: 'apt-getを更新'
      shell: apt-get update
      
    - name: 'apacheをインストール'
      shell: apt-get install -y apache2

    - ...
```

(Github Gistは[こちら](https://gist.github.com/adwinying/30cb07935da19e1264dca5f1ead798e6))

各コマンドに説明を付けるとわかりやすくなりますが、
冪等性は前と変わりませんでした。そこで、モジュールを導入しましょう。



## 3. モジュールの導入
Ansibleのモジュールは、いろんなアプリのAPI経由で定義された結果を達成します。その上モジュールを使うと、定義された結果をすでに達成していた場合には変更はされません。

### 例1: aptモジュール
[apt](https://docs.ansible.com/ansible/latest/modules/apt_module.html)モジュールは、パッケージのインストールや削除ができます。

```yaml
  # before
  tasks:
    - name: 'apt-getを更新'
      shell: apt-get update
      
    - name: 'apacheをインストール'
      shell: apt-get install -y apache2

  # after
  tasks:
    - name: 'apacheをインストール'
      apt:
        name: apache2
        state: present
        update_cache: yes # apt-get updateと同じ
```

覚えられないフラグはモジュールのパラメーターに切り替えたので、可読性も上がりました。



### 例2: postgresql_userモジュール

例１に書いてある`apt-get install`は基本何回実行しても結果は変わらないですが、DBユーザー作成するときは同じ名前のユーザーが存在するとエラーが出ます。[postgresql_user](https://docs.ansible.com/ansible/latest/modules/postgresql_user_module.html)モジュールで、エラーを避けられる上、定義したロールやパスワードがちゃんと反映するか確認できます。

```yaml
  # before
  tasks:
    - name: 'DBを生成'
      shell: "sudo -u postgres createdb {{ DB_NAME }}"

  # after
  tasks:
    - name: '[postgres] ユーザーを生成'
      become: yes
      become_user: postgres
      postgresql_user:
        name: "{{ DB_USER }}"
        password: "{{ DB_PASS }}"
        encrypted: yes
        role_attr_flags: 'CREATEDB,SUPERUSER'
        state: present
```

Ansibleのモジュールはたくさんあるので全部紹介できないため、
他にあるモジュールはAnsibleの[docs](https://docs.ansible.com/ansible/latest/modules/modules_by_category.html)を参照してください。

## 4. Handlerを活用

Handlerはタスクと構成がほぼ一緒ですが、指定したタスクに変更があれば最後に実行します。Handlerは何回起こしても１回しか実行されません。

```yaml
  # before
  tasks:
    - name: 'phpを有効'
      shell: a2enmod php5.6

    - name: 'apacheサービスを再起動'
      shell: service apache2 restart

  # after
  tasks:
    - name: '[php] phpを有効'
      apache2_module:
        name: php5.6
        state: present
      notify:
        - '[apache] サービスを再起動'

  handlers:
    - name: '[apache] サービスを再起動'
      service:
        name: apache2
        state: restarted
```

## 5. Vagrantfileを修正

最後に、VagrantをプロビジョンをするときにAnsibleを適用するためVagrantfileに以下の修正が必要です：

```ruby
  # before
  config.vm.provision "shell", path: "bootstrap.sh"

  # after
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "playbook.yml"
  end
```

## 6. 完成！
完成物は[こちら](https://gist.github.com/adwinying/a362e92ef3f80fb629ca9a8d01157ee0)のGithub Gistをご参照ください。



# まとめ
Ansibleでプロビジョンをやると、可読性を高め、冪等性も保証でき、
よりスッキリなコードが書けます。
Vagrantで環境を作る方はぜひ試してみてください！