Ansibleを触り始めて半年でわかった良いところ、使いにくいところ

# Ansibleについて、実務を通じてわかったこと
Ansibleを使うようになって半年が経ちました。

半年前までは「Ansible。。聞いたことあるような」レベルだったところから実務を通じてAnsibleのどういうところが良いのか、shell等で実行することとの違いなどを書きます。

## Ansibleの良いところ
詳しい話は他に情報いっぱいあるので、主に私の体感したイメージで書きます。

* ssh接続出来る環境でさえあればサーバ側に特別な準備がいらない
* 同じ処理を何度実行しても同じ結果になるように設計されている
* 処理を記載するのが「YAML」というデータ構造型のファイルに記載するのでbash等よりも見やすい。
* タスクひとつひとつに`name`というタイトルを付けるので処理実行中も内容を追いやすい。

一つ一つ書いていきます。

#### ssh接続出来る環境でさえあればサーバ側に特別な準備がいらない
対象サーバに特別インストールが必要なものは無い（pythonが必要だが大体入っている）ので、サーバに余計なものをインストールしたく無いって時でも使えます。サーバに何かインストールが必要であれば、サーバ台数が多いと面倒ですし、大きなプロジェクトでは影響範囲などの調査を行うコストが発生しますがそういう懸念が必要ないのは非常に大きなメリットです。

#### 同じ処理を何度実行しても同じ結果になる
「普通そうじゃん？」って思われるかもしれませんが、もう少し踏み込んで言うと、`行おうとしているタスクが既に実行されていると判断されれば、その処理を行いません。`

例えばファイル末尾に文字を追加する「lineinfile」モジュール。
ファイルにaaaとbbbの文字を行追加するタスクを複数回実行してみます。

```yaml
[playbook.yml]
- hosts: vagrants
  tasks:
    - name: 末尾に文字を複数行追加
      lineinfile:
        dest: /tmp/test.txt
        line: "{{ item }}"
      with_items:
        - "aaa"
        - "bbb"
```

一回目

```console
$ ansible-playbook -i inventory playbook.yml

PLAY [vagrants] ***************************************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************************
ok: [192.168.11.10]

TASK [末尾に文字を複数行追加] ************************************************************************************************************************************************************************
changed: [192.168.11.10] => (item=aaa)
changed: [192.168.11.10] => (item=bbb)

PLAY RECAP ********************************************************************************************************************************************************************************
192.168.11.10              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

実行タスクに対して`changed`がステータスとして出力されています。

再度実行。2回目

```console
$ ansible-playbook -i inventory playbook.yml

PLAY [vagrants] ***************************************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************************
ok: [192.168.11.10]

TASK [末尾に文字を複数行追加] ************************************************************************************************************************************************************************
ok: [192.168.11.10] => (item=aaa)
ok: [192.168.11.10] => (item=bbb)

PLAY RECAP ********************************************************************************************************************************************************************************
192.168.11.10              : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

次は`chaged`ではなく`ok`になっています。
okは、`もう既に実行された状態になってたんで何もやって無いです`状態です。

実際にファイルの中身を見てみると、、

```console
$ cat /tmp/test.txt
aaa
bbb
```

末尾に既に指定した文字があった為2回目が実行されていないのがわかります。
ここがbashなどと違う大きなポイントだと個人的には思ってます。bashで追記の`>>`って記載してれば実行する度にファイルに追記されますからね。

#### 処理を記載するのが「YAML」というデータ構造型のファイルに記載するのでbash等よりも見やすい。
これも大きなメリット。bashで書くと空白とか処理の記載に人のクセが色濃く出て、ひどいものだと、インデントがぐっちゃぐちゃでもう見たくないレベルのシェルがそこに。。

AnsibleはYAMLというデータ構造型、つまりインデントなどの空白にも意味があるのでぐちゃぐちゃっとした記述が出来ません。（エラーになる）

下記のキャプチャですが、先ほどのPlaybookを例に、`-name`と`dest`の位置を変えてみました。
<img width="281" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321507/1814a0b7-f4c2-d701-aa90-db93a36f8732.png">
VScodeで記載していますが赤波線で書き方についてエラーが出力されています。
これにより妙な段落での記載が出来ず、プレイブックはスッキリ見えやすいです。

#### タスクひとつひとつに`name`というタイトルを付けるので処理実行中も内容を追いやすい。

bashを実行してる時は今処理がどこまで行ってるか追いづらいですが、Ansibleはタスクのnameと実行結果がコンソールに出力されるのでどこまで処理されているかすぐわかります。

```yaml

- hosts: vagrants
  tasks:
    - name: 末尾に文字を複数行追加
      lineinfile:
        dest: /tmp/test.txt
        line: "{{ item }}"
      with_items:
        - "aaa"
        - "bbb"
```

`- name`で記載されたタスク名が、下記のように`TASK`部分に記載されます。

```console
$ ansible-playbook -i inventory playbook.yml

PLAY [vagrants] ***************************************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************************
ok: [192.168.11.10]

TASK [末尾に文字を複数行追加] ************************************************************************************************************************************************************************
ok: [192.168.11.10] => (item=aaa)
ok: [192.168.11.10] => (item=bbb)
```

実行中もさることながら、プレイブックを編集するときもnameにタスク内容を簡潔に書いていることが多いので初見の人でも何をしようとしてるかざっくりとした流れを比較的容易に把握出来ます。（書くのは別）

## Ansibleの（個人的に）使いにくいところ or 面倒なところ
冪等性という観点では非常に良く出来てるAnsibleですが、使いにくいところ、面倒なところもあります。個人的には下記です。

* エラー文からエラー内容がわかりにくい時がある。
* 状況によっては実行処理に問題があってもエラーにならないことがある
* YAMLによる記載ルールがbash等と全然違う為慣れるまで厄介
* ドキュメントが英語

これも一つ一つ書いていきます。

#### エラー文からエラー内容がわかりにくい時がある。
下記のようなプレイブックを用意しました。`with_items`のインデントを間違った箇所にしています。VScodeでエラーは出ていません。

```yaml
- hosts: vagrants
  tasks:
    - name: 末尾に文字を複数行追加
      lineinfile:
        dest: /tmp/test.txt
        line: "{{ item }}"
      with_items:
        - "aaa"
        - "bbb"
```

実行結果は下記です。

```console

$ ansible-playbook -i inventory playbook.yml

PLAY [vagrants] ***************************************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************************
fatal: [192.168.11.10]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.11.10 port 22: Connection reset by peer", "unreachable": true}

PLAY RECAP ********************************************************************************************************************************************************************************
192.168.11.10              : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0
```

サーバへの接続がどうたらってエラーが出ています。
`with_items`はループ処理で使用するもので、接続云々のエラーでここのインデントがおかしいという判断は中々難しいと思います。インデントがおかしい！って端的に出てくれれば良いんですけど、、

#### 状況によっては実行処理に問題があってもエラーにならないことがある
どういうこと？ってなると思いますが、実体験を交えて紹介します。

私が経験したのは`get_url`というモジュールでファイルをダウンロードするタスクを実行した時です。
プレイブック上ではエラーが出なかったのですが、そのあとダウンロードしたrpmファイルをインストールしようとしたところエラーが発生しました。

原因を調べたところ作業環境の制限か、1.1GBを超えるファイルを落とそうとすると落としきる途中で処理が終了してしまうみたいでした。ファイルのダウンロードは完了しきっていないのですが、Ansible上ではエラーとは判断されず処理が実行されていました。
こういった事例はAnsibleが提供しているモジュールだと発生しづらいとは思いますが、`command`モジュールなどでLinuxコマンドを実行する時は注意が必要です。

コマンドの実行結果がエラーでも、コマンドが実行出来ればAnsibleでは問題なしで処理される可能性があります。

`11/06追記`
「コマンドの実行結果がエラーでも、コマンドが実行出来ればAnsibleでは問題なしで処理される可能性があります。」の部分についてどういうことか質問があったので具体例を記載します。

例えばこういうタスクを記載するとします。

```console
  - name: エラー実験
    shell: "cat /tmp/aaa.txt"
```
実行結果は下記になります。そんなファイル無いよ！ってことでエラーが出ます。

```console
TASK [エラー実験] ******************************************************************************************************************************************************************************
fatal: [192.168.11.10]: FAILED! => {"changed": true, "cmd": "cat /tmp/aaa.txt", "delta": "0:00:00.002387", "end": "2019-11-06 15:24:05.978394", "msg": "non-zero return code", "rc": 1, "start": "2019-11-06 15:24:05.976007", "stderr": "cat: /tmp/aaa.txt: そのようなファイルやディレクトリはありません", "stderr_lines": ["cat: /tmp/aaa.txt: そのようなファイルやディレクトリはありません"], "stdout": "", "stdout_lines": []}

```

これをbashにして書いて、playbookはこのbashを実行するタスクに変更します。

```console
$ cat /tmp/test.sh
#!/bin/bash

cat /tmp/aaa.txt


[playbook]
  - name: エラー実験
    shell: "sh /tmp/test.sh"

```

実行します。まだエラーが出ます。

```console
TASK [エラー実験] ******************************************************************************************************************************************************************************
fatal: [192.168.11.10]: FAILED! => {"changed": true, "cmd": "sh /tmp/test.sh", "delta": "0:00:00.004081", "end": "2019-11-06 15:26:18.027636", "msg": "non-zero return code", "rc": 2, "start": "2019-11-06 15:26:18.023555", "stderr": "ls: /tmp/aaa.txt にアクセスできません: そのようなファイルやディレクトリはありません", "stderr_lines": ["ls: /tmp/aaa.txt にアクセスできません: そのようなファイルやディレクトリはありません"], "stdout": "", "stdout_lines": []}
```
いつまでわかりきったことやってるんだ！って言われそうですが、次がポイントです。
bashの中をこうします。

```console
$ cat /tmp/test.sh
#!/bin/bash

cat /tmp/aaa.txt
date
```

存在しないファイルの中身参照のコマンドの次にdateコマンドを実行するようにします。すると。。

```console
TASK [エラー実験] ******************************************************************************************************************************************************************************
changed: [192.168.11.10]
```

この通りエラーが出なくなります。どうやらbashなどで複数コマンド実行すると最後のコマンドで結果を識別するようです。
つまり、`bashの実行途中にエラーがあっても最後のコマンドがOKならエラーが出力されない`ということです。パッと見は問題なく行ってるように見えるのでbashなどを噛ませる場合は注意が必要です。

#### YAMLによる記載ルールがbash等と全然違う為慣れるまで厄介
見やすくなるインデントのルールですが、慣れまでは厄介です。どこのインデントが同じにしてどこが中に入れ込むように記載するか、などが少し厄介です。
bashならコマンドを羅列すれば済むところを一つ一つタスクにするというのも始めはもどかしかったです。モジュールを調べながら「コマンドならすぐなのに！」とよく思っていました。

#### ドキュメントが英語
モジュールの使い方を調べようと公式をみると全部英語なんですよね。。
Linuxコマンドみたいに個人のサイトなどで詳細に解説したものもまだまだ少ないので英語を頑張って読み解く必要が少しあります。公式ページにはモジュールのexampleがあるのでそこから大体読み取ることは出来ると思います。

## 最後に
自分がAnsibleを全く知らない状態から現場で使うことになったので、その経験から知らない人にAnsibleがどういうもので、どういうところが使いにくい、という触り部分を伝えたいと思い記事にしました。

実際使ってみると冪等性という部分がすごく有り難く、間違いのあった箇所を修正して同じプレイブックを再度流せるというのはとても楽です。

「便利そう！」と思ったら是非挑戦してみてください。
管理部分まで考慮すると覚えることが多いですが、とりあえず使ってみるということであれば、Ansibleをインストールしてファイルを二つ用意すれば実行可能なので試してみてください！