【Ansible備忘録】Ansibleでサービス状態を取得する（command, serviceモジュール）

# 本記事のターゲット
* Ansible初学者
* 実務でAnsibleを使用しているエンジニア
* サービス状態の確認を自動化したい方
* サービス状態の確認を一括で行いたい方

# 前書き
設定変更作業などを実施した後、対象のサービスが問題なく起動しているかを確認することが多いと思います。
その際、対象のサーバに接続し、`systemctl status 〇〇(サービス名)`というコマンドでサービス状態を確認しますが、下記のような場合、その確認作業が面倒に感じることもあるのではないでしょうか。
> * **対象サーバが複数ある場合**
> * **対象サービスが複数ある場合**

今回は上記の課題を解決するようなAnsibleの使い方を紹介します。


# 記載例
2つの例を紹介します。
ご自身の環境に合わせて選択していただければと思います。

## 1. commandモジュールを用いた方法
``` 
- name: サービス状態確認（commandモジュール）
  command:
    cmd: systemctl is-active "{{ item }}"
  loop:
    - httpd.service
    - tomcat.service
  register: check_status
  failed_when: "'active' not in check_status.stdout_lines"
  changed_when: false
  ignore_errors: true
```

▼commandモジュールに関する参考資料

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html

commandモジュールを用いた方法は、簡単に言えば「**`systemctl status`コマンドを実行しているだけ**」です。
以下に各行の記載内容について説明します。

#### ①cmd: systemctl is-active "{{ item }}"
```
command:
  cmd: systemctl is-active "{{ item }}"
  loop:
    - httpd.service
    - tomcat.service
```
LinuxOSでは、**`systemctl is-active 〇〇(サービス名)`** というコマンドを実行することでそのサービスの**状態のみ**を標準出力として返します。
（稼働中であれば`active`。停止中であれば`inactive`。）

`systemctl status 〇〇(サービス名)`でも同様の情報を取得できますが、エラー（サービスが起動していない状態）の場合、エラーメッセージが長文で表示されてしまい視認性を下げてしまいます。そのため、今回はこの**状態のみ**という出力結果を使用します。

<br>

また、サービス名(○○の部分)が`"{{ item }}"`となっていますが、ここには`loop:`配下に記載したサービスが指定されます。
「loop」と言っているくらいなので、一般的に使われるループ処理と同じように繰り返し実行されます。1行目（httpd.service）を実行後、2行目（tomcat.service）を実行します。**2つ以上ある場合は最終行まで繰り返します。**

今回は例として「httpd.service」と「tomcat.service」の2つを指定していますが、対象サービスを増やしたい場合は同様の書き方で追記していけば実装可能です。

#### ②register: check_status
7行目は、上記のcommandモジュールの実行結果を`check_status`という変数に格納しています。変数名は任意です。
この変数は次の行で使用します。

#### ③failed_when: "'active' not in check_status.stdout_lines"
8行目は、上記のcommandモジュールの実行結果を **`failed`** とする条件を記載しています。

中身はシンプルで、
**`systemctl is-active 〇〇(サービス名)`の返り値が「activeではない」場合に`failed`とする。**
という感じです。

activeかどうかの判断には、7行目で指定した変数を使用します。
この変数には、「active」「inactive」などの文字列が格納されるため、その文字列を比較して`ok`か`failed`かの判断をしている形となります。


#### ④changed_when: false
9行目は、上記のcommandモジュールの実行結果を`changed`にしないように設定しています。

commandモジュールは実行結果を常に`changed`にするという特性を持っています。
今回であれば、**対象のサービスが起動済みであるにも関わらず`changed`にしてしまいます。** 問題なく起動しているのであれば`ok`と出力してほしいですよね。

この課題を解決するために`changed_when: false`を指定します。

▼changed_whenに関する参考資料

https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html#defining-changed

▼ **`changed_when: false`を付けた場合**
![成功.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3339646/31b1f066-0852-b7c4-9d59-be9785d90979.png)

▼ **`changed_when: false`を付けてない場合**
![changed_whenがない場合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3339646/140a3e83-c72e-5901-811f-8a7d53317af3.png)
⇒このようにサービスが起動状態でも`changed`とされてしまう。


#### ⑤ignore_errors: true
10行目は、タスクが失敗した（エラーコードを吐いた）場合でも、Ansibleの実行を続けるために設定しています。
ignore_errors＝「**エラーを無視する**」というわけですね。

Ansibleはタスクが失敗（エラー）した場合、そのタスク内の処理を完遂した後、後続のタスクを中止します。
今回であれば、1つ目の「httpd.service」が起動していないことでエラーとなった場合、2つ目の「tomcat.service」の状態確認までは行われますが、それ以降のタスクは中止します。

状態確認のタスクの後に何かしらのタスクを記載する場合が多いと思います。必要に応じて`ignore_errors: true`を設定しておくことをおすすめします。

▼ **`ignore_errors: true`を付けている場合**
![ignore_errorsあり.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3339646/cfc5cf3b-3491-f372-9ea8-b68867b5cf5d.png)
⇒`failed`となっているが、後続の「後続確認用タスク」が実行されている。

▼ **`ignore_errors: true`を付けてない場合**
![ignore_errorsなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3339646/2f84bd84-8d59-967b-f33d-23a514b8478b.png)
⇒後続タスクを実行する前に中断されている。


▼ignore_errorsに関する参考資料

https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_error_handling.html#ignoring-failed-commands


<br>
<br>

以上がcommandモジュールを用いた方法となります。

簡単に言えば、返り値の文字列を利用しているだけです。そこにfailed_whenとchanged_whenを組み合わせて出力結果を変化させているという形です。


## 2. serviceモジュールを用いた方法

```
- name: サービス状態確認（serviceモジュール）
  service:
    name: "{{ item }}"
    state: started
  loop: 
    - httpd.service
    - tomcat.service
  check_mode: true
```

▼serviceモジュールに関する参考資料

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_module.html

serviceモジュールを用いた方法は、通常の使い方を少しアレンジした形です。

serviceモジュールは、`name: 〇〇(サービス名)`と`state: △△(そのサービスに対して行いたい起動処理)`を指定するのが基本的な使い方です。
上記の例であれば、**「httpd.service」と「tomcat.service」を起動させる(started)** という処理を行います。

ただ、今回はあくまでも「**サービスの状態を確認すること**」が目的なので、「起動する」という処理は不要ですよね。
そこで使用するのが **`check_mode: true`** というオプションです。

`check_mode`は、簡単に言えば「実際に実行したらAnsibleがどんな風に動くかを教えてくれる機能」です。
あくまでも動き方を**検証**しているだけで、**実際の変更は行いません**。

▼check_modeに関する参考資料

https://docs.ansible.com/ansible/2.8/user_guide/playbooks_checkmode.html

#### serviceモジュールの性質を活用する
serviceモジュールは、上述の記載例のように`state: started`と指定した場合、対象のサービスが起動している場合はそのまま、停止状態の場合は起動させます。
この場合のAnsibleの実行結果は、既に起動状態の場合は`ok`、停止→起動の処理を行った場合は`changed`となります。

この実行結果より、**`changed`と出力されたサービスは元々停止状態だったこと**がわかります。
これを利用し、「**`ok`なら起動済み、`changed`なら停止状態**」という確認観点を設けた上で、オプションとして **`check_mode: true`** を付与すれば、**サービスの起動を行わずにサービスの状態を判別する**ことができます。

▼実行結果例
![systemd(service)モジュール_stop時.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3339646/68630965-e076-11ed-9e0f-22226bc25b61.png)
⇒`ok`なら起動中。`changed`なら停止状態。
　`check_mode: true`が付いているため、実際には起動処理は行われていない。

### serviceモジュールを用いた方法の問題点
serviceモジュールを用いた方法の問題点は、**停止状態が`changed`と表示されること**です。

commandモジュールでは、起動状態は`ok`、停止状態は`failed`と出力するようにできましたが、serviceモジュールでは停止状態は`changed`と出力されてしまいます。
「`changed`の場合は停止状態」という確認観点を設ければ特に問題はありませんが、停止状態だということが一目でわかるのは赤字で`failed`と記載されている方かなと思います。



# 終わりに
今回は、Ansibleを用いてサービスの状態を確認する方法を2つ紹介しました。見た目的に使いやすいのは1つ目のcommandモジュールを用いた方法かなと個人的には思います。ただ、やはりcommandやshellモジュールを使うことには少し躊躇いがありますね。もっと使いやすい方法があれば良いのですが。。
~~というかserviceモジュールで状態確認だけを行える方法を公式から追加実装してくれれば良いだけの話ですが。。~~