Ansible と Vagrant を使って Rails 開発環境（Ubuntu + rbenv + MySQL5.6 + node.js）を構築する

## バージョン＆記載ソースリポジトリ

| version | 
|:---|
| ansible 1.9.1 |
| Vagrant 1.7.2 |
| VirtualBox 4.3.28 |

ポイントとなるソースのみ載せています。全てのソースが見たい場合は、以下のリポジトリを参照してください。

* https://github.com/katsuhiko/template-rails-ansible

Ansible で構築したサーバーのアプリの各バージョン

| version |
|:---|
| Ubuntu 14.04.2 LTS |
| ruby 2.2.2p95 (rbenv) |
| mysql 5.6.19 |
| Rails 4.2.3 |
| Bundler 1.10.5 |
| nodejs 0.12.5 |
| npm 2.11.2 |
| bower 1.4.1 |
| grunt-cli 0.1.13 |


## 概要

Ansible を使って、Rails 開発環境のテンプレートを作りました。
Docker で作るべきか悩みましたが、みんなのローカル環境に展開するための環境を Docker とするべき理由を理解することができませんでした。
Docker での開発環境構築については、これから勉強していきます。

今回は、Amazon Linux ではなく、Ubuntu（Vagrant + VirtualBox） で作っています。


## rbenv, ruby のインストール

https://github.com/avsej/ansible-rbenv/blob/master/tasks/main.yml

これを参考にしました。
Ansible では、rbenv のモジュールが提供されていないので、shell:, command: を使うことになります。
Ansible のモジュールはたくさん出ているので、いつか rbenv モジュールが提供されるかもしれません。そうすれば、もっとスッキリした感じで書けると思います。

ignore_errors: というパラメーターを知りました。
Ansible はエラーが出た時点で処理が止まりますが、このパラメーターを使うことで、エラーが無視されます。
コンソールに赤文字で「failed:...」と表示され、その下に「...ignoring」が表示されるます。
問題はないのですが、failed と表示されることが気になります。
ignore_errors: を使わない方法があるのであれば、その方法を利用したほうが良いと思いました。

```yaml:ansible/roles/rbenv/tasks/main.yml
---
- name: Install dependencies for rbenv
  apt: name={{ item }} state=latest
  with_items:
    - git

- name: Install rbenv
  sudo: yes
  sudo_user: "{{ rbenv_user }}"
  git: repo=https://github.com/sstephenson/rbenv.git dest=~/.rbenv

- name: Add ~.rbenv/bin to PATH
  sudo: yes
  sudo_user: "{{ rbenv_user }}"
  lineinfile: >
    dest="~/.profile"
    line="export PATH=$HOME/.rbenv/bin:$PATH"

- name: Eval rbenv init in ~/.profile
  sudo: yes
  sudo_user: "{{ rbenv_user }}"
  lineinfile: >
    dest="~/.profile"
    line='eval "$(rbenv init -)"'

- name: Install dependencies for ruby-build (see. https://github.com/sstephenson/ruby-build/wiki)
  apt: name={{ item }} state=latest
  with_items:
    - autoconf
    - bison
    - build-essential
    - libssl-dev
    - libyaml-dev
    - libreadline6-dev
    - zlib1g-dev
    - libncurses5-dev
    - libffi-dev
    - libgdbm3
    - libgdbm-dev

- name: Install ruby-build as rbenv plugin
  sudo: yes
  sudo_user: "{{ rbenv_user }}"
  git: repo=https://github.com/sstephenson/ruby-build.git dest=~/.rbenv/plugins/ruby-build

- name: Check if version is installed ruby
  shell: "sudo -iu {{ rbenv_user }} rbenv versions | grep {{ rbenv_ruby_version }}"
  register: rbenv_check_install
  changed_when: False
  ignore_errors: yes

- name: Install ruby
  command: "sudo -iu {{ rbenv_user }} rbenv install {{ rbenv_ruby_version }}"
  when: rbenv_check_install|failed

- name: Check if version is the default ruby version
  shell: "sudo -iu {{ rbenv_user }} rbenv version | grep {{ rbenv_ruby_version }}"
  register: rbenv_check_default
  changed_when: False
  ignore_errors: yes

- name: Set default ruby version
  command: "sudo -iu {{ rbenv_user }} rbenv global {{ rbenv_ruby_version }}"
  when: rbenv_check_default|failed
```

## mysql インストール

以下のエラーが発生して、悩みました。

```
invoke-rc.d: initscript mysql, action "start" failed.
dpkg: error processing package mysql-server-5.6 (--configure):
 subprocess installed post-installation script returned error exit status 1
Errors were encountered while processing:
 mysql-server-5.6
E: Sub-process /usr/bin/dpkg returned an error code (1)
```

仮想環境のメモリが少ないためにエラーが発生していました。メモリを 1G 以上割り当てればエラーを回避できることを確認しました。Vagrant ファイルは別途記載します。

ホストからも mysql に接続できるようにするために IPアドレスによる制限をかけないようにしています。これはローカルで動かす開発環境ならではの設定です。
絶対に本番環境で、このような設定にはしないでください。

```yaml:ansible/roles/mysql/tasks/main.yml
---
- name: Install mysql
  apt: name={{ item }} state=latest
  with_items:
    - mysql-server-5.6
    - python-mysqldb

- name: Set mysql service to start on boot
  service: name=mysql state=started enabled=true

- name: Put my-develop.cnf
  template: src=my-develop.cnf.j2 dest=/etc/mysql/conf.d/my-develop.cnf backup=true mode=0644
  notify: restart mysql

- name: Set the root privileges
  mysql_user:
    user: root
    host: "{{ item }}"
    password: ""
  with_items:
   - "%"
   - 127.0.0.1
   - ::1
   - localhost
```

以下のテンプレートの設定は、外部から接続をさせないローカル環境ならではの設定です。本番環境では絶対にしてはダメな設定です。
conf.d 配下に設定ファイルを置くことで上書きできるようになったので、my.cnf を修正しなくても設定変更できます。
上書きしたい設定内容が増えてきたら設定情報の変数化を行う予定です。

```py:ansible/mysql/templates/my-develop.cnf.j2
# {{ ansible_managed }}
 
[mysqld]
bind-address = 0.0.0.0
```

## node.js のインストール

therubyracer でも良いのですが、Javascript の開発ツールを使うことが多いので、node.js をインストールします。

node.js は setup 用のシェルをダウンロードしてから実行するタイプです。
shell: モジュールを使っていて、ansible を実行するたびに changed と判断されてしまっています。この箇所は改善すべき箇所ですが、良い方法を思いついていません。

```yaml:ansible/roles/nodejs/tasks/main.yml
---
- name: Download setup shell for nodejs
  get_url: url="https://deb.nodesource.com/setup_0.12" dest="/tmp/setup_node.sh" mode=0755

- name: Setup nodejs
  shell: /tmp/setup_node.sh

- name: Install nodejs
  apt: name=nodejs state=latest
```

## 開発ツールのインストール

rbenv の gem に対してインストールする方法は、現時点(2015.06.28)ではこの方法がスマートだと思います。
shell:, command: モジュールを利用する方法もあるようですが、gem: モジュールを使ったほうがスッキリ書けます。

```yaml:ansible/roles/env/tasks/main.yml
---
- name: Install mysql
  apt: name={{ item }} state=latest
  with_items:
    - libmysqld-dev

- name: Install rails and bundler
  sudo_user: "{{ rbenv_user }}"
  gem: name={{ item }} executable=.rbenv/shims/gem user_install=False
  with_items:
    - rails
    - bundler

- name: Install javascript tools
  npm: name={{ item }} state=latest global=yes
  with_items:
    - bower
    - grunt-cli
```

## Vagrant と Ansible のファイル

今回、roles の位置を ansible 配下に移動したので、ansible.cfg でパスを変更しています。

```ini:ansible.cfg
[defaults]
roles_path = ./ansible/roles/
```

playbook は単純です。各ロールを呼んでいるだけです。

```yaml:develop.yml
---
- name: Build a Rails development environment
  hosts: all
  sudo: yes
  sudo_user: root

  roles:
    - apt-upgrade
    - rbenv
    - mysql
    - nodejs
    - env
```

Vagrant ファイルです。
MySQL5.6 の起動時のエラー回避のためにメモリを 1G（vb.memory = "1024"）にしています。
その他は特に変わった設定はしていません。

2015.07.12 開発をしていると MySQL のプロセスが落ちることがわかりました。メモリを2G（vb.memory = "2048"）へ、さらに変更することで安定して開発を行えています。

2015.07.25 仮想環境の時刻を HOST に合わせる設定を追加しました。

```rb:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

  config.vm.network "forwarded_port", guest: 3000, host: 3000
  config.vm.network "private_network", ip: "192.168.33.20"

  config.vm.synced_folder "../", "/home/vagrant/projects", type: "nfs"

  config.vm.provider "virtualbox" do |vb|
    # Customize the amount of memory on the VM:
    vb.memory = "2048"
    # Set the time to HOST
    vb.customize ["setextradata", :id, "VBoxInternal/Devices/VMMDev/0/Config/GetHostTimeDisabled", 0]
  end

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

## 実行方法

[VirtualBox](https://www.virtualbox.org), [Vagrant](http://vagrantup.com), [Ansible](http://www.ansible.com) をインストールして、以下のコマンドを入力すれば、開発環境を構築できます。

```
$ git clone https://github.com/katsuhiko/template-rails-ansible.git
$ cd template-rails-ansible
$ vagrant up
```

## 感想

Ansible Galaxy で調べて参考しながら、作っていくことに慣れてきました。
今回は、rbenv のインストールが一番勉強になりました。

全体的に変数化しなかったので、各 role は使い回しができなくなっています。
これをベースとして、作り込み、使い回しができるようにしていきたいです。

Mac で動作を試しました。Windows で動作するかを試せていません。
Windows でも動かしたい場合、Windows に Ansible をインストールするより、Ansible を仮想環境上で実行するほうが、ハードルは低いかもしれません。

Ansible は関係ないですが、Windows のファイル共有問題については、
http://tech.respect-pal.jp/vagrant-synced_folder-type/
こちらのサイトが役に立ちそうです。