2014/01/31

AWS CloudWatchの課金情報をZabbixのLow Level Discoveryで見られるようにする #1

AWSのCloudWatchでは、EC2インスタンスの性能情報と同様に、AWSアカウントに紐付く課金情報(AWS/Billing)をAPI経由で取得することが出来ます。
今回はまずCloudWatchで課金情報項目を引っ張り出して、ZabbixのLow Level Discoveryで拾えるように加工するところまでやってみます。

AWS CLIをインストール

今回は、オンプレミス環境に置いてあるCentOS 6上から監視するイメージです。
まずはAWS APIを利用するために、AWSコマンドラインインターフェース ツール(AWS CLI)を導入します。
AWS CLIはPython pip経由でインストールするのが手っ取り早いので、EPELのpython-pip経由で導入。

 # yum install python-pip
 # python-pip awscli
 # aws configure

jqのインストール

AWS CLIの出力は基本的にJSONフォーマットになるので、整形したり編集したりをコマンドラインで出来る jq コマンドを導入します。

 # yum install jq

ZabbixのLow Level Discoveryが受け付けるJSONフォーマット

Zabbix 2.0 Manual - 3.4. Creating custom LLD rules を見ると分かる…と思うのですが、簡単にまとめてみました。
  • 最上位となる要素は辞書(Dictionary)。
    • 内容は キー "data"、値はディスカバリした結果を格納した配列(Array)。
    • 例)
      { "data": [ディスカバリー結果] }
  • [ディスカバリ結果]は、それぞれZabbixマクロの変数名と同じキーと、それに対する値を持つ辞書の配列。
    • 例)
       [{ "{#FS_NAME}":"/", "{#FS_TYPE}":"ufs" },  { "{#FS_NAME}":"/boot", "{#FS_TYPE}":"ufs" }]
    • 変数名に使えるのは "A-Z , 0-9 , _ , ."のいずれか。
      ※英小文字は使えないので注意。
  • 組み立ててみて、下記のような感じになるとOK
    { "data": [  { "{#FS_NAME}":"/", "{#FS_TYPE}":"ufs" },  { "{#FS_NAME}":"/boot", "{#FS_TYPE}":"ufs" }
    ]
     }

AWS CloudWatchの課金情報メトリックのフォーマット

AWS CLI経由で課金情報のメトリック一覧を出力させてみます。
※その月に利用したサービスに応じて、出力されるメトリック(項目)は増減します。

# aws cloudwatch --region us-east-1 list-metrics | jq "." | head -n 30
{
  "Metrics": [
    {
      "MetricName": "EstimatedCharges",
      "Dimensions": [
        {
          "Value": "AmazonEC2",
          "Name": "ServiceName"
        },
        {
          "Value": "USD",
          "Name": "Currency"
        }
      ],
      "Namespace": "AWS/Billing"
    },
    {
      "MetricName": "EstimatedCharges",
      "Dimensions": [
        {
          "Value": "AmazonRoute53",
          "Name": "ServiceName"
        },
        {
          "Value": "USD",
          "Name": "Currency"
        }
      ],
      "Namespace": "AWS/Billing"
    },
    …

ざっと見た感じ、以下のように変換すると良さそうです。

AWS CLIJSON Zabbix LLDJSON
.Metrics .Data
.Namespace {#AWS_BILLING_NAMESPACE}
.MetricName {#AWS_BILLING_METRIC_NAME}
(.MetricName + .DimensionsServiceName) {#AWS_BILLING_METRIC_DISPLAYNAME}
(.Dimensions | tostring) {#AWS_BILLING_METRIC_DIMENSIONS}

というわけで、jqコマンドで変換するコマンドを組み立ててみる。。。
(試行錯誤すること 2時間ほど…)

できました!

 # aws cloudwatch --region us-east-1 list-metrics | jq '.Metrics[] | {
 "{#AWS_BILLING_NAMESPACE}": (.Namespace),
 "{#AWS_BILLING_METRIC_NAME}": (.MetricName),
 "{#AWS_BILLING_METRIC_DISPLAYNAME}": (.MetricName + " - " + ((.Dimensions[] | select(.Name=="ServiceName") | .Value) // "total") ),
 "{#AWS_BILLING_METRIC_DIMENSIONS}": (.Dimensions | tostring)
}' | jq -s '{data:(.|sort)}'

ためしてみる!

# aws cloudwatch --region us-east-1 list-metrics | jq '.Metrics[] | {
 "{#AWS_BILLING_NAMESPACE}": (.Namespace),
 "{#AWS_BILLING_METRIC_NAME}": (.MetricName),
 "{#AWS_BILLING_METRIC_DISPLAYNAME}": (.MetricName + " - " + ((.Dimensions[] | select(.Name=="ServiceName") | .Value) // "total") ),
 "{#AWS_BILLING_METRIC_DIMENSIONS}": (.Dimensions | tostring)
 }' | jq -s '{data:(.|sort)}'

{
  "data": [
    {
      "{#AWS_BILLING_NAMESPACE}": "AWS/Billing",
      "{#AWS_BILLING_METRIC_NAME}": "EstimatedCharges",
      "{#AWS_BILLING_METRIC_DISPLAYNAME}": "EstimatedCharges - AWSDataTransfer",
      "{#AWS_BILLING_METRIC_DIMENSIONS}": "[{\"Value\":\"AWSDataTransfer\",\"Name\":\"ServiceName\"},{\"Value\":\"USD\",\"Name\":\"Currency\"}]"
    },
    {
      "{#AWS_BILLING_NAMESPACE}": "AWS/Billing",
      "{#AWS_BILLING_METRIC_NAME}": "EstimatedCharges",
      "{#AWS_BILLING_METRIC_DISPLAYNAME}": "EstimatedCharges - AmazonEC2",
      "{#AWS_BILLING_METRIC_DIMENSIONS}": "[{\"Value\":\"AmazonEC2\",\"Name\":\"ServiceName\"},{\"Value\":\"USD\",\"Name\":\"Currency\"}]"
    },
    {
      "{#AWS_BILLING_NAMESPACE}": "AWS/Billing",
      "{#AWS_BILLING_METRIC_NAME}": "EstimatedCharges",
      "{#AWS_BILLING_METRIC_DISPLAYNAME}": "EstimatedCharges - AmazonRoute53",
      "{#AWS_BILLING_METRIC_DIMENSIONS}": "[{\"Value\":\"AmazonRoute53\",\"Name\":\"ServiceName\"},{\"Value\":\"USD\",\"Name\":\"Currency\"}]"
    }, …

お、上手くいっているっぽい。

ちなみに上記の情報を基に、実際の課金情報を引っ張ってみるとこうなります。

 # aws cloudwatch --region us-east-1 get-metric-statistics \
    --namespace "AWS/Billing" \
    --metric-name "EstimatedCharges" \
    --dimensions "[{\"Value\":\"AmazonEC2\",\"Name\":\"ServiceName\"},{\"Value\":\"USD\",\"Name\":\"Currency\"}]" \
    --period 60 \
    --start-time $(date --iso-8601=seconds --date '24 hour ago') \
    --end-time   $(date --iso-8601=seconds) \
    --statistics "Maximum" \
     | jq '.Datapoints | sort_by(.Timestamp)'

[
  {
    "Unit": "None",
    "Maximum": 1.19,
    "Timestamp": "2014-01-30T11:34:00Z"
  },
  {
    "Unit": "None",
    "Maximum": 1.2,
    "Timestamp": "2014-01-30T15:34:00Z"
  },
  {
    "Unit": "None",
    "Maximum": 1.2,
    "Timestamp": "2014-01-30T19:34:00Z"
  },
  {
    "Unit": "None",
    "Maximum": 1.21,
    "Timestamp": "2014-01-30T23:34:00Z"
  },
  {
    "Unit": "None",
    "Maximum": 1.21,
    "Timestamp": "2014-01-31T03:34:00Z"
  },
  {
    "Unit": "None",
    "Maximum": 1.21,
    "Timestamp": "2014-01-31T07:34:00Z"
  }
]

実際には最後の値しか要らなさそうなので、jqコマンドに "reverse | .[0]"を追加することになりそうです。( .[-1]だと上手くいかない )
続きはまた今度。

AWSのELBがなんで動的IPなのか考えてみた。

最近、AWSを触る日々。

ふつーの環境でBIG-IPだか、NetScalerだかってロードバランサーを使ってると不思議なのは、
「ELB (Elastic Load Balancing)はエンドポイント名=FQDNは固定なのに、なんでIPアドレスは動的なの?」
ってところ。

# ちなみにさらっと調べてみた感じ、EIPをELBに割り当てることも出来ないみたいですね。。。


で、AWSの世界でELBを作るとしたらこうなのかな?とすると納得というモデルを考えてみた。


こういうもんだととらえれば、あえてなぜ代表IPを固定にしてないのか分かりますね。

古典的なロードバランサが代表IPを固定に出来るのは、前提としてネットワークがマルチキャストを通してたり、STPやStackされたL2スイッチに横並びに配置されることを想定しているからで、
AWSでマルチAZ構成だったりすればそういう前提は成り立たないですから、その中で出来る構成と言えばDNSラウンドロビン+リバースプロキシのような構成だと思います。

ま、ここまで勝手な妄想なので本当かどうかは知りませんが。

↓この辺を読んでも、なぜなのかははっきり書いていません(当然ですね・・・