ansibleの使い所


この記事はクラウドワークス Advent Calendar 2015 2日目の記事になります。
いろいろある構成管理ツールの中でansibleがどういう場合に適しているか使い所を構成の例を入れながら紹介します。

# 対象
* 実案件でつかってみたいけど、どういう構成にすればいいかわからない人
* そもそもansibleが向いているのか向いていないのかイメージできない人

# 個人的にansibleが適していると思われるポイント
* 顧客環境の制約などインフラそのものを自分たちで管理できない
* サーバーに導入できるツールに制限がある
* 踏み台構成になっていて接続が面倒
* お金かけられない
* 時間かけられない

だいたいこの辺はansibleが向いているところだと思います。
ansibleはサーバー自体には何もエージェントをインストールする必要がなく、SSHで接続してplaybook(chefでいうcookbookに相当)を実行していくので手動でやっていたことをそのまま自動化するイメージに近いです。

クラウドワークスのサービスではchefをメインで使っていて、一部分でOpsWorksを使っていますが、ansibleはそんなに大規模でもないしchefはオーバースペックだな、、とか、AWSじゃないからOpsWorksも使えない、、みたいな場合に有効だと思います。

自分が使ったきっかけはあるPJでサーバー１台構成で最初shellでサーバー設定書いてあるだけだったので、さすがにつらいと思ってansibleにしようってなって移行させました。

# ansibleの概要
ansibleはpython製のツールでmac使っている人なら下記コマンドで一発インストールできます。

```
brew install ansible
```

基本的な動作についてはこちらのチュートリアルをやればだいたいわかると思います。
[ansible-tutorial](http://yteraoka.github.io/ansible-tutorial/)

# ansible-playbookの開発で入れておいたほうがいいもの
ansible入れただけだと、chefでいう `bundle exec knife cookbook create` 等にあたる雛形作成の機能がないので、 `ansible_utils` を入れておくとすこしだけはかどります。

```
gem install ansible_utils
```

使い方：[Usage](https://github.com/pferdefleisch/ansible_utils#usage)

utilと言っても雛形作成の機能しかないので、knifeとは全く別物です。

※[volanja/ansible_tools](https://github.com/volanja/ansible_tools)こういうのもあった。

# ディレクトリ構成と動作
ansible-playbookのディレクトリ構成はいろいろな構成をとることができます。一応公式でも [ベストプラクティス](http://docs.ansible.com/ansible/playbooks_best_practices.html) は紹介されていますが、それぞれの環境にあわせてカスタマイズするのがよいと思います。
ちなみに公式ベストプラクティスだとロールごとでユーザーを変えたり、踏み台接続はできません。

なので、今回は公式のベストプラクティスよりももう少し複雑にして、下記の要件を満たすような構成を参考に、どのように開発するかを紹介します。

* 適用したいサーバーはdevelopment(vagrant)とproduction
* それぞれ1台構成
* なのでgroupはappのみ
* ロールによって実行ユーザーを変更したい（初期ユーザーとdeployユーザー）
* productionは踏み台経由

※ansibleのロールはchefでいうレシピに該当します。

## ディレクトリ構成

```
.
├── README.md
├── ansible.cfg - パスや、実行コマンドの設定
├── development - 環境ごとのインベントリファイル
├── production - 環境ごとのインベントリファイル
├── group_vars - groupごとの変数を格納
│   └── app.yml
├── playbook-app.yml - 初期ユーザーで実行したいplaybook
├── playbook-deploy.yml - deployユーザーで実行したいplaybook
├── roles - ロール
│   ├── common
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       └── hoge.sh.j2
│   ├── deploy
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       ├── database.yml.j2
│   │       └── production.rb.j2
│   ├── memcached
│   │   └── tasks
│   │       └── main.yml
│   ├── mysql
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       └── my.cnf.j2
│   ├── nginx
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       └── nginx.conf.j2
│   └── ruby
│       ├── handlers
│       │   └── main.yml
│       └── tasks
│           └── main.yml
├── site.yml - 全体のplaybook
├── ssh_config - ssh接続に関する設定
└── vars - 環境ごとの変数を格納
    ├── development
    │   ├── main.yml
    │   └── secret.yml
    └── production
        ├── main.yml
        └── secret.yml
```

命名がイケてない感はあるのですが、上記のファイルがどのように実行されていくのかみてみます。

## ansibleの動作

まず、productionにplaybook全体を実行するには下記のコマンドを使います。

```
ansible-playbook -i production site.yml
```

productionのインベントリファイルでsite.ymlを実行しています。

productionの中身は下記のようになっており、

```ini:production
[app]
server01

[all:vars]
stage=production
custom_ssh_user=deploy
custom_ssh_private_key_file=~/.ssh/id_rsa
```
appグループにserver01を登録しています。
また、production全体に適用される変数を`[all:vars]`で定義しています。

このserver01というのはssh_configで定義していて、

```ini:ssh_config
ForwardAgent yes

Host server00
  HostName      ***.***.***.***
  Port          22
  User          admin00
  IdentityFile  ~/.ssh/admin00
Host server01
  HostName      ***.***.***.***
  Port          22
  User          admin01
  IdentityFile  ~/.ssh/admin01
  ProxyCommand  ssh -CW %h:%p server00
```
こんなかんじで通常のsshのconfigと同じように踏み台設定を記載します。

このssh_configはどこで読み込まれているかというと、

```ini:ansible.cfg
[defaults]
roles_path = ./roles
executable = /bin/bash -l

[ssh_connection]
ssh_args = -F ssh_config -o ForwardAgent=yes
```
でssh実行次の引数に指定しています。
ansible.cfgはこれ以外にも、ansible実行時のいろいろな設定を指定することができます。[Ansible Documentaition | Configuration file](http://docs.ansible.com/ansible/intro_configuration.html)

site.ymlの中身は下記のようにしています。

```yaml:site.yml
- hosts: app
  include: playbook-app.yml
  vars_files:
    - vars/{{ stage }}/main.yml
    - vars/{{ stage }}/secret.yml

- hosts: app
  include: playbook-deploy.yml
  vars_files:
    - vars/{{ stage }}/main.yml
    - vars/{{ stage }}/secret.yml
  vars:
    - ansible_ssh_user: '{{ custom_ssh_user }}'
    - ansible_ssh_private_key_file: '{{ custom_ssh_private_key_file }}'

```

同じappというhostsに対して、実行するplaybookを分けています。
playbook-deployの方は、varsでユーザーを上書きすることによって
playbook-app.ymlの実行には、admin01ユーザーで、
playbook-deploy.ymlの実行には、deployユーザーで、
それぞれロールを実行することができます。

また、productionインベントリで指定したstage変数にはproductionが入っているので、
vars_filesで環境ごとの変数が読み込まれます。
main.ymlはパスやDB名など、リポジトリに含めてよい情報。
secret.ymlはパスワードなど、リポジトリに含めない情報を入れる方針にしています。
こういった公開したくないデータは、chefの場合はdatabag等に暗号化していれます。ansibleにも[Vault](http://docs.ansible.com/ansible/playbooks_vault.html)という暗号化のための仕組みが用意されていますが、時間短縮のためという目的からはずれるので使っていません。プロジェクトの人数が少なければgitignoreしておいて、secret.yml自体は権限が必要なNAS領域等においておくくらいの運用でも問題なさそうです。

playbook-app.ymlとplaybook-deploy.ymlは順番に実行されていきます。

```yaml:playbook-app.yml
- hosts: app
  roles:
    - common
    - mysql
    - nginx
    - memcached
```

```yaml:playbook-deploy.yml
- hosts: app
  roles:
    - ruby
    - deploy

```

rubyに関するdeployユーザー行いたい設定はplaybook-deploy.ymlで、それ以外はplaybook-app.ymlで行うようにしています。

roleの書き方は一番短いmemcachedを参考にあげると、下記のようになっています。

```yaml:main.yml
- name: install memcached packages
  sudo: yes
  yum: name={{ item }} state=present
  with_items:
    - memcached

- name: start memcached
  sudo: yes
  service: name=memcached state=started enabled=yes
```

taskひとつひとつにnameをつけていきます。
このnameは任意のtaskのみ実行したいときに

`ansible-playbook -vvvv -i production --start-at='install memcached packages' --step site.yml`

このように指定するためユニークな名前をつけておくとよいでしょう。
その他上記の例では設定ファイル等のテンプレートをtemplatesディレクトリに、コールバック処理（apacheをインストールしたらservice httpd startするなど）をhandlerに格納しています。

実際にroleに記述するモジュールの解説はここではしません。

# ansibleのハマりどころ
ハマりどころをいくつか紹介します。

## 変数やグループの考え方がわかりにくい
何をどう定義するとどう反映されるのかが結構わかりづらい気がします。
理由はディレクトリ構成や変数の設定の仕方が何パターンかあることに起因すると思います。

上の例では、productionインベントリでproduction全体に適用される変数を`[all:vars]`で定義しましたが、`[app:vars]`と書くとappグループにのみ適用される変数を定義することができます。

また、group_varsディレクトリ以下にグループ名.ymlとしてグループ変数を定義することもできます。
上の例ではgroup_vars/app.ymlを定義して、app全体で使う変数を定義しています。
group_varsディレクトリの中のファイルはファイル名をグループ名としておくと自動的に読み込まれます。

また、グループ変数ではないですが、インベントリでホストのみに変数を定義したり、上のsite.ymlの例のSSHユーザーの上書きのようにplaybook単位で変数を定義することもできます。

逆に言えば、変数やグループに関してはかなり柔軟な構造をとれるということでもあるので、理解して使えばそれぞれのやりたいことに合った構成をとれると思います。


## デバッグしづらい
ansibleはplaybookをどう処理してどうエラーがでたかのログが出ません。デバッグの際には主に以下の２つの方法で行います。

* -vvオプションをつける
* debugモジュール
* debugerツール

### -vvオプション
playbook実行時に-vvオプションをつけるとモジュールのパラメータが出力されます。また、-vvvオプションにするとSSHの情報も表示されます。

### debugモジュール
何かの処理の結果を変数に格納して、その変数に基づいて別の処理を分岐するような処理を書いていた場合、-vvvオプションだけでは変数の中身は表示されません。
そんなときは、debugモジュールで変数の中身をログに出してあげます。

```yaml
- debug: var=variable1
```

```yaml
- debug: msg={{variable1}}
```

コマンド実行時の標準出力をansibleに表示させたい場合もこの方法で確認することができます。

### debugerツール

[ks888/ansible-playbook-debugger](https://github.com/ks888/ansible-playbook-debugger)こういうのもあるっぽいけど使ったことないのでワカラン

## commandモジュールとshellモジュール
コマンドをそのまま実行したいときには、commandモジュールとshellモジュールが使えます。
ただし以下の注意点があります。

* `&`や`|`, `>`, `<`はshellモジュールでしか使えない
* 実行されるとかならずchanged=trueになる

### `&`や`|`, `>`, `<`はshellモジュールでしか使えない
commandモジュールでは１コマンドしか実行できません。

### 実行されるとかならずchanged=trueになる
たとえばsedをつかってファイル書き換えを行う場合は、べき等性が保たれた処理になっていたとしても、処理結果はchanged=trueになります。
たとえば、serviceモジュールでhttpdの再起動を行う処理を書いた場合はすでに起動済みの場合はchanged=falseになりますが、commandやshellモジュールはそこまでカバーしてくれません。
changed=falseにしたい場合は前段で状態チェックを行う処理を行い、結果に応じて実行するしないを分ける書き方をする必要があります。

設定ファイルの変更等の場合はtemplateモジュールを使ったほうが早い場合がほとんどですので、どう置換しよう、、とか悩むことなく、テンプレートに突っ込むのがよいです。

## べき等性を信じすぎない
上にも書きましたが、使い方によってはべき等にならない場合も多々あります。実際に実行してべき等性が保たれているかきちんと確認しながら構築しないと意図した設定になっていない可能性があります。

# 最後に
ansibleは使い所を見極めれば手軽に自動化できて便利です。
間違っている点やわかりにくい点あればご指摘くださいm(_ _)m