一括請求で連結したアカウントを横断してEC2インスタンスリストを表示してリザーブドインスタンスを最適化する

AWSには[一括請求（ConsolidatedBilling）](http://docs.aws.amazon.com/ja_jp/awsaccountbilling/latest/about/consolidatedbilling.html)という機能があります。これは複数アカウントの請求を一つの親アカウントに結合して決済を行う機能ですが、そのメリットは請求の一元化だけに留まらず、ボリュームディスカウント対象の容量統合や未使用リザーブドインスタンスの共用など、金銭面のメリットも大きいという嬉しい機能です。

さてそんなメリットですが、複数案件でAWSアカウントを分けて使ってそれぞれのアカウントでEC2インスタンスを起動したりReservedInstanceを購入して更にいくつかの案件はもう終わっててーみたいな運用を続けていると、全体でいくつのインスタンスが起動していてRIを余らせたりしていないか？などの把握が段々難しくなってきます。

特にせっかくRIを持ってるのにAZが違っていて適用されてないだとかは非常に勿体無い、更にはMulti-AZでオートスケールしてるインスタンスがたまたまaゾーンに偏っててcゾーン用のRIが余る状況が出来ちゃったりとかもあるし。

そんな理由から全アカウントを横断して describe-instances と describe-reserved-instances を実行して一覧表示したりしたいと思ったわけです。

# 作り途中のチラ見せスクリーンショット
とりあえず今直近でやりたかったことはReservedInstanceの棚卸しでSTSの実験がてらシコシコと作ったツールのスクショがこちらです。

![4114711.png](https://qiita-image-store.s3.amazonaws.com/0/18726/1a2aa042-6b0e-c01a-c16c-0361bc5fd790.png)

- 上の方は起動中のEC2インスタンスとリザーブドインスタンスをずらっとダンプしたもので、頭の数字は起動数で、@がついてるのはスポットインスタンス、マイナスがついてるのはReservedInstanceです。
- 下の方は見やすくまとめたやつで頭の数字は、オンデマンドインスタンスとリザーブドインスタンスの相殺数、オンデマンドインスタンス数、リザーブドインスタンス数、スポットインスタンス数となっています。
- 重要なのは頭のsumの数字ですこれが…
 - 0だとリザーブドインスタンスがキッチリ使えてていい感じ
 - プラスの場合はリザーブドインスタンスが適用されてないのでRI購入を検討する
 - マイナスの場合はリザーブドインスタンスが余ってるので勿体無い！
 - 特に同じインスタンスファミリーでプラスとマイナスが両方あるというミスマッチが最悪です。お金の無駄です。要はインスタンスタイプやAZが一致していなくて割引が適用されていないという状況です。

で、コレを見ながら余ってるRIをmodifyして別のインスタンスタイプに分割や統合したり、AZの変更をしたりすることで無駄を取り除いてコストを最適化したりするわけです。
今後の利用計画の絡みもあるはずなので、完全にプラマイゼロは目指さずに適当なバランスまでいけたとこで妥協します。

あと特に想定外だったのは、Multi-AZのオートスケールを複数設定してたら運悪く端数分のインスタンスが一つのAZに偏ってしまって、反対のAZのRIを無駄にした上に高額料金を支払っていることに気がつかない。なんて勿体無いことがあります。こういうのは早めに気がついて直したいですよね。
とりあえず今日は一覧が出来るようになったので、次はRIのタイプやAZの偏りを発見したらその調整まで自動で行なってくれるようにしようかなぁと企んでます。

今はまだかなり恥ずかしい感じのコードなので整理してそのうちGithub辺りに公開しておきます。需要があるかは知らんけどｗ


# 実装方法
横断的にdescribeする為に、対象の全アカウントのアクセスキーとシークレットをコピペで集めて一箇所で管理するのはセキュリティ的にも運用的にもあまりかっこよくないので、[AWS Security Token Service](http://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html)を使っています。STSは何かというと、適切な権限を持った有効期限付きのアクセスキーを一時的に発行したり出来るという地味に重要なサービスです。

流れとしては、以下の様な感じになってます。

## まず下準備
- 親アカウントにIAMロール`ConsolidatedAuditor`を作る（別に一括請求の親アカウントである必要は特に無い）
- 全アカウントにIAMロール`ConsolidatedReader`を作る（親アカウントの情報も知りたいので親でも作る）
- 親のConsolidatedAuditorロールのPermissionに以下のポリシーを設定する。これは他アカウントのConsolidatedReaderという名前のIAMロールに対してAssumeRoleを実行出来るようにする設定です。（ここではAssumeRoleのAPIコールの実行を許可しただけで、実際に権限をもらえるかどうかは相手側の許可も必要です）

```json:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1388045626000",
      "Effect": "Allow",
      "Action": [
        "sts:AssumeRole"
      ],
      "Resource": [
        "arn:aws:iam::*:role/ConsolidatedReader"
      ]
    }
  ]
}
```

- 全アカウントのConsolidatedReaderロールのパーミッションにPolicyTemplateからRead Only Accessを選んで追加する。（今はEC2インスタンスとリザーブドインスタンスの一覧を取得したいだけだけど、今後RDSとかElastiCacheとかRoute53のゾーンリストとか色々見たくなると思うので。気前よくReadOnlyAccessを使った）
- 更にConsolidatedReaderロールの`Trust Relationships`に以下のJSONを設定する。（Principal->AWSに設定しているのはConsolidatedAuditorのARNです。これによってアカウントをまたいだロール権限の引き渡しができるようになります。STSのキモです。）

```json:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::112233445566:role/ConsolidatedAuditor"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

## APIコールの流れ
IAM関連の準備ができたところで、今度は実際に各アカウント毎にDescribeInstancesとかを呼びまります。基本的な手順は以下のようになります。

- まず親アカウントでConsolidatedAuditorロールを付けたEC2インスタンスを起動します。t1.microで十分です。以後の作業はこのインスタンス上で行います。
- 全アカウントのConsolidatedReaderロールのARNリストは事前に集めておいて配列か何かで持っておきます。
- 各アカウントのロールARNの権限をよこせーと、順番にAWS.STSのassumeRoleを実行しまくります。
- AssumeRoleが成功すれば各アカウントの有効期限付きのクレデンシャル（アクセスキーとシークレットとセッショントークン）が取得できます。（他のAPI使う度に取得してたら大変なのでこれはキャッシュしておくと後の作業が捗ります）
- 各アカウントのクレデンシャルを使ってDescribeInstancesとか必要なAPIを必要なだけ呼びまくる。

以上です。IAMとSTSの全体像が分かってしまえば案外シンプルですが最初はかなり戸惑うことだと思います。やりたいことを思い浮かべながら何度かIAM設定の下準備辺りをやってみれば何となく分かってくるかと…。