Ansibleの基本

[前回](https://qiita.com/ch7821/items/22735f2a9c8f7e0d4b2c)環境ができたところで、改めて公式ドキュメントからAnsibleに関するもろもろまとめ。

# 前提

+ 2018/02/28時点の情報つまりAnsible v2.4
+ 以降のリンクは最新版（latest）への参照であり、また正式なレビューも受けていないため、本記事と内容が異なる場合は参照先が真
+ deprecatedなものは記載しない

# コマンドリファレンス
まずはよく使う（であろう）コマンドから。
## [ansible](http://docs.ansible.com/ansible/latest/ansible.html)
ホスト（セット）に対し、シングルタスク（playbook）を実行するコマンド。
単発で「リモートでなにか実行（doing ‘remote things’）」する。

```bash:実行例（Windowsサーバ上にフォルダを作成する）
$ ansible windows -i /etc/ansible/hosts -m win_file -a "path=C:\\\test state=directory"
```

```bash:実行例（pingモジュールで疎通確認する）
$ ansible windows -m ping
```

+ 最初の引数（"windows"）はインベントリのグループ名（"all"を指定すると全部、公式マニュアル上は"[host-pattern](http://docs.ansible.com/ansible/latest/intro_patterns.html)"）
+ `-i`でインベントリファイル（デフォルトは"/etc/ansible/hosts"）を指定
+ `-m`で使う[モジュール](http://docs.ansible.com/ansible/latest/modules.html)（詳細後述）を指定
+ `-a`でモジュールへの引数（argument）を指定

### 補足：インベントリファイル
そのままだが、管理対象ホストのリストを記載したファイル。`[groupname]`でセクション化することで、ホストをグルーピング可。

```text:inventory
[groupname]
10.1.1.1
hostname.mydomain.com

[hogeWebServers]
www1.hogehoge.com
www2.hogehoge.com
www3.hogehoge.com
```

## [ansible-playbook](http://docs.ansible.com/ansible/latest/ansible-playbook.html)
こちらは"playbook"（後述）を作成して複数タスクを実行するコマンド。こいつこそがAnsibleの真骨頂（のはず）。

```bash:実行例
$ ansible-playbook playbook.yml
```

+ ほとんどの設定（実行サーバ等）はplaybook自体に書くので、実行時は割りとシンプル
+ `-C` or `--check`をオプション指定すれば実際の変更操作は行わず、起こりうる変更を予測する（try to predict some of the changes that may occur）。

## [ansible-vault](http://docs.ansible.com/ansible/latest/ansible-vault.html)
YAMLファイルを暗号化したり複合化したりするユーティリティ。vaultは地下金庫（[Harry Potterにも出てきましたね](https://www.google.co.jp/search?q=gringotts+bank+vault+713)）。Ansibleのデータファイル（/etc/ansible/group_vars,host_vars下）中にパスワードなどのセンシティブな情報を書き込んだら暗号化しておきましょう。

```bash:実行例
$ ansible-vault encrypt /etc/ansible/group_vars/windows.yml
```

+ 暗号化したら`ansible`や`ansible-playbook`実行時に`--ask-vault-pass`オプションをつけるなりしないと、当然動かない
+ 第一引数に`create`を指定するとエディタが開いて保存時に暗号化

## [ansible-galaxy](http://docs.ansible.com/ansible/latest/ansible-galaxy.html)
すごい名前。インターネット接続前提（または自分の共有リポジトリ）のようなので、今回は対象外。
とりあえずロール（リポジトリを管理する上での役割と推測）を管理するためのユーティリティらしい。

## [ansible-console](http://docs.ansible.com/ansible/latest/ansible-console.html)
AnsibleのREPL（Read-eval-print loop）なコンソールユーティリティ。

```実行例
$ ansible-console hogeWebServers
-m ping
-m ping
```

## [ansible-config](http://docs.ansible.com/ansible/latest/ansible-config.html)
Ansibleの設定周りを表示するコマンド。

```bash:実行例
$ ansible-config list
$ ansible-config dump
$ ansible-config view
```

+ ANSIBLE_CONFIG環境変数でcfgファイルを指定（/etc/ansible/ansible.cfg overrided by ~/.ansible.cfg）
+ または`-c` or `--config`オプションでオーバーライド

## [ansible-doc](http://docs.ansible.com/ansible/latest/ansible-doc.html)
Ansibleプラグインのドキュメントを表示するユーティリティ。

```bash:実行例
$ ansible-doc -m ping
```

## [ansible-inventory](http://docs.ansible.com/ansible/latest/ansible-inventory.html)
インベントリを表示するユーティリティ。

```bash:実行例
$ ansible-inventory --graph all
$ ansible-inventory --list all
```

## [ansible-pull](http://docs.ansible.com/ansible/latest/ansible-pull.html)
"playbook"をリポジトリからダウンロードしてきて実行するユーティリティ。

# モジュール
## [モジュールとは](http://docs.ansible.com/ansible/latest/modules.html)
簡単に言えば、AnsibleのコントロールマシンからOSネイティブコマンドを実行せずとも、モジュールと引数によってなんらかの処理をしてくれる。タスク・プラグイン、ライブラリ・プラグインとも呼ばれる。APIのようなもの。
もちろん、自作のモジュールを開発することもできる（腕があれば）。

## [概要](http://docs.ansible.com/ansible/latest/modules_intro.html)
リンク先のほぼ翻訳です。

```bash:モジュールを使った実行例
$ ansible webservers -m service -a "name=httpd state=started"
$ ansible webservers -m ping
```

`-m`でモジュールを指定し、`-a`でモジュールへの引数を渡す。ほとんどのモジュールは"key=value"型で引数を渡し、複数あるときはスペースで区切る。"ping"のように引数が不要なモジュールもある。
これを"playbook"にすると、

```yaml
- name: start the webservers
 service:
   name: httpd
   state: started
```

のようになる。

```bash:モジュールを使った実行例2
$ ansible webservers -m command -a "/sbin/reboot -t now"
```

これを"playbook"にすると、

```yaml
- name: reboot the webservers
 action: command /sbin/reboot -t now
```

これは[playbookの解説](http://docs.ansible.com/ansible/latest/playbooks_intro.html)によれば古い書き方で、次のように短く書くのがモダンなやり方。、

```yaml
- name: reboot the webservers
 command: /sbin/reboot -t now
```

自分でモジュールを書くようなことでもないかぎりあまり気にしなくてもいいけど、モジュールはJSONフォーマットのレスポンスを返す。
モジュールは冪等性を持つべき（should be idempotent）で、実行前に状態を確認して、既に最終形になっているなら、なにもしないほうがいい（should avoid making any changes）。
個別の詳細は実際に使うときになって調べることにしますが、どんなものがあるかくらいは目を通しておいてよさそうです。

+ [全モジュールリスト](http://docs.ansible.com/ansible/latest/modules_by_category.html)

あとはサードパーティ製のモジュールもあったりなかったりだとか。

# [YAML Syntax](http://docs.ansible.com/ansible/latest/YAMLSyntax.html)
既にちょこちょこと出てきている"playbook"はYAML形式で記載します。HTMLに対するmarkdownのようなもので、XMLやJSONに比べて人間に優しい形式だと主張していますが、実際のところどうなんでしょうね。

## リストとディクショナリ
AnsibleではほぼすべてのYAMLファイルはリストで始まります。リスト内の各項目はkey/valueのペアで、一般的にハッシュとかディクショナリと呼ばれるものです。
YAMLは"---"で開始と終端を表すことができます。

リスト内のすべての項目は同じインデントレベルで、行頭を"- "(a dash + a space)にします。

```yaml
---
# A list
hogeList:
   - listItem1
   - listItem2
   - listItem3
---
```

ディクショナリは"key: value"で表します（コロンの後ろのスペース１個は必須）。

```yaml
---
# A dictionary
hogeDict:
   name: hogename
   attr: hogeattr
   value: hogevalue
---
```

リストもディクショナリも短縮形で書けます。

```yaml
---
hogeList: ['listItem1', 'listItem2', 'listItem3']
hogeDict: {name: hogename, attr: hogeattr, value: hogevalue}
---
```

## 複数行にまたがる場合の指示子
複数行をみたまま表示させるか（HTMLでいうところのpre的な）、空行や改行を無視するかを指定する場合、それぞれ"|"と">"を使用します。

```yaml
# みたままバージョン
asYouCanSee: |
   hogehoge
   the second line
   the third line

# 改行等無視バージョン
ignoreLineBreaks: >
   hogehoge
   hogehoge
   hogehoge
```

## 注意点
コロンが値にある場合は注意が必要です。

```yaml
test: this is test: which is intended to be meaningless

drive: c:
```

このままだとシンタックスエラーになりますが、それぞれ'"'（ダブルクォート）で括ることで回避できます。

```yaml
test: "this is test: which is intended to be meaningless"

drive: "c:"
```

ただし、クォートが不要なケースも存在します（なぜOKなのか言及されていないので詳細不明）。

```yaml
windows_path: c:\windows
```

さらに、Ansibleでは"{{ var }}"という形式で変数を保存する一方、YAMLパーサはそれをディクショナリのワンライナー型と解釈するため、この場合もダブルクォートする必要があります。
その他、クォートするべき事項をまとめます。

```yaml
hoge: "{{ var }}"
hoge2: "{{ basedir }}/hoge/geho"   # 固定文字列を含む場合は全体をクォート
hoge3: "{{ basedir }}\\hoge\\geho" # バックスラッシュも特殊文字
hoge4: "text which includes white spaces all must be quoted" # スペースを含む文字列も全体をクォート
hoge5: "yes"                       # booleanではなく文字列としての"yes"
hoge6: "1.0"                       # 浮動少数点数ではなく文字列としての"1.0"
```
# [Playbooks](http://docs.ansible.com/ansible/latest/playbooks.html)
最後にこれで締めます。
「モジュールが道具だとすれば、"playbooks"はマニュアル（操作手順）であり、インベントリは素材だ」とのことです（DIY好きな人にはわかりやすい例えなのかしら？）。
また、「[とりあえず読んでおけ、これらがベストプラクティスだ！](https://github.com/ansible/ansible-examples)」とのことです。

まずは、ドキュメント中の例をそのまま記載します。

```yaml
---
- hosts: webservers
 vars:
   http_port: 80
   max_clients: 200
 remote_user: root
 tasks:
 - name: ensure apache is at the latest version
   yum: name=httpd state=latest
 - name: write the apache config file
   template: src=/srv/httpd.j2 dest=/etc/httpd.conf
   notify:
   - restart apache
 - name: ensure apache is running (and enable it at boot)
   service: name=httpd state=started enabled=yes
 handlers:
   - name: restart apache
     service: name=httpd state=restarted
---
```

"key=value"となっている部分は、ディクショナリ形式でも書けます。

```yaml
---
- hosts: webservers
 vars:
   http_port: 80
   max_clients: 200
 remote_user: root
 tasks:
 - name: ensure apache is at the latest version
   yum:
       name: httpd
       state: latest
 - name: write the apache config file
   template:
       src: /srv/httpd.j2
       dest: /etc/httpd.conf
   notify:
   - restart apache
 - name: ensure apache is running (and enable it at boot)
   service:
       name: httpd
       state: started
       enabled: yes
 handlers:
   - name: restart apache
     service:
         name: httpd
         state:restarted
---
```

さて、どちらが見やすい（あるいはメンテしやすい、Excel等から自動生成しやすい）ですかね。

## hostsとremote_user
### hosts
hosts行は1つ以上のグループやホストパターンのリストです。複数指定する場合はコロン":"で区切ります。
詳細は[pattern](http://docs.ansible.com/ansible/latest/intro_patterns.html)の項に記載のとおり。

### remote_user
説明の必要がないくらい文字通り、管理対象サーバで"play"を実行するユーザ名。
タスク単位で指定可能。

```yaml
---
hosts: webservers
   remote_user: root
   tasks:
       - name: test
       ping:
       remote_user: ec2-user
---
```

### become
sudoとかで権限昇格して実行する場合に指定。

```yaml
---
hosts: webservers
   remote_user: ec2-user
   tasks:
       - name: test
       ping:
       become: root
       become_method: sudo
---
```

### 順序制御
複数のホストが対象となっている場合、"order"で実行順序を制御可能。

<dl>
 <dt>inventory</dt>
 <dd>これがデフォルト。インベントリファイルに記載のとおりの順序で実行する。</dd>
 <dt>reverse_inventory</dt>
 <dd>インベントリファイルの逆順で実行する。</dd>
 <dt>sorted</dt>
 <dd>実行ホスト名のアルファベット降順で実行する。</dd>
 <dt>reverse_sorted</dt>
 <dd>実行ホスト名のアルファベット昇順で実行する。</dd>
 <dt>shuffle</dt>
 <dd>ランダムな順序で実行する。</dd>
</dl>

### タスクリスト
各"play"はタスクのリストを含められ、上から順に逐次実行します。エラーとなったホストは、以後スキップされます。失敗したら修正してやり直せとのことです（だからこそモジュールの冪等性が重要で、"playbook"内で使用するモジュールに冪等性があれば、"playbook"全体も冪等性があるでしょうと）。
すべてのタスクには名前（"name"）をつけましょう。結果のログに読みやすい形で出力されます。指定がない場合は、"action"とそっけなく表示されます。

### [変数](http://docs.ansible.com/ansible/latest/playbooks_variables.html)
変数は"playbook"のvarsセクション、またはインベントリの"[Host Variables](http://docs.ansible.com/ansible/latest/intro_inventory.html#host-variables)"や"[Group Variables](http://docs.ansible.com/ansible/latest/intro_inventory.html#group-variables)"で宣言し、アクション行から"{{varName}}"で参照します。
変数名に使える文字種は、文字（a-zA-Z）とアンダースコア("_")と数字で、必ず文字から始まる必要があります。
YAMLではディクショナリも使えるので、"[]"や"."記法を使った参照も可能です。

```yaml
---
- hosts: webservers
 vars:
   hoge1: hogevalue1
   hoge2: hogevalue2
 tasks:
   - name: test1
     command: /bin/echo {{ hoge1 }}
   - name: test2
     command: /bin/echo {{ hoge2 }}
   - name: test3
     command: /bin/echo vars.hoge1
   - name: test4
     command: /bin/echo vars['hoge2']

---
```

### ハンドラ
例えば設定ファイルを変更するタスクを定義し、実際に変更があった場合にのみ特定のサービスを再起動させたいような場合、ハンドラを使うことで実現できるというお話。

```yaml
---
- hosts: webservers
 tasks:
   - name: replace httpd.conf
     template: 
       src: template.conf
       dest: /etc/apache/conf/httpd.conf
     notify:
       - restart memcached
       - restart apache
 handlers:
     - name: restart memcached
       service: 
         name: memcached
         state: restarted
     - name: restart apache
       service:
         name: apache
         state: restarted
---
```

"nofify"に指定したリストがハンドラであり、"handlers"セクションに実際のアクションを記載します（普通の"tasks"とほぼ同じ）。"handlers"セクションのリストで、"name"にハンドラで定義した文字列を設定します（グローバルにユニークである必要）。
"handlers"アクションの実行順序は、"handlers"に定義した順番となり、"notify"した順番ではないことに注意が必要。
上記とは別の方法として、"handers"のタスクに"listen"を加えることでも実現可能。

```yaml
---
 tasks:
  - name: replace httpd.conf
    template: src=template.conf dest=/etc/apache/conf/httpd.conf
    nofity: "restart web services"

 handlers:
     - name: restart memecached
       service: name=memcached state=restarted
       listen: "restart web services"
     - name: restart apache
       service: name=apache state=restarted
       listen: "restart web services"
---
```

また、全てのハンドラを実行したい場合、次のように"meta"行を入れることで実現可能です。

```yaml
tasks:
 - shell: /bin/echo executing all handler tasks
 - meta: flush_handlers
 - shell: /bin/echo done
```

# YAMLのシンタックスチェック
`ansible-playbook`コマンドに`--syntax-check`オプションを付与することで確認可能です。また、実行対象ホストを確認するには`--list-hosts"オプションを付与します。

```bash
$ ansible-playbook playbook.yml --syntax-check
$ ansible-playbook playbook.yml --list-hosts
```

<img width="1119" alt="スクリーンショット 2018-02-28 19.33.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/232587/653d2762-ed06-3e69-141f-997fe04604dc.png">
ちなみにですが、日本語のフォルダ作成もいけました。

まだまだ奥というか底が深そうなAnsiblですが、必要に迫られたらもう少しお勉強したいところです。