Ansible 入門

# Ansible 入門

社内勉強会で Ansible の初歩的な発表をしたのでその資料を公開します。
間違い等あるとおもいますが、指摘して頂ければ幸いです。

## Ansible とは？

構成管理ツールです。Chef とか Puppet とかああいうののお仲間です。
Puppet は知りませんが、Chef は少し使って嫌気が差して止めました。
理由は

* 特殊なことをしようとすると Ruby の知識が必要になる
* 構成管理「される側」にも Chef が必要(エージェント)

が主です。あとなんだかんだ言って結構複雑です。

その点 Ansible は下記の特徴があります。

* 標準入出力さえできればプラグインの言語は何でも構わない
* 一つ Ansible が入っているサーバと sshd が動いている対象サーバがあればそれでOK(要するに準備が要らない。もちろん自分自身だけでもOK)
* 標準(組込みモジュール？)で結構なんでもできる(ただし、 Chef と比較して、ではない。Chef は詳しくないので)

構成管理ツールに必須の冪等性も確保されています。

## Ansible の構成

Ansible は端的に言えば

* hosts ファイル(インベントリ)
* playbook
* モジュール

の3つの要素で成り立ちます。

### hosts ファイル

作業対象となるホストを記述します。
一般的には下記のように記述されます。

```ini:/etc/ansible/hosts
[lbserver]
192.168.1.1:1022
192.168.1.2:1022
[apserver]
192.168.2.1:1022
192.168.2.2:1022
[dbserver]
192.168.3.1:1022
192.168.3.2:1022
```

見ての通り ini 形式です。[セクション]でグループ化出来ます。このグループは後述する playbook で指定する際に使用しますが別に無くても構いません。
このファイルを`/etc/ansible/hosts`として保存すると ansible 実行時に対象ホストが定まり、各ホストで ssh 経由で色々やってくれるということです。
※ hosts の場所は`/etc/ansible/hosts`じゃなくても構いません。環境変数や`-i`オプションで指定できたりします。

1022 はポート番号ですが、標準の 22 であれば指定はいりません。また、ssh_config も見てくれるので、ポートや鍵ファイルの指定は本来そちらで行うべきでしょう。

### playbook

簡単にいえば後述のモジュールを組み合わせて、「このホストたちにはこのモジュールをこういうオプションで実行してよ」という命令書です。
yaml 形式で記述されます。

```yaml:playbook例
- hosts: dbserver
  remote_user: hoge
  sudo: yes
  gather_facts: no
  vars:
    port: 3306
    socket: "/var/lib/mysql/mysql.sock"
    datadir: "/var/lib/mysql"
  handlers:
    - name: restart mysqld
      service: name=mysqld state=restarted
  tasks:
    - name: yum install mysql
	  yum: name="{{item.name}}" enablerepo="{{item.repo}}" state=present
	  with_items:
        - {name: mysql,        repo: remi}
        - {name: mysql-server, repo: remi}
        - {name: mysql-devel,  repo: remi}
    - name: template my.cnf
      template: src=etc/my.j2 dest=/etc/my.cnf mode=0644
	  notify: restart mysqld

    - name: permit bin files
      file: path={{item}} mode=0744
      with_fileglob:
        - /root/bin/*

    - name: register
      shell: touch /tmp/hoge creates=/tmp/hoge
      register: result
    - name: print result
      debug: msg="rc:{{result.rc}} stdout:{{result.stdout}} stdout:{{result.stderr}}"
      when: result.stdout == ""
```

これは割といろいろ使ってる playbook です。
キー個別に説明します。

#### hosts

前述の hosts ファイルで指定したグループ名を指定します。
all で全サーバ対象になるようです。

#### remote_user

リモート側の実効ユーザを指定します。

#### sudo

sudo するなら yes を指定します。
上に記述はないですが `sudo_user` で sudo のユーザを指定することもできるようです。

#### gather_facts

ホスト情報を掻き集めるかのフラグです。
ホスト情報とはCPUのコア数や搭載メモリなどです。
これらは変数に代入されるので後述の template モジュールなどで威力を発揮します。
使用しない場合、無駄なので no にした方が実行速度が早くなります。

#### vars

変数を使用する場合はここで宣言します。
上の例のように宣言しておくと `{{port}}`、`${socket}`、`$datadir` などで参照することが出来ます。

#### handlers

例えば apache の設定ファイルをいじった場合に httpd を restart したい場合などがあると思います。
この handlers を指定すると、変更があった場合に指定タスクを実行してくれます。
通知は tasks の notify で行います。複数のタスクで notify され、かつそれら全てで変更があった場合でも handlers が実行されるのは1回限りです。

#### tasks

playbook のキモとなる項目です。実行するモジュールを列挙していきます。
name 指定すると実行時に出力が為されます。また、name は必須ではないようです。書かなかった場合、モジュール部分の文字列がそのまま出力されるようです。

##### with_items

タスク内で使用する変数を宣言します。
モジュールは共通で、オプションのみ異なる場合などに便利です。個人的に使用頻度は非常に高いです。
上の例で言うと mysql と mysql-server をともに remi リポジトリを使って yum install します。

##### with_fileglob

`glob` した結果を変数に代入し、その数分モジュールを実行します。
上の例で言うと `/root/bin` 以下にあるファイル全てのパーミッションを0744にします。

##### notify

handlers へ通知を行います。値には handlers の name キーを指定します。
上の例で言えば /etc/my.cnf に変更があった場合に mysqld を再起動します。

##### register

現在のモジュールの諸々の情報を指定した変数名へ代入します。
「諸々の情報」はモジュールによって異なるようです。
上記のように shell モジュールであれば `rc`, `stdout`, `stderr` が取れますし、まっとうな？モジュールであれば `changed` や `failed` が取得でき、下記 when と組み合わせることで「前回のタスク結果に依存した」処理が書けるようになります。
※ どんなモジュールがどんなキーを扱えるかはどこかにドキュメントがあるはず…

##### when

指定した条件が成り立つときのみタスクを実行します。
上記の例だと register タスクの標準出力が空のときのみ実行されるようになります。
自信ないですがおそらく初回実行時のみ実行されます。

##### changed_when

changed 結果を明示的に指定できます。
`changed_when: False` などとすると ステータスが changed になることがなくなります。

##### failed_when

基本的に changed_when と同じです。

### モジュール

ansible 自体の主な仕事は、前述の playbook の解釈やホストへの接続、変数の展開などのメタ的な仕事であって、「このファイルをコピーしてくれ」とか「このサービスを起動してくれ」とかそういう具体的な実務は一切行いません。
それら実務はすべてプラグインとして提供・実装され、「モジュール」と呼ばれています。

代表的な(今回簡単に解説する)モジュールを下記に挙げます。

| モジュール名 | 役目                                                                            |冪等性      |
|:-------------|:--------------------------------------------------------------------------------|:-----------|
| shell        |シェル上でコマンドを実行                                                         |creates     |
| file         |ファイルやディレクトリの作成、パーミッション                                     |自動        |
| lineinfile   |指定ファイルの行単位の書き換え                                                   |半自動      |
| copy         |ファイルの丸コピー                                                               |自動        |
| template     |テンプレートを利用したファイルのコピー                                           |自動        |
| synchronize  |要するに rsync                                                                   |自動        |
| get_url      |指定URLからダウンロード                                                          |自動        |
| yum          |yum コマンド。enablerepo やdisablerepo はもちろん rpm から直接インストールも可能 |自動        |
| service      |service コマンド。chkconfig も兼ねる                                             |自動        |

上記は全て標準です。
さらに前述のとおり、なにか凝ったことをしようと思ったときは自分であらゆる言語でモジュールを作成することが出来ます。

ただしモジュールはリモートに転送されて実行されます。
よって php で自前でモジュールを作成してもリモート側に php が入っていないと実行できません。

下記はよく使いそうなオプションのみ記載しています。
結構便利なオプションがあったりするので調べてみるといいです。
モジュールの詳細が知りたい場合は`ansible-doc モジュール名`で調べることが出来ます。

#### shell

シェル上でコマンドを実行します。似たようなものに command モジュールがありますが、パイプやリダイレクトなどが使えるので基本的には shell の方がいいです。

```yaml
# composer のインストール
shell: >
  curl -sS https://getcomposer.org/installer | php && mv composer.phar composer
  chdir=/usr/local/bin
  creates=/usr/local/bin/composer
```
※ 見やすいように改行いれてます(folded スタイル)。一番上が実行コマンドでその他がオプションになります。

`chdir` オプションでカレントディレクトリを指定、
`creates` オプションでそのファイルが既にあるなら実行しないようになります。

なんでもできるので便利ですが、playbook が shell ばっかりになると単に手順書を複雑に書いているだけな気分になり、やる気が削がれます。気をつけましょう。
また、ansible は各タスクごとに プロセスを生成するようなので `cd` だけのタスクとかをして無駄です。chdir オプションを使います。

#### file

ファイルやディレクトリの作成・削除、パーミッションの設定などを行います。

```yaml
# /var/log/php ディレクトリを作成して 0644 にする
file: path=/var/log/php mode=0644 state=directory owner=apache group=apache
```
```yaml
# /tmp/hoge を削除
file: path=/tmp/hoge state=absent
```

`path` で対象ファイルを指定します。
`mode` でパーミッションを指定します。
`state` で対象の種類を指定します。ファイルだったりディレクトリだったり作成だったり削除だったり。
`owner` でオーナーを指定します。
`group` でグループを指定します。

ファイルの作成は基本的に**やらないほうがいいです**。
`state=touch` を指定すれば作成されますが、毎回「changed」になるので精神衛生上良くありません。
`changed_when: False` で抑止できますが、これだと初回実行時も抑止されます。
そういうものだと割りきって shell モジュールで `shell: touch /tmp/hoge creates=/tmp/hoge` しちゃうのが良いんじゃないでしょうか。

#### lineinfile

ファイル内の特定の行を書き換えます。個人的に一番良く使います。

```yaml
# selinux を無効にする
lineinfile: dest=/etc/sysconfig/selinux regexp="^SELINUX=.*" line="SELINUX=disabled"
```
```yaml
# ssh のパスワード認証を禁止する
lineinfile: >
  dest=/etc/ssh/sshd_config
  regexp="^PasswordAuthentication"
  line="PasswordAuthentication no"
  insertafter="#PasswordAuthentication"
```
```yaml
# php.ini に include_path を追加する
lineinfile: >
  dest=/etc/php.ini
  backrefs=yes
  regexp='^include_path = "((?!.*add_path.*)(?(1)|.*))"'
  line='include_path "\1:add_path"'
```

`dest` で対象ファイルを指定します。
`regexp` で対象行を指定します(マッチした行が置換される)。
`line` で置換する文字列自体を指定します。
`insertafter` でマッチしなかった場合にここで指定した正規表現のにマッチする次の行へ挿入されます。
`backrefs` でキャプチャ文字が使えるようになりますが、`insertafter/before` が使用できなくなります。

`backrefs` を使用して既存文字列に文字列を加えるような場合、上記のような小細工が必要です。
※ ただし、説明は省く

#### copy

  ファイルをローカルからリモートへコピーします。

```yaml
# php 設定ファイルを転送する
copy: src=etc/php.d/default.ini dest=/etc/php.d/default.ini mode=0644
```

`src` でローカルの対象ファイルパスを指定します。絶対パス、あるいは yaml ファイルからの相対パスです。
`dest` でリモートのファイルパスを指定します。
`mode` でパーミッションを指定します。

#### template

テンプレートファイルを元にして、変数置換したファイルをローカルからリモートへコピーします。

```yaml
# nginx 設定ファイルを転送する
template: src=etc/nginx/nginx.j2 dest=/etc/nginx/nginx.conf mode=0644
```

`src` でローカルの対象ファイルパスを指定します。テンプレートエンジンは jinja という python のテンプレートです。
`dest` でリモートのファイルパスを指定します。
`mode` でパーミッションを指定します。

`etc/nginx/nginx.j2` の冒頭は下記のようになっています。

```
user              nginx;
worker_processes  {{ ansible_processor_vcpus }};
・・・
```

こういう環境依存のファイルを設置するときは template が便利です。
nginx のプロセス数は CPU のコア数を指定するのが望ましいですが、copy モジュールだと丸コピーなので細かな設定ができません。
copy したあと lineinfile してもいいですが、たくさんあると面倒です。

前述のとおり、変数は自分で宣言できますし、上記のように ansible が用意した変数を使うことも出来ます。ただし、上記の変数は `gather_fact:yes`していないと使用できません。

#### synchronize

rsync 的なことをします。
オプションが大量にある上、各オプションが rsync のオプションに対応しているので例は省きます。

1点注意として、src オプションに相対パスを記述すると**実効ユーザのホームディレクトリ**からの相対パスになるようです。
なんかとても微妙…。

#### get_url

指定 URL からファイルをダウンロードします。

```yaml
# fluentd インストールスクリプトをダウンロードする
get_url: url=http://toolbelt.treasuredata.com/sh/install-redhat.sh dest=/root/fluentd-install.sh
```

`url` で URL を指定します。
`dest` で保存場所を指定します。

dest が既に存在する場合は実行されません。

#### yum

yum コマンドに対応します。

```yaml
# yum で remi リポジトリから php5.5 をインストールする
yum: name=php state=present enablerepo=remi-php55
```
```yaml
# yum で mysql をインストールする
yum: name=http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm state=present
yum: name=mysql-community-server state=present
yum: name=mysql-devel state=present
```

`name` でパッケージ名を指定します。
`state` でどういう状態にするかを指定します。[present|latest|absent] = [install|update|erase]
`enablerepo` で有効にするリポジトリを指定します。
`disablerepo` で無効にするリポジトリを指定します。

#### service

service コマンドに対応します。

```yaml
# httpd サービスを起動して、自動起動をオンにする
service: name=httpd state=started enabled=yes
```
```yaml
# ip6tables サービスを停止して、自動起動をオフにする
service: name=ip6tables state=stopped enabled=no
```

`name` でサービス名を指定します。
`state` でどういう状態にするかを指定します。[started|stopped|restarted|reloaded] = [start|stop|restart|reload]
`enabled` で自動起動の ON/OFF を指定します(=chkconfig)。

**graceful がない！**

#### make install

以下、ソースファイルを download してきて解凍して make して make install する playbook です。
※ そういうモジュールがあるかと思ったけどなかった

```yaml
    - name: download hoge source file
      get_url: url=https://hostname/hoge_source.tar.gz dest=/usr/local/src/hoge_source.tar.gz

    - name: extract hoge source file
      unarchive: src=/usr/local/src/hoge_source.tar.gz dest=/usr/local/src/ creates=/usr/local/src/hoge_source

    - name: install hoge
      shell: ./configure && make && make install chdir=/usr/local/src/hoge_source creates=/usr/bin/hoge
```

冪等性がいい感じに効いてくれるので2回目以降はすべて skip されます。
※ ただし `creates=/usr/bin/hoge` だけは注意。インストールした結果、何かが出来上がるならそれを指定する

## role

依存関係とかいろいろ設定できる role ってのがあるらしいがまだ触ってません。