CentOSローカル開発環境構築用のAnsibleのplaybookを作った


## 概要

VagrantでCentOS6.6にGit,(私の)dotfiles,Zsh,Oh-My-Zsh,peco,tmux,Vim,Nginx,MySQL,rbenvをインストールしてローカル開発環境構築を自動化するAnsibleのplaybookを作った。

以下が作ったplaybook。

shifumin/vagrant-ansible-centos
https://github.com/shifumin/vagrant-ansible-centos


## 背景

Railsで遊ぶためにVagrantでローカル開発環境を構築しようと思ったけど、どうせなら環境構築を自動化したいと思い、構成管理ツールの中で興味があったAnsibleの勉強がてらVagrantのCentOSのローカル開発環境構築用のAnsibleのplaybookを作った。Railsと言いながらrbenvのインストールまでの自動化になっている^^;


## 環境

* VirtualBox 5.0.2
* Vagrant  1.7.4
* Ansible 1.9.2
* Vagrant Box chef/centos-6.6


## 実行

```
$ git clone git@github.com:shifumin/vagrant-ansible-centos.git
$ cd vagrant-ansible-centos
$ vagrant up
```


## playbookの内容

CentOS6.6に

* Git 2.5.0
* (私の)dotfiles( https://github.com/shifumin/dotfiles )
* Zsh 5.0.8 (+Oh-My-Zsh)
* peco
* tmux 2.0
* Vim 7.4
* Nginx
* MySQL 5.6
* rbenv

をインストールする。

各rolesの詳しい内容は以下のとおり。

### yum_repo
* EPELリポジトリを追加
* Remiリポジトリを追加
* Nginxリポジトリを追加
* MySQL-communityリポジトリを追加

### yum_install

* 必要そうなパッケージをインストール
* Development Tools をグループインストール

### Git
* 依存パッケージのインストール
* ソースコードからGitをダウンロードして解凍してインストール

### dotfiles
* dotfilesをgit cloneする
* 各ドットファイルのシンボリックリンク張りのスクリプトを実行

### Zsh
* 依存パッケージのインストール
* ソースコードからZshをダウンロードして解答してインストール
* Oh-My-Zshのインストール
* ログインシェルをzshに変更

### peco
* pecoのバイナリをダウンロードして解答して/usr/local/binにコピーする。

### tmux
* ソースコードからlibeventをダウンロードして解凍してインストール
* ソースコードからtmuxをダウンロードして解答してインストール 

### Vim
* Vimパッケージ他のインストール

### Nginx
* Nginxパッケージをインストールする
* ginxを起動し、自動起動の設定をonにする

### MySQL
* MySQLパッケージをインストールする
* mysqldを起動し、自動起動の設定をonにする。

### rbenv
* 依存パッケージのインストール
* rbenvのインストール
* ruby-buildのインストール
* rbenv-default-gemsのインストール
* ~/dotfiles/default-gemsのシンボリックリンク張り
* rbenv-updateのインストール
* ソースコードからRSenseをダウンロードして解凍してインストール


## ディレクトリ構成

```
vagrant-ansible-centos/
├── playbook/
│   ├── group_vars/
│   │   └── all.yml
│   ├── roles/
│   │   ├── dotfiles/
│   │   │   └── tasks/
│   │   │       └── main.yml
│   │   ├── git/
│   │   │   └── ...
│   │   ├── rbenv/
│   │   │   └── ...
│   │   ├── tmux/
│   │   │   └── ...
│   │   ├── vim/
│   │   │   └── ...
│   │   ├── yum_repo/
│   │   │   └── ...
│   │   ├── yum_update/
│   │   │   └── ...
│   │   └── zsh/
│   │   │   └── ...
│   ├── hosts
│   └── site.yml
├── Vagrantfile
└── ansible.cfg
```

基本的には、[公式のベストプラクティス](http://docs.ansible.com/ansible/playbooks_best_practices.html)に沿った構成になっているはず。
メインのplaybookは **playbook/site.yml** 、inventoryファイルは **playbook/hosts**、変数の格納ファイルは **playbook/group_vars/all.yml** とした。


## 詰まったところ

全てがSSH関連でほとんどがgitモジュール関連。

### SSH Error: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password)
#### 問題

ansible-playbookしようとすると、初っ端に以下のエラーが出る。

```
fatal: [192.168.xx.xx] => SSH Error: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
    while connecting to 192.168.42.30:22
It is sometimes useful to re-run the command using -vvvv, which prints SSH debug output to help diagnose the issue.
```

公開鍵が云々。

#### 解決方法

原因は[これ](http://mawatari.jp/archives/solution-of-vagrant-ssh-connection-error)らしい。
ansible.cfgに **private_key_file** を付け加える。

```ansible.cfg
[defaults]
private_key_file = .vagrant/machines/web/virtualbox/private_key
```


### Warning: Permanently added the RSA host key for IP address '192.30.xx.xx' to the list of known hosts

#### 問題

gitモジュールでgit cloneしようとすると、以下のエラーが出る。

```
failed: [192.168.xx.xx] => {"cmd": "/usr/local/bin/git ls-remote '' -h refs/heads/HEAD", "failed": true, "rc": 128}
stderr: Warning: Permanently added the RSA host key for IP address '192.30.xx.xx' to the list of known hosts.
Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

msg: Warning: Permanently added the RSA host key for IP address '192.30.xx.xx' to the list of known hosts.
Permission denied (publickey).
fatal: Could not read from remote repository.
```
known hostsにhostkeyが云々。

#### 解決方法

Vagrant上に構築する仮想環境に秘密鍵を置かずにGithubとやり取りをするためにansible.cfgに **ForwardAgent=yes** を付け加える。

```ansible.cfg

[ssh_connection]
ssh_args = -o ForwardAgent=yes
```


### Failed to find required executable git
#### 問題

同じく、gitモジュールでgit cloneしようとすると、以下のエラーが出る。

```
failed: [192.168.xx.xx] => {"failed": true}
msg: Failed to find required executable git

FATAL: all hosts have already failed -- aborting
```

gitのバイナリがない？って言われる。

#### 解決方法

sudo時にgitへのPATHが通っていないからっぽい。
gitモジュールではsudoじゃなくてユーザで実行させるために、taskに **sudo=no** をつける。

```playbook/roles/dotfiles/tasks/main.yml
---
- name: git clone dotfiles repository
  sudo: no
  git:
    repo=git@github.com:shifumin/dotfiles.git
    dest=/home/{{ user }}/dotfiles
    accept_hostkey=yes
```


### github.com has an unknown hostkey
#### 問題

さらに同じく、gitモジュールでgit cloneしようとすると、以下のエラーが出る。

```
failed: [192.168.xx.xx] => {"failed": true}
msg: github.com has an unknown hostkey. Set accept_hostkey to True or manually add the hostkey prior to running the git module
```

github.comに登録されてあるhostkeyが違う？ので、accept_hostkeyを有効にしろって言われる。

#### 解決方法

playbookの一番最初に実行するgitモジュールのtaskに **accept_hostkey=yes** をつける。
初回のみでいいはず。

```playbook/roles/dotfiles/tasks/main.yml
---
- name: git clone dotfiles repository
  sudo: no
  git:
    repo=git@github.com:shifumin/dotfiles.git
    dest=/home/{{ user }}/dotfiles
    accept_hostkey=yes
```


## 以下メモ
### 変数をどこに格納するか

変数をどこに書くかに大分迷ったけど、変数の使い分け等をしない場合は **group_vars/all** に書いておけば特に指定しなくても全rolesに適用されるようだ。
また、ファイル名をall.ymlとすることで、シンタックスハイライトを付けることができる(YAMLファイルにシンタックスハイライトを設定している場合)

したがって、変数は、 **group_vars/all.yml** に記述することにした。


### ソースコードをダウンロードして解凍してmake installするやり方

他の人の方法を見ていたら色々なやり方・書き方があることが分かって、当初は全てをshellモジュールを使って一気通貫で処理させていた。
例えば以下のように記述していた。

```
- name: install tmux
  shell: |
    wget https://github.com/tmux/tmux/releases/download/{{ tmux_ver }}/tmux-{{ tmux_ver }}.tar.gz
    tar zxvf tmux-{{ tmux_ver }}.tar.gz
    rm -f tmux-{{ tmux_ver }}.tar.gz
    cd tmux-{{ tmux_ver }}
    ./configure
    make
    make install
  args:
    chdir: "{{ src_dir }}"
```

しかし、shellモジュールは冪等性が担保されずに初回以降も毎回実行されて
(上手く条件分岐すれば回避できるだろうけど)スキップされないので、Ansible標準のモジュールを使いtaskを分けるようにした。

つまり、以下のように、get_urlモジュールでソースコードをダウンロードして、unarchiveモジュールで解凍して、最後のmake installだけをshellモジュールで処理するようにした。

```playbook/roles/tmux/tasks/tmux.yml
---
- name: download tmux source
  get_url:
    url=https://github.com/tmux/tmux/releases/download/{{ tmux_ver }}/tmux-{{ tmux_ver }}.tar.gz
    dest="{{ src_dir }}/tmux-{{ tmux_ver }}.tar.gz"

- name: extract tmux source
  unarchive:
    src="{{ src_dir }}/tmux-{{ tmux_ver }}.tar.gz"
    dest="{{ src_dir }}"
    copy=no

- name: install tmux
  shell: >
    {{ item }}
    chdir="{{ src_dir }}/tmux-{{ tmux_ver }}"
    creates="{{ bin_dir }}/tmux"
  with_items:
    - ./configure
    - make
    - make install
```

get_urlモジュールはdestに既にファイルが存在する場合はスキップされ、unarchiveモジュールもdestのディレクトリに解凍後のファイルがあればスキップされるので冪等性は保たれる。そして、最後のmake installのshellモジュールはcreatesでインストールされていたらスキップするようにした。


### YAMLで複数行にわたる書き方

上記の、ソースコードからmake installするshellモジュールの書き方を考えている時に、どうしたらきれいに書けるだろうと調べていたら、YAMLにはLiteral StyleとFolded Styleという書き方があることを知った。

#### Literal Style

Literal Styleはインジケータとして **"|"** を使う書き方で、

```lireral_style
text: |
  hoge
  foo
  bar
```

```結果
"text" => "hoge\nfoo\nbar\n"
```

各行の改行が保存される。


#### Folded Style

そして、Folded Styleは、インジケータとして **">"** を使う書き方で、

```folded_style
shell: >
  hoge
  foo
  bar
```

```結果
"text" => "hoge foo bar\n"
```

各行の改行が半角スペースに置換され、最終行の改行は保存される。


この2つを使えば、例えば、shellモジュールで./configureした後にmakeしてその後にmake installする処理は、

```
shell: |
  ./configure
  make
  make install
```

と書けるし、

./configure && make && make intallする処理を複数行に分けて書きたい時は、

```
shell: >
  ./configure &&
  make &&
  make install
```

と書くことができる。


詳しくは、

[YAML Ain’t Markup Language (YAML™) Version 1.2](http://www.yaml.org/spec/1.2/spec.html)

の **8.1.2. Literal Style** と **8.1.3. Folded Style** 辺りに書いてあった。


## 終わり

ゆくゆくはさくらVPSのCentOS本番環境構築自動化に取り組みたい。

・・・の前にNginxとMySQLの初期設定の自動化にも取り組みたい。


## 参考

[Best Practices — Ansible Documentation](http://docs.ansible.com/ansible/playbooks_best_practices.html)

[Configuration file — Ansible Documentation](http://docs.ansible.com/ansible/intro_configuration.html)

[git - Deploy software (or files) from git checkouts — Ansible Documentation](http://docs.ansible.com/ansible/git_module.html)

[YAML Ain’t Markup Language (YAML™) Version 1.2](http://www.yaml.org/spec/1.2/spec.html)

[Ansibleを使い出す前に押さえておきたかったディレクトリ構成のベストプラクティス - 双六工場日誌](http://sechiro.hatenablog.com/entry/2015/01/06/Ansible%E3%82%92%E4%BD%BF%E3%81%84%E5%87%BA%E3%81%99%E5%89%8D%E3%81%AB%E6%8A%BC%E3%81%95%E3%81%88%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%8B%E3%81%A3%E3%81%9F%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF)

[Ansible で git clone させる - Qiita](http://qiita.com/seizans/items/f5f052aec1592c47767f)

[Vagrant 1.7+でSSH接続エラーが出た場合の対処法 | mawatari.jp](http://mawatari.jp/archives/solution-of-vagrant-ssh-connection-error)