EC2に付けたタグで同じ値のものがいくつあるのか集計する

##EC2のタグを集計したい理由
タグを付ける理由も色々あるように、集計したい理由も色々考えられます。

 - 本番系か開発系か（Envタグ等）を付けている場合、本番系の割合
 - メンテナンス時刻（MaintHHタグ等）を付けている場合、暇なEC2が多い時間帯
 - コスト配分タグの付与状況
 - タグによってジョブ実行させる場合の同時実行数の調整
 - きれいにタグが付いていることを眺めたい！

等

手動ならマネージメントコンソールで、EC2を選択して、左ペインで「タグ」をクリックすればOKですが、ここではCLI／API＋スクリプトで考えます。


## EC2の全てのタグの取得

AWS-CLIで、特殊なコマンドは必要ありません。

```bash
aws ec2 describe-instances --output json
```

を実行するだけでよいです。
いろんな属性とともにEC2のタグも全て取得できます。
--query を付けて絞ることもできます。

## describe-instancesの結果に含まれるタグの場所

取得されたJSONの中で、タグ情報が格納されている場所を示します。(タグ以外の属性は省略して書いています。)

```json
{
    "Reservations": [
        {
            "Instances": [
                {
                    "Tags": [
                        { "Key" : "Name", "Value" : "server-1 (※Reservation[0]、Instances[0], Tags[0])" },
                        { "Key" : "Env", "Value" : "Prod (※Reservation[0]、Instances[0]、Tags[1])" }
                    ]
                },
                {
                    "Tags": [
                        { "Key" : "Name", "Value" : "server-1  (※Reservation[0]、Instances[1]、Tags[0])" },
                        { "Key" : "Env", "Value" : "Dev  (※Reservation[0]、Instances[1]、Tags[1])" }
                    ]
                }
            ]
        },
        {
            "Instances": [
                {
                    "Tags": [
                        { "Key" : "Name", "Value" : "server-1 (※Reservation[1]、Instances[0], Tags[0])" },
                        { "Key" : "Env", "Value" : "Prod (※Reservation[1]、Instances[0]、Tags[1])" }
                    ]
                },
                {
                    "Tags": [
                        { "Key" : "Name", "Value" : "server-1  (※Reservation[1]、Instances[1]、Tags[0])" },
                        { "Key" : "Env", "Value" : "Dev  (※Reservation[1]、Instances[1]、Tags[1])" }
                    ]
                }
            ]
        }
    ]
}
```

 - ReservationsもInstancesも、複数存在する場合も、0の場合もあります。
 - Tagsの数(Tagsの塊の数)は、1つのInstanceの中に複数は存在しませんが0の場合はあります。

まあざっくりと書くと、
**0個以上のReservationsの中の、0個以上のInstancesの中の、Tagsがあればその中** 
に各タグが存在します。

## 集計する方法
集計するときに少々厄介なのが、格納されている形式が、

```json
{ "タグのキー名" : "タグの値" }
```

となっているのではなく

```json
{ "Key" : "タグのキー名", "Name" : "タグの値" }
```

となっていることです。

そのため一旦変換して
``{ "タグのキー名" : "タグの値" }``
にしたものを集計しようかと考えましたが、「それぞれの数さえ分かればいいのよ」っていう心の声によって
``"タグのキー名 = 'タグの値'"``
の文字列の形式に変換して集計することにしました！もうこれでいいやと。楽ですからね。・・雑だね。

Python、PHP、Javascript を使って集計する方法です。
冒頭に記載したCLI／APIを使って *ec2_describe_instances.json* ファイルが保存されていることを前提とします。

## Pythonで集計

```python
import codecs, json

# Ec2:DescribeInstances の結果を保存したJsonファイルを読み込む
arr = json.load( codecs.open('./ec2_describe_instances.json', 'r', 'utf-8') )

# 全てのタグを "タグキー = タグの値" の文字列にして格納するための配列
tag_flat_arr = []

result_arr = {}

for rsv in arr["Reservations"]:
    for inst in rsv["Instances"]:
        for tag in inst ["Tags"]:
            # "タグキー = タグの値" の文字列を配列の末尾に追加
            tag_flat_arr.append( tag["Key"] + " = " + tag["Value"] )

# タグ順にソート(optional)
tag_flat_arr.sort()

# 値の出現回数を集計
for tag_val in tag_flat_arr:
    result_arr[tag_val] = result_arr[tag_val] + 1 if (result_arr.get(tag_val)) else 1

#  → 取得結果が result_arr に格納される

# コンソールでの確認用
print(json.dumps(result_arr, indent=4))
```

codecsは、実施した環境がCP932とUTF-8の考慮が必要だったためですので通常はもっとシンプルでよいです。


## PHPで集計


```php
<?php

// 全てのタグを "タグキー = タグの値" の文字列にして格納するための配列
$tag_flat_arr = [];

// Ec2:DescribeInstances の結果を保存したJsonファイルを読み込む
$arr = json_decode( file_get_contents("./ec2_describe_instances.json"), true);

foreach ($arr["Reservations"] as $rsv) {
    foreach ($rsv["Instances"] as $inst) {
        foreach ($inst["Tags"] as $tag) {

            array_push($tag_flat_arr, $tag["Key"] . " = '" . $tag["Value"] . "'");
        }
    }
}

// 値の出現回数を集計
$result_arr = array_count_values( $tag_flat_arr );

// ==> 取得結果が$result_arr に格納される

// タグ順にソート(optional)
ksort( $result_arr );

// Webブラウザでの確認用
print_r("<pre>" . json_encode( $result_arr, JSON_PRETTY_PRINT ) . "</pre>");

?>
```


## Javascriptで集計


```javascript
<!doctype html>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<span id="out"> </span>

<script>
"use strict";

// 全てのタグを "タグキー = タグの値" の文字列にして格納するための配列
var tag_flat_arr = [];

var result_arr = {};

// Ec2:DescribeInstances の結果を保存したJsonファイルを読み込む
$.getJSON( "./ec2_describe_instances.json" )
    .done(function(arr){
        $.each(arr["Reservations"], function(i, rsv){
            $.each(rsv["Instances"], function(i2, inst){
                $.each(inst["Tags"], function(i3, tag){

                    // "タグキー = タグの値" の文字列を配列の末尾に追加
                    tag_flat_arr.push( tag["Key"] + " = '" + tag["Value"] + "'");
                });
            });
        });

        // タグ順にソート(optional)＋値の出現回数を集計
        $.each(tag_flat_arr.sort(), function(i, tag_val){

            result_arr[tag_val] = (result_arr[tag_val]) ? result_arr[tag_val] + 1  : 1;
        });

        // → 取得結果が result_arr に格納される

        $("#out").html("<pre>" + JSON.stringify(result_arr, null, 4) );
    }
);
</script>

```


## 結果
上記は、どれも同じようなやり方です。
3重ループのべた書きとかこれまた雑ですが。値が取れればいいんだ、ということで。

例えば、以下のような出力になります。

```json
{
   "Env = 'Prod'" : 3,
   "Env = 'Dev'" : 3,
   "Maint = '0'" : 4,
   "Maint = '2'" : 1,
   "Name = 'server-1'" : 1,
   "Name = 'server-2'" : 1,
   "Name = 'server-3'" : 1,
   "Name = 'server-1dev'" : 1,
   "Name = 'server-2dev'" : 1,
   "Name = 'server-3dev'" : 1,
   "Operation = 'True'" : 4,
   "Operation = 'False'" : 2
}
```

###注意点
・区切り文字は「 = 」にしましたが、キー名やキーの値に登場してこないものを選択する必要があります。
・いろいろな特殊文字が含まれているとNGのケースもあるかもしれません。

###応用例

特定のタグで抽出できるようにもう少しスクリプトをカスタムして、Lambdaで登録しておくのもよいでしょう。

タグの値が一定の数を超えるとジョブのパフォーマンス影響が出るなどまずい環境の場合、タグが増加するイベントをトリガーにLambdaを実行し、通知する、といったことが考えられます。