Ansible Tower(AWX)のジョブタスク結果をKibanaで一覧表示した時のメモ

Ansible Tower(AWX)で実行したジョブの結果をELKで可視化しようとして、まずはジョブの実行結果をテーブルで表示させてみた時のメモです。
実行結果は動的に変わる部分があるので[painless](https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting-painless.html)を使ってコードを書いて表示してみます。
Logstash経由でElasticsearchへAnsible Towerのログを取り込む手順は[Ansible Towerドキュメント](https://docs.ansible.com/ansible-tower/latest/html/administration/logging.html#elastic-stack-formerly-elk-stack)を参照してください。

# 1. 目的

Ansible Towerで実行したジョブ結果(okやfailなど)をKibanaでPlaybook毎に可視化します。
イメージは以下の様な感じです。

|         時間        |  Playbook | ok | fail |
|---------------------|-----------|----|------|
| `yyyyMMdd HH:mm:ss` | main.yml  |  1 |    0 |
| `yyyyMMdd HH:mm:ss` | main2.yml |  0 |    1 |

# 2. 環境

|      項目     | バージョン |
|---------------|------------|
| OS            | RHEL7.5    |
| ELK           | 6.3.2-1    |
| Ansible Tower | 3.2.5      |

# 3. ジョブ実行結果が入るフィールド

Ansible Towerでジョブを実行するとジョブのタスクを一つ実行する度に一つのログを出力します。
そのため、Kibanaでログを確認すると一つのジョブで数個記録されています。
その中の一つにジョブ結果を格納されているフィールドがあります。
それが `event_data` です。
以下は、一つの例です。

```json
(snip)
    "event_data": {
      "ok": {
        "localhost": 1,
        "192.168.0.236": 1
      },
      "playbook_uuid": "c867d20e-841a-44e7-b934-683dc0bad1d3",
      "changed": {
        "localhost": 1,
        "192.168.0.236": 1
      },
      "playbook": "main.yml",
      "skipped": {},
      "processed": {
        "localhost": 1,
        "192.168.0.236": 1
      },
      "dark": {},
      "failures": {},
      "pid": 3
    },
(snip)
```

`event_data` はジョブに関連する各sourceに記録されていますが中身の構造が違ってきます。
結果は `event_data` 内に `ok` `playbook_uuid` `changed` `playbook` `skipped` `processed` `dark` `failures` `pid` のキーがあります。
実行したホストやPlaybook数によって `ok` や `failures` などの中のキーや値が変わってきます。
そのため、まずは簡単に合計数を可視化するために `painless` を使って可視化してみます。

# 4. Kibanaの設定

## 4-1. Scripted fieldsの作成

ここでは、以下4つのScripted fieldsをpainlessで作成します。

* task_skipped
* task_ok
* task_changed
* task_failures

`Management` をクリックして `Index Patterns` -> `Scripted fields` へ移動し `Add scripted field` をクリックして追加します。

![スクリーンショット 2018-08-10 21.27.45.png](https://qiita-image-store.s3.amazonaws.com/0/180118/6268e55d-6cf4-32df-8062-5562c9328bf6.png)

ここでの対象indexは `ansible_tower*` です。

### 4-1-1. task_skipped

![スクリーンショット 2018-08-10 21.12.08.png](https://qiita-image-store.s3.amazonaws.com/0/180118/a254fe8d-4fe7-0171-4f63-c8eb6c6a7e38.png)

```javascript
int r = 0;
if(params._source.containsKey('event_data') && params._source.event_data.containsKey('skipped')) {
  for (def key : params._source.event_data.skipped.keySet()) {
    r = r + params._source.event_data.skipped[key];
  }
}

return r;
```

### 4-1-2. task_ok

![スクリーンショット 2018-08-10 21.13.25.png](https://qiita-image-store.s3.amazonaws.com/0/180118/c6c42202-69c0-9067-1b86-f4bcb572c656.png)

```javascript
int r = 0;
if(params._source.containsKey('event_data') && params._source.event_data.containsKey('ok')) {
  for (def key : params._source.event_data.ok.keySet()) {
    r = r + params._source.event_data.ok[key];
  }
}

return r;
```

### 4-1-3. task_changed

![スクリーンショット 2018-08-10 21.14.51.png](https://qiita-image-store.s3.amazonaws.com/0/180118/9a8cbd85-0d41-22bc-b44d-b3129d2e1be6.png)

```javascript
int r = 0;
if(params._source.containsKey('event_data') && params._source.event_data.containsKey('changed')) {
  for (def key : params._source.event_data.changed.keySet()) {
    r = r + params._source.event_data.changed[key];
  }
}

return r;
```

### 4-1-4. task_failures

![スクリーンショット 2018-08-10 21.15.31.png](https://qiita-image-store.s3.amazonaws.com/0/180118/3e205e98-0fbb-e39d-563a-a96536296d1d.png)

```javascript
int r = 0;
if(params._source.containsKey('event_data') && params._source.event_data.containsKey('failures')) {
  for (def key : params._source.event_data.failures.keySet()) {
    r = r + params._source.event_data.failures[key];
  }
}

return r;
```

## 4-2. 簡単に説明

キーのチェック(containsKey)に `doc` を使わずに `params._source` を使っている理由は可視化したい対象のデータが存在したりしなかったりするためです。
そのため `doc` ではうまく動作しなかったので `params._source` を直接使っています。

# 5. 可視化結果

こんな感じになります。

![スクリーンショット 2018-08-10 16.48.48.png](https://qiita-image-store.s3.amazonaws.com/0/180118/c4150193-3a3c-b3dc-3941-a53183efa0e7.png)

# 6. 課題

一応、このデータを元にグラフ化などもできますが `params._source` を使っているソースなのでfilterが使えません。
そのため検索に難ありです。
Elasticsearchのindexに入れる前にデータを加工(フィールドを増やすなど)する必要があると思いました。
この事をTwitterで発言したところ `@johtani` さんからIngestのアドバイスを頂いたので今後の検証で試していきたいと思います。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">scripted fieldで表示時に変換するのではなく、登録時にIngestの機能で変換した方がいい気がします。<a href="https://t.co/9FDWfGgeyB">https://t.co/9FDWfGgeyB</a></p>&mdash; Jun Ohtani (@johtani) <a href="https://twitter.com/johtani/status/1027824645775405056?ref_src=twsrc%5Etfw">2018年8月10日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 7. 参考

[https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-api-reference.html](https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-api-reference.html)
[https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting-fields.html](https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-scripting-fields.html)