Ansibleで開発用サーバー作るのに覚えたはじめの一歩

Ansible自分でやってみると書くの短時間で済むし、構成はシンプルで管理しやすいし、だいぶ好きになってきました。まだ開発用のVPS1台作るのに触っただけなので本格的に学べてはいないですが、導入部分とつまずきポイントをさっくりまとめときます。自分のplaybookは以下レポジトリ。



## Ansibleの構成

やっていることは極めてシンプルで、サーバーの設定／構築等の処理を「モジュール」と呼ばれるリソースで定義し、指定したIPやホスト名の対象サーバーに対して、SSH経由で処理を流し込んでいくというだけ。`ansible`コマンドを使ってコマンド1発だけを送ったりもできるけど、基本的にはモジュールを複数記載した **playbook** と呼ばれるyamlファイルを用意して、ホスト情報も **インベントリファイル** にリストアップしていくことになる。playbookを使用したデプロイには`ansible-playbook`コマンドを使用する。

```sh
$ ansible-playbook -i hosts playbook.yml
```

オプションとしてよく使いそうなのは`-C`で実行を伴わないDry runができたり、`-k`でSSHの、`-K`でsudoのパスワードを対話式に入力できるようになるなどのあたりか。あとは後述するが、playbook内の一部taskだけを選択して実行できるようなオプションもある。

なので言ってしまえばモジュールの書き方さえ覚えれば最初はなんとかなる。Ansibleは内部的にはPythonによるツールだが、ChefやPuppetがRubyに則った構成ファイルを自分で書かなくてはならないのに対し、AnsibleではPython自体に触れる必要がないので、だいぶ敷居は低い。

## playbook

主に使用するキー

* name: コメント。playbookの概要を簡潔に書いておくとよい。
* hosts: 対象ホスト。インベントリファイルに記載した名前を定義する。後述。
* user: playbook内のタスクを実行するユーザー。
* sudo: sudoが必要か否か指定する。
* vars: 変数。実際の変数はネストして定義していく。
* tasks: タスクのモジュールを列挙。
* handlers: タスク実行の後続で呼び出す処理を列挙。

### tasks

やりたいことを公式docsの[Module Index](http://docs.ansible.com/ansible/modules_by_category.html)で探して書いていけばよい。こちらでも同じく`name:`でタスクの概要を書いておくのが通例。`ansible-playbook`コマンドの`--start-at-task`オプションにこの`name:`を渡すと、そのタスク以降だけを実行したりできるので、あまり冗長な名前は避けた方が良いかと思う。

よく使うモジュールとしてはユーザー制御に使う`user`と`group`、言わずもがなの`yum`、ファイル内の一部を編集する`lineinfile`、テンプレートファイルを変数埋め込んで配置する`templates`、読んで字のごとく`git`、ファイルとディレクトリ制御に使う`file`、`copy`、シェルコマンド実行のための`command`といったところか。

taskにはいろいろと効率化のための書き方が用意されている。例えば同一のモジュールを複数回用いることがある場合には、`with_items:`を使うことでループが可能。

```yaml
- name: install via yum
  yum: name={{ item }} state=installed
  with_items:
    - vim
    - zsh
    - git
```

`with_items:`に定義した配列が`{{ item }}`に代入されてループ実行される。1回のループで複数の変数が必要な場合には、代入先を`{{ item.foo }}`といった形で定義し、`with_items:`は`{ foo: 'foo', bar: 'bar' }`とリスト型で定義すればよい。さらにループをネストしたり、複雑なこともできるっぽい。詳細は[公式](http://docs.ansible.com/ansible/playbooks_loops.html)参照。

あと便利な使い方としては、taskにタグを設定することで、特定のタグがついたtaskだけを実行したりしなかったりという指定ができる。自分のplaybookではCentOS6と7の分岐でタグをつかっているが、本来この場面であれば[When Statement](http://docs.ansible.com/ansible/playbooks_conditionals.html#id3)を使うべきと思うので、あまりよろしい見本ではないのかなと。

```yaml:playbook.yml
- name: only DB server
  yum: name=mysql state=installed
  tags:
    - db
```

```sh
$ ansible-playbook playbook.yml --tags "db"
$ ansible-playbook playbook.yml --skip-tags "db"
```

### handlers

書き方はtasksと同様。実行したいhanderの`name:`をtask内の`notify:`で定義する。なおhandlerはnotifyされた直後の実行ではなく、全task完了後の実行となる。呼び出しというよりは、実行フラグを立てるためのものと考えた方がいい。

```yaml
tasks:
  - name: change SSH port
    lineinfile: dest=/etc/ssh/sshd_config regexp="^#Port " line="Port {{ ssh_port }}" state=present
    notify: restart sshd

handlers:
  - name: restart sshd
    service: name=sshd state=restart
```

### vars

当然のごとく変数が使える。基本は`vars:`で変数を定義し、`{{ var }}`の形で参照する。変数名は英数字とアンダースコアしか使えないので注意。また[コロン直後の変数参照等ではダブルクォートで囲う必要のあるときがある](http://docs.ansible.com/ansible/YAMLSyntax.html#gotchas)。

変数の定義はいくつかパターンが有る。外部ファイルに定義した変数を読み込む際には`vars_files:`でパスを指定する。

````yaml:/vars/external_vars.yml
foo: foo
var: var
```

```yaml:playbook.yml
vars_files:
  - /vars/external_vars.yml
```

実行時に対話式に変数を定義させる場合は`vars_prompt:`が使える。[プロンプトはマスターするとだいぶ便利そう](http://docs.ansible.com/ansible/playbooks_prompts.html)。

```yaml
vars_prompt:
  - name: "your_name"
    prompt: "what's your name?"
    default: "John Doe"
```

またこれら変数定義を一切行わずとも、`ansible-playbook`コマンドのオプションで変数を直接定義できる。

```shell-session
$ ansible-playbook playbook.yml --extra-vars "foo=foo bar=bar"
```

## インベントリファイル

インベントリファイルは特に難しいこともない。ホスト名でもIPでもSSH接続できる形で対象サーバーを列挙しておけばよい。`~/.ssh/config`はちゃんと見に行ってくれるようなので、そちらの指定に従った定義でも可。また`[group]`の形でホストをグループ化できるので、特定のグループ全体に対して同一のplaybookを実行するといったこともできる。

```
[web_server]
192.168.1.1
web1.example.com
web2.example.com

[db_server]
192.168.10.1
```

## つまずきポイント

以降は個人的なつまずきポイントのまとめ。

* デフォルトシェルを変えたかったのだが、chshコマンドを普通に実行させても、パスワードを聞かれるところで固まってしまって動かない。⇒ [userモジュールをつかう](http://yutaka-j.hatenablog.com/entry/2013/06/16/115221)
* `sudo: yes`を指定するとplaybook全体でsudoが有効化されるが、一部sudoしたくないtaskがある（$HOMEが変わってしまって予期しない挙動になったりして困ってた）⇒ [そのtaskで`sudo: no`を指定すればよい](http://dev.classmethod.jp/server-side/ansible/ansible-separate-user/)
* `git clone`させたらpermission deniedになった ⇒ [ssh agent使え](http://safx-dev.blogspot.jp/2014/03/vagrantosgithubansible.html)。[公式にもそう書いてある](http://docs.ansible.com/ansible/intro_getting_started.html#your-first-commands)

つまずいてもかなりのノウハウがすでに日本語でも蓄積されていることと、何より公式ドキュメントがベストプラクティス含めだいぶ充実しているので、それほど悩むことなく使いこなせるように思う。特に公式が本当に役に立つので、下手にググったりする前にすべて目を通しておくべきかなと。特に、ここに書いたのはあくまで少数のサーバー構築に使ったわずかな知識なので、より大規模な管理に必要なことはまたベストプラクティスが異なってくるはず。