vague memory

うろ覚えを無くしていこうともがき苦しむ人の備忘録

mackerel-plugin-apache2 BASIC認証

ApacheBASIC認証が掛かっている場合の取得方法です。
ユーザ・パスワードを設定するオプションは無いですが、URLに直接入れ込む形で設定できます。

apache2.conf

[plugin.metrics.apache2]
command = "/usr/local/bin/mackerel-plugin-apache2 -o <USER>:<PASSWORD>@<HOSTNAME>"
type = "metric"

# ロングオプションの場合
command = '''
  /usr/local/bin/mackerel-plugin-apache2 \
     --http_host <USER>:<PASSWORD>@<HOSTNAME>
'''
type = "metric"

access_log 例

# 未設定
localhost - - [24/May/2016:05:59:00 +0900] "GET /server-status?auto HTTP/1.1" 401 401 "-" "Go-http-client/1.1" 505 761
# ユーザ・パスワード設定
localhost - authuser [24/May/2016:05:59:15 +0900] "GET /server-status?auto HTTP/1.1" 200 1181 "-" "Go-http-client/1.1" 1056 893

Mackerel事始め mkr(3)

Mackerel で mkr を使用した CLI 操作の続きです。

f:id:htnosm:20160523041549p:plain

create

ホストを作成します。 オプション指定無しだと working で作成されます。

$ mkr create TEST
   created XXXXXXXXtLu

f:id:htnosm:20160523041550p:plain

  • ステータスをメンテナンスとし、ロールを指定した場合
$ mkr create -st maintenance -R testservice:testrole TEST
   created XXXXXXXXPcN
   updated XXXXXXXXPcN maintenance

f:id:htnosm:20160523041551p:plain

update

ホスト情報を更新します。

$ mkr update --displayName 表示名更新 XXXXXXXXtLu
   updated XXXXXXXXtLu

f:id:htnosm:20160523041552p:plain

  • ステータスをスタンバイ、ロールを更新した場合

ロールは追加されるようです。

$ mkr update --st standby --roleFullname testservice:web -n test2 XXXXXXXXPcN
   updated XXXXXXXXPcN

f:id:htnosm:20160523041553p:plain

fetch

メトリックの最新値を取得します。 name で指定する値は上記ヘルプページに記載があります。

  • loadavg5
$ mkr fetch -n loadavg5 `mkr hosts -n TEST | jq -r '.[].id'`
{
    "XXXXXXXkAJd": {
        "loadavg5": {
            "time": 1463941200,
            "value": 0.45499999999999996
        }
    }
}
  • cpu.system.percentage
$ mkr fetch -n cpu.system.percentage `mkr hosts -n TEST | jq -r '.[].id'`
{
    "XXXXXXXkAJd": {
        "cpu.system.percentage": {
            "time": 1463941200,
            "value": 0.6792990723058558
        }
    }
}

複数ホスト

hostId をスペース区切りで指定することで複数ホストのメトリック値の収集が可能です。

% mkr fetch -n loadavg5 `mkr hosts -n TEST2 | jq -r '.[].id'` `mkr hosts -n TEST | jq -r '.[].id'`
{
    "XXXXXXXkcAq": {
        "loadavg5": {
            "time": 1463941500,
            "value": 0.21799999999999997
        }
    },
    "XXXXXXXkAJd": {
        "loadavg5": {
            "time": 1463941500,
            "value": 0.144
        }
    }
}

throw

mackerel-agentが存在しない環境からでもメトリックデータの送信が行えます。

書式

以下の様な書式で値を登録します。 数値以外の値は投稿できません。

cat <<EOF | mkr throw --host <hostId>
<name>  <value> <time>
<name>  <value> <time>
EOF
...

cat <<EOF | mkr throw --service My-Service
<name>  <value> <time>
<name>  <value> <time>
EOF
...

ホスト(カスタムメトリック)

$ cat <<EOF | mkr throw --host XXXXXXXXtLu
lv15 `uptime | awk '{print $10}'` `date +'%s'`
uptime `uptime | awk '{print $1}'` `date +'%s'`
str "aaa" `date +'%s'`
EOF
   warning Failed to parse values: strconv.ParseFloat: parsing "3:48": invalid syntax
   warning Failed to parse values: strconv.ParseFloat: parsing "\"aaa\"": invalid syntax
    thrown XXXXXXXXtLu 'lv15    1.810000    1463942894'

f:id:htnosm:20160523041554p:plain

サービスメトリック

$ cat <<EOF | mkr throw --service test
ResponseTime `curl -L -S -s 'https://example.com/' -o /dev/null -w "%{time_total}" | sed -e 's/%//'` `date +'%s'`
EOF
    thrown test 'ResponseTime   0.714000    1463943757'

f:id:htnosm:20160523041555p:plain

存在しないサービス名指定ではエラーとなります。

% cat <<EOF | mkr throw --service TEST-SERVICE
pipe heredoc> ResponseTime `curl -L -S -s 'https://example.com/' -o /dev/null -w "%{time_total}" | sed -e 's/%//'` `date +'%s'`
pipe heredoc> EOF
     error API result failed: 404 Not Found

mkr コマンドを一通り試しましたが、一括で操作が行える点が便利だと感じました。 特にAPI + α の仕様となっている monitors と、 一括退役が行える retire は利用頻度が高いのではと思います。

Mackerel事始め mkr(2)

Mackerel で mkr を使用した CLI 操作の続きです。 ホスト一覧取得と退役操作を行います。

f:id:htnosm:20160523024748p:plain

hosts

ホスト一覧を取得します。ホストを対象とする操作を行う際のidを抽出する為に使用します。

オプション

オプション ロングオプション 説明
-n --name ホスト名で検索。 完全一致。
-s --service サービス名で検索
-r --role ロール名で検索。複数指定可。 サービス未指定だと無視。
-st --status ステータスで検索。複数指定可。デフォルト working もしくは standby
-f --format 出力フォーマット指定
-v --verbose 詳細表示

-f | --format

READMEを参照すると以下の様な指定方法のようです。mkrはjqと組み合わせて使う事が多いので、固有の指定方法ではなく、jq側でフォーマット調整をすることになりそうです。

mkr hosts -f '{{range .}}{{if (len .Interfaces)}}{{(index .Interfaces 0).IPAddress}}{{end}}{{"\t"}}{{.Name}}{{"\n"}}{{end}}'

id 抽出

READMEを真似て id を抜き出してみると以下のようになります。

$ mkr hosts -f '{{range .}}{{.ID}}{{"\n"}}{{end}}'
XXXXXXXXes1
XXXXXXXXWNU
XXXXXXXXAJd
・・・略・・・
素直に jq 使用

私はjqの方が慣れているのでこちらを使用すると思います。

mkr hosts | jq -r '.[].id'

-v | --verbose

オプション無し

$ mkr hosts -n ip-172-31-3-172
[
    {
        "id": "XXXXXXXX37m",
        "name": "ip-172-31-3-172",
        "status": "working",
        "roleFullnames": [
            "test:testrole"
        ],
        "isRetired": false,
        "createdAt": "Mar 23, 2016 at 4:39pm (JST)",
        "ipAddresses": {
            "eth0": "172.31.3.172"
        }
    }
]

オプション有り

オプション無しの出力と出力形式が変わり、CPUやメモリ情報が追加されます。
createAt はUNIXTIMEになるのでスクリプト等で使用する場合はこちらの方が扱い易いと思います。

  • 長いので所々省略
$ mkr hosts -n ip-172-31-3-172 -v
[
    {
        "id": "XXXXXXXX37m",
        "name": "ip-172-31-3-172",
        "type": "unknown",
        "status": "working",
        "roles": {
            "test": [
                "testrole"
            ]
        },
        "createdAt": 1458718748,
        "meta": {
            "agent-revision": "7d278aa",
            "agent-version": "0.30.0",
            "block_device": {
                "loop0": {
                    "removable": "0",
                    "size": "0"
                },
・・・略・・・
                "ram0": {
                    "removable": "0",
                    "size": "131072"
                },
・・・略・・・
                "xvda": {
                    "removable": "0",
                    "size": "16777216"
                }
            },
            "cpu": [
                {
                    "cache_size": "25600 KB",
                    "core_id": "0",
                    "cores": "1",
                    "family": "6",
                    "mhz": "2500.066",
                    "model": "62",
                    "model_name": "Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz",
                    "physical_id": "0",
                    "stepping": "4",
                    "vendor_id": "GenuineIntel"
                }
            ],
            "filesystem": {
                "/dev/xvda1": {
                    "kb_available": 5.8507e+06,
                    "kb_size": 8.115168e+06,
                    "kb_used": 1.829192e+06,
                    "mount": "/",
                    "percent_used": "24%"
                },
・・・略・・・
            },
            "kernel": {
                "machine": "x86_64",
                "name": "Linux",
                "os": "GNU/Linux",
                "release": "3.13.0-48-generic",
                "version": "#80-Ubuntu SMP Thu Mar 12 11:16:15 UTC 2015"
            },
            "memory": {
                "active": "474800kB",
                "anon_pages": "55116kB",
                "bounce": "0kB",
                "buffers": "136852kB",
                "cached": "482840kB",

・・・略・・・
                "writeback": "0kB"
            }
        },
        "interfaces": [
            {
                "name": "eth0",
                "ipAddress": "172.31.3.172",
                "macAddress": "00:00:00:00:00:7d"
            }
        ]
    }
]

retire

ホストの退役を行います。 オプションは --force のみで、違いは y/n プロンプトの有無です。

USAGE:
    mkr retire hostIds...

オプション有無

オプション未指定では y/n を聞いてきます。

# forceオプション無
$ mkr retire XXXXXXXXCVE
Retire following hosts.
  XXXXXXXXCVE
Are you sure? (y/n) [y]: y
   retired XXXXXXXXCVE
# forceオプション有
$ mkr retire --force XXXXXXXXHwG
   retired XXXXXXXXHwG

キャンセル (n押下)

明示的に "n" を指定しないと、 "y" を指定した際と同じ動作です。

# n 押下でキャンセル
$ mkr retire XXXXXXXX1N5
Retire following hosts.
  XXXXXXXX1N5
Are you sure? (y/n) [y]: n
           retirement is canceled.
# n 以外押下だと y 押下と同様に削除
$ mkr retire XXXXXXXX1N5
Retire following hosts.
  XXXXXXXX1N5
Are you sure? (y/n) [y]:
   retired XXXXXXXX1N5

複数指定

スペース区切りで複数指定が可能です。

% mkr retire XXXXXXXXUmG XXXXXXXXrdm
Retire following hosts.
  XXXXXXXXUmG
  XXXXXXXXrdm
Are you sure? (y/n) [y]:
   retired XXXXXXXXUmG
   retired XXXXXXXXrdm

PowerOffを全てretire

hosts コマンドから id を抽出して削除する例です。

mkr retire --force $(mkr hosts --status poweroff | jq -r '.[].id' | tr '\n' ' ')

ホストの退役はWeb画面上からだと1ホストずつしか行えないため、一括退役をさせる場合に非常に便利です。

おそらく次回に続きます

Mackerel事始め mkr (1)

Mackerel の CLI 操作を試します。

f:id:htnosm:20160523005746p:plain

WebAPIが用意されていますが、APIを簡略化したコマンド mkr があるのでそちらを利用してみます。

機能

利用頻度の高い操作が揃っているようです。mkrに無い機能の CLI 利用は直接APIを使用することになります。

Command 機能 用途
monitors 監視 監視ルールの取得・更新・差分表示
alerts アラート 一覧取得・クローズ
hosts ホスト 一覧取得
status ホスト 詳細情報取得
create ホスト 新規作成
update ホスト 情報更新
retire ホスト ホストを退役
fetch メトリック 最新値の取得
throw メトリック 値の投稿

APIKEY

APIKEYの設定が必要になるので予め取得・設定をしておきます。 設定をしていない場合以下の様なエラーが出力されます。

$ mkr hosts
     error
    MACKEREL_APIKEY environment variable is not set. (Try "export MACKEREL_APIKEY='<Your apikey>'")

monitors

公式ヘルプにも固有ページがあります。 jsonダウンロード → 編集 → jsonアップロードで設定変更が簡単に行なえます。
サブコマンドも pull、diff、push のみとシンプルです。

設定数が多くなってきた場合、jsonファイルは可読性に難有りになるかもしれません。(split的な物が欲しくなりそうです)

ちなみにヘルプ表示の際に GLOBAL OPTION は使えないようです。

# 不可
#mkr monitors {-h|--help}
# ヘルプ表示
mkr monitors {h|help}

pull

  • Mackerel から monitor 設定をダウンロード

直下に monitors.json が生成されます。

$ mkr monitors pull
$ ls -l
total 8
-rw-r--r--  1 testuser  testgroup  1394  5 10 13:27 monitors.json
$ cat monitors.json
{
    "monitors": [
        {
            "id": "XXXXXXXXkdu",
            "type": "connectivity"
        },
        {
            "id": "XXXXXXXXQTj",
            "name": "loadavg5",
            "type": "host",
            "metric": "loadavg5",
            "operator": ">",
            "warning": 5,
            "critical": 10,
            "duration": 10
        },
        {
            "id": "XXXXXXXXZfY",
            "name": "CPU %",
            "type": "host",
・・・略

diff

  • Mackerel と monitors.json の差分表示
# Mackerel との差分が無い場合
$ mkr monitors diff
Summary: 0 modify, 0 append, 0 remove

# monitors.json を編集
$ vi monitors.json
# Mackerel との差分が有る場合
$ mkr monitors diff
Summary: 1 modify, 0 append, 0 remove

  {
    "name": "Filesystem %",
    "type": "host",
    "metric": "disk%",
    "operator": ">",
-   "warning": 50.000000,
+   "warning": 60.000000,
    "critical": 80.000000,
    "duration": 1,
  },

push

  • Mackerel への反映
$ mkr monitors push
      info Update a rule.
 {
   "id": "XXXXXXXXopA",
   "name": "Filesystem %",
   "type": "host",
   "metric": "disk%",
   "operator": ">",
   "warning": 60,
   "critical": 80,
   "duration": 1
 },
$ mkr monitors diff
Summary: 0 modify, 0 append, 0 remove

Web画面で確認

  • 反映前

f:id:htnosm:20160523005747p:plain

  • 反映(push)後

f:id:htnosm:20160523005748p:plain

Web画面でも変更が確認できました。

alerts

アラート操作としては一覧表示とクローズのみです。 ちなみにこちらもヘルプ表示の際に GLOBAL OPTION は使えないようです。

list

  • アラート一覧出力

アラートが存在しない場合は当然ですが何も返って来ません。

$ mkr alerts list
$

アラートが存在する場合は以下のようになります。レベルに色が付いていて良い感じです。

  • アラートが存在

f:id:htnosm:20160523005749p:plain

  • コマンド出力結果

f:id:htnosm:20160523005750p:plain

close

  • アラートのクローズ(アラートID指定)

list で取得した Id を指定してクローズします。

  • コマンド出力結果

f:id:htnosm:20160523005751p:plain

  • アラートがクローズされる

f:id:htnosm:20160523005752p:plain


きっと次回に続きます

AWS Lambda で CloudWatch Logs のログ本文をSlack通知(2)

f:id:htnosm:20160505000213p:plain

前回 AWS Lambda で CloudWatch Logs のログ本文をSlack通知(1) の続きです。
SNSを介さず、Lambdaから直接Slackへ投稿する方法です。

f:id:htnosm:20160504231804p:plain

(2) Stream to AWS Lambda でSlack通知

各種設定

Blueprints冒頭に記載されている Slack Integration や KMS の設定については割愛します。

Lambda Function 作成

  • [Services] -> [Lambda] -> [Create a Lambda Function]

f:id:htnosm:20160504231806p:plain

cloudwatchlog-alarm-to-slack.py

今回使用するFunctionです。 Blueprintと同様に ENCRYPTED_HOOK_URL、SLACK_CHANNEL にSlackの設定値を入れます。

from __future__ import print_function

import base64
import json
import zlib
import datetime
import boto3
import logging

from base64 import b64decode
from urllib2 import Request, urlopen, URLError, HTTPError

ENCRYPTED_HOOK_URL = ''  # Enter the base-64 encoded, encrypted key (CiphertextBlob)
SLACK_CHANNEL = '#'  # Enter the Slack channel to send a message to

HOOK_URL = "https://" + boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    data = zlib.decompress(base64.b64decode(event['awslogs']['data']), 16+zlib.MAX_WBITS)
    data_json = json.loads(data)
    log_json = json.loads(json.dumps(data_json["logEvents"][0], ensure_ascii=False))
    if data_json["logGroup"]:
        date = datetime.datetime.fromtimestamp(int(str(log_json["timestamp"])[:10])) + datetime.timedelta(hours=9)
        message = str(date) + " [CloudWatch Log Alarm] " + data_json["logGroup"] + " / " + data_json["logStream"] + "\nLogMessages:"
        logger.info(data_json)
        for e in data_json["logEvents"]:
            message = message + '\n' + e['message']
            logger.info("LogMessage: " + str(message))

        slack_message = {
            'channel': SLACK_CHANNEL,
            'text': "%s" % (message)
        }

        req = Request(HOOK_URL, json.dumps(slack_message))
        try:
            response = urlopen(req)
            response.read()
            logger.info("Message posted to %s", slack_message['channel'])
        except HTTPError as e:
            logger.error("Request failed: %d %s", e.code, e.reason)
        except URLError as e:
            logger.error("Server connection failed: %s", e.reason)

IAM role 設定

Lambda Function 作成時に指定した Role に下記が実行できるよう権限を付与します。

  • lambda_basic_execution (Function作成時に付与するポリシー)
  • KMS復号化
  • filter-log-events実行

f:id:htnosm:20160504231807p:plain

Metric Filter 作成

CloudWatch Logs からメトリックフィルタを作成します。

  • CloudWatch -> Logs -> LogGroup -> Stream to AWS Lambda

f:id:htnosm:20160504231816p:plain

  • 作成した Lambda Function を指定

f:id:htnosm:20160504231809p:plain

  • Filter Pattern を設定

f:id:htnosm:20160504231810p:plain f:id:htnosm:20160504231811p:plain

  • 設定後のロググループ一覧

Subscription に Lambda が表示されます。

f:id:htnosm:20160504231812p:plain

Metric Filter の追加

CloudWatch 側からだけでなく、 Lambda 側からもメトリックフィルタの追加が可能です。

f:id:htnosm:20160504231813p:plain

  • [Lambda] -> [Function] -> [Event source]

f:id:htnosm:20160504231814p:plain

通知例

f:id:htnosm:20160504231815p:plain


CloudWatchLogsにログを転送していることが前提ですが、 EC2インスタンスに手を入れずにログ監視が行えます。
それ程クリティカルでなく、監視エージェントを導入する程では無いのであれば、 実装候補に入りうると思います。

AWS Lambda で CloudWatch Logs のログ本文をSlack通知(1)

CloudWatch Logsを使用したログ監視です。

f:id:htnosm:20160505000213p:plain

CloudWatch Logs のメトリックフィルタから Alarm を作成し、SNS メッセージをSlackへ投稿する Blueprint が提供されていますが、 通知されるメッセージだけでは Alarm が発生した事がわかるのみなので、ログ本文を通知したいと思いました。

f:id:htnosm:20160504231803p:plain

通知先(Slack)以外はAWSで完結可能な方法という所で、Lambda(Python)を使用した2パターンを試します。

f:id:htnosm:20160504231804p:plain

  • 図内黒矢印は Blueprint を使用した場合

メトリクスに過去発生頻度を残したいというのであれば(1)、 通知先固定でエラー発生がわかれば良いレベルなら(2)が使用できるのではないでしょうか。

利用サービスと設定箇所が少ないので、(2)の方がシンプルです。
更に、通知先を複数にしたいならLambdaからSNSへ飛ばしてSubscriptionさせれば実現できると思います。(SlackのEmail Subscriptionはスタンダードプラン以上が必要です。)

(1) SNSを経由してSlack通知

  • CloudWatch [Logs] -> Alarm -> SNS -> Lambda -> Slack
  • AWS提供の Slack Integration Blueprints を元にログ本文を追加

blueprint cloudwatch-alarm-to-slack-python 変更箇所

Blueprints冒頭に記載されている Slack Integration や KMS の設定については割愛します。

ライブラリをインポート

ログイベント取得時の日時指定のため、datetime、calendar ライブラリを追加します。

 import boto3
 import json
 import logging
+import datetime
+import calendar

取得条件

今回の取得条件は以下のようにしています。

  • FILTER_PATTERN
  • OUTPUT_LIMIT
    • 取得行(limit)は最大5行
  • TIME_FROM_MIN
    • Logs取得の開始時刻(timefrom)は CloudWatch Alarm の Period(今回は5分)に合わせる
+FILTER_PATTERN='{ $.log_level = "ALERT" }'
+OUTPUT_LIMIT=5
+TIME_FROM_MIN=5

ENCRYPTED_HOOK_URL = '<kmsEncyptedHookUrl>'  # Enter the base-64 encoded, encrypted key (CiphertextBlob)
SLACK_CHANNEL = '<slackChannel>'  # Enter the Slack channel to send a message to

filter_log_events

CloudWatch Logs からイベントを取得します。
ロググループ(logGroupName) はメトリクス名(MetricsName)と一致している前提にしています。

     reason = message['NewStateReason']
+    metric = message['Trigger']['MetricName']
+
+    timeto = datetime.datetime.strptime(message['StateChangeTime'][:19] ,'%Y-%m-%dT%H:%M:%S') + datetime.timedelta(minutes=1)
+    u_to = calendar.timegm(timeto.utctimetuple()) * 1000
+    timefrom = timeto - datetime.timedelta(minutes=TIME_FROM_MIN)
+    u_from = calendar.timegm(timefrom.utctimetuple()) * 1000
+    client = boto3.client('logs')
+    streams = client.describe_log_streams(logGroupName = metric, orderBy = 'LastEventTime')
+    stream = str(streams['logStreams'][0]['logStreamName'])
+    logger.info("logGroupName = " + str(metric) + ", logStreamNames = [" + str(stream) + "], filterPattern = " + str(FILTER_PATTERN) + ", startTime = " + str(u_from) + ", endTime = " + str(u_to) + ", limit = " + str(OUTPUT_LIMIT))
+    response = client.filter_log_events(logGroupName = metric, logStreamNames = [stream], filterPattern = FILTER_PATTERN, startTime = u_from, endTime = u_to, limit = OUTPUT_LIMIT)
+    log_events = response['events']
+    log_message = '[CloudWatch Log Alarm] ' + str(metric) + ' / ' + str(stream) + '\nLogMessages:'
+    for e in log_events:
+        date = datetime.datetime.fromtimestamp(int(str(e['timestamp'])[:10])) + datetime.timedelta(hours=9)
+        log_message = log_message + '\n{"Timestamp":"' + str(date) + '","Message":' + e['message'] + '}'

Slackメッセージにeventを追加

取得したイベントを通知メッセージに追加します。

     slack_message = {
         'channel': SLACK_CHANNEL,
-        'text': "%s state is now %s: %s" % (alarm_name, new_state, reason)
+        'text': "%s state is now %s: %s \n %s" % (alarm_name, new_state, reason, log_message)
     }

通知例

f:id:htnosm:20160504231805p:plain


次回 パターン(2)に続きます。 尚、Lambda の Code 全文は以下になります。

続きを読む

Mackerel事始め ホストの退役

退役=Mackerel上からの削除です。

f:id:htnosm:20160429183205p:plain

Web画面上での設定方法がパッと出なかったので流れを書いてみます。 何てことは無いのですが一覧から変更できるものと思っていたので少々彷徨いました。

Web画面からの退役設定

Hosts(ホスト一覧)画面で設定できるのは、Working, Standby, Maintenance, Power off の4種類のみです。 退役を表す retired は選択肢にありません。

f:id:htnosm:20160429183206p:plain

退役手順

Hosts → ホスト画面

f:id:htnosm:20160429183207p:plain

ホスト画面 → [設定]

f:id:htnosm:20160429183208p:plain

[このホストを退役させる] → [OK]

f:id:htnosm:20160429183209p:plain

退役後

f:id:htnosm:20160429183210p:plain

退役後の参照方法

Hosts → 退役ホストをみる

f:id:htnosm:20160429183211p:plain

退役ホスト一覧

f:id:htnosm:20160429183212p:plain

退役ホスト一覧上はサービスやロールでの絞り込みには対応していないようです。 絞込を行おうとすると所属しているはずのホストも表示されなくなりました。

f:id:htnosm:20160429183213p:plain