Ansible自分でやってみると書くの短時間で済むし、構成はシンプルで管理しやすいし、だいぶ好きになってきました。まだ開発用のVPS1台作るのに触っただけなので本格的に学べてはいないですが、導入部分とつまずきポイントをさっくりまとめときます。自分のplaybookは以下レポジトリ。
Ansibleの構成
やっていることは極めてシンプルで、サーバーの設定/構築等の処理を「モジュール」と呼ばれるリソースで定義し、指定したIPやホスト名の対象サーバーに対して、SSH経由で処理を流し込んでいくというだけ。ansible
コマンドを使ってコマンド1発だけを送ったりもできるけど、基本的にはモジュールを複数記載した playbook と呼ばれるyamlファイルを用意して、ホスト情報も インベントリファイル にリストアップしていくことになる。playbookを使用したデプロイにはansible-playbook
コマンドを使用する。
$ 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で探して書いていけばよい。こちらでも同じくname:
でタスクの概要を書いておくのが通例。ansible-playbook
コマンドの--start-at-task
オプションにこのname:
を渡すと、そのタスク以降だけを実行したりできるので、あまり冗長な名前は避けた方が良いかと思う。
よく使うモジュールとしてはユーザー制御に使うuser
とgroup
、言わずもがなのyum
、ファイル内の一部を編集するlineinfile
、テンプレートファイルを変数埋め込んで配置するtemplates
、読んで字のごとくgit
、ファイルとディレクトリ制御に使うfile
、copy
、シェルコマンド実行のためのcommand
といったところか。
taskにはいろいろと効率化のための書き方が用意されている。例えば同一のモジュールを複数回用いることがある場合には、with_items:
を使うことでループが可能。
- 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' }
とリスト型で定義すればよい。さらにループをネストしたり、複雑なこともできるっぽい。詳細は公式参照。
あと便利な使い方としては、taskにタグを設定することで、特定のタグがついたtaskだけを実行したりしなかったりという指定ができる。自分のplaybookではCentOS6と7の分岐でタグをつかっているが、本来この場面であればWhen Statementを使うべきと思うので、あまりよろしい見本ではないのかなと。
- name: only DB server
yum: name=mysql state=installed
tags:
- db
$ ansible-playbook playbook.yml --tags "db"
$ ansible-playbook playbook.yml --skip-tags "db"
handlers
書き方はtasksと同様。実行したいhanderのname:
をtask内のnotify:
で定義する。なおhandlerはnotifyされた直後の実行ではなく、全task完了後の実行となる。呼び出しというよりは、実行フラグを立てるためのものと考えた方がいい。
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 }}
の形で参照する。変数名は英数字とアンダースコアしか使えないので注意。またコロン直後の変数参照等ではダブルクォートで囲う必要のあるときがある。
変数の定義はいくつかパターンが有る。外部ファイルに定義した変数を読み込む際にはvars_files:
でパスを指定する。
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)
つまずいてもかなりのノウハウがすでに日本語でも蓄積されていることと、何より公式ドキュメントがベストプラクティス含めだいぶ充実しているので、それほど悩むことなく使いこなせるように思う。特に公式が本当に役に立つので、下手にググったりする前にすべて目を通しておくべきかなと。特に、ここに書いたのはあくまで少数のサーバー構築に使ったわずかな知識なので、より大規模な管理に必要なことはまたベストプラクティスが異なってくるはず。