vague memory

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

Mackerel Webhook で Lambda 起動

f:id:htnosm:20170226021308p:plain

Mackerel のアップデートで Webhook の API Gateway 対応が発表されました。

Webhook のリクエスト先に API Gateway のエンドポイントを指定できるようになりました 通知先として選択できる Webhook の対応証明書が拡張され、Webhookの対応先が増えました。 これにより多くの要望を頂いておりました Amazon API Gateway にも対応しました。 AWS Lambda を Amazon API Gateway 経由で呼び出すことも可能です。

アップデート前は通知先に直接 API Gateway の指定は行えていなかったようです。(アップデート前に検証はしてませんでした)
個人的に色々 Webhook からの Lambda 起動を検証していた際のタイムリーなアップデートだったので、Mackerelでも実施してみました。

Mackerel Webhook 設定

Monitors → チャンネル設定 から Webhook を作成します。URLに API GatewayInvoke URL を指定します。

f:id:htnosm:20170226021309p:plain

実行例

Mackerel から Webhook のテスト通知を送ると Lambda Function の起動を確認できます。

  • Lambda Function Log

f:id:htnosm:20170226021310p:plain

実行元の制限

API Gateway を指定できるようになったとは言え、ヘッダー付与等は行えないようなので API Key による制限はまだ行えません。

CloudFront 経由で AWS WAF での IP アドレス制限を考えましたが、AWS WAF で IPアドレス制限を書ける場合は設定できるIPアドレスレンジが限定されており、少々面倒だなと思いました。

Mackerelからの通知のリクエスト元IPアドレスレンジは、 59.106.108.64/26 です

AWS WAF supports /8, /16, /24, and /32 IPv4 address ranges

API Gateway の $context 変数

API Gateway で sourceIp が使用できるので、 Lambda Function 側で制限を掛けてみます。

API Gatewayマッピングテンプレート追加

Method Execution → Integration Request → Body Mapping Template に Context-Type application/json を追加します。 今回は、用意されているテンプレート Method Request passthrough をそのまま使用します。

f:id:htnosm:20170226021311p:plain

実行例

SourceIp が渡されていることが確認できます。

  • Lambda Function Log

f:id:htnosm:20170226021312p:plain

Lambda Function に判定処理を追加

IPアドレス判定に使えそうなライブラリとして、 Python のバージョン 3.3 からは ipaddress というライブラリがあります。 残念ながら、 Lambda の Python は 2.7 ということで使えません。
Google先生検索結果から、以下参考URLのソースをそのまま使わせてもらいました。

Lambda Function の処理先頭でMackerel のリクエスト元IPアドレスレンジ以外は弾くようにして、制限を掛ける事ができました。

    ### Check Source IpAddress
    source_ip = str(event['context']['source-ip'])
    if not is_in_subnet(source_ip, "59.106.108.64/26"):
        return {'message': 'None'}
    ### Check Source IpAddress
  • 適当な場所から API を叩いた場合
$ curl -H "Content-Type: application/json" -X POST "https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/webhook" -d "{\"hoge\":\"fuga\"}"
{"message": "None"}

Mackerel 側で Webhook に x-api-key を追加できるようになると良いですね。

Datadog Monitor から Webhook で Lambda 起動

f:id:htnosm:20170219061829p:plain

前回 Datadog から SNS 経由で Lambda Function を起動を行いました。

別の方法を探してみた所、Webhook Integration が使えそうだったので試してみました。

1) API Gateway と Lambda Function を作成

API Gateway を trigger として Lambda Function を作成します。APIへは AccessKey認証 とします。

f:id:htnosm:20170219061830p:plain f:id:htnosm:20170220065001p:plain

今回使用した Lambda Function は、受け取った内容をそのまま Slack へポストするだけの物です。

2) CloudFront Distribution 作成

API Gateway の Endpoint をそのまま叩けば良いかと考えたのですが、Datadog の Webhook が SSLv3 を使用しているようで以下のようなエラーが発生します。

f:id:htnosm:20170219061832p:plain

Datadog 側での設定変更は無いようだったので、CloudFront を挟む事で回避させます。

Origin Domain Name へ API GatewayInvoke URL を入力します。 (Origin Path、Origin ID は自動的に入力されます。)

f:id:htnosm:20170219061833p:plain

また、AccessKey認証のため WhiteList Headers に x-api-key を設定します。

f:id:htnosm:20170219061834p:plain

3) Datadog Webhook Integration 設定

Integration → Webhooks → Configuration に CloudFrontのエンドポイントと API GatewayAPI Key を設定します。

f:id:htnosm:20170219061836p:plain

正常に登録できていれば、Monitor の Nortify 設定で Webhook: 〜 が選択できるようになります。

f:id:htnosm:20170219061835p:plain

実行例

Datadogのアラート発行をトリガーに、Lambda Function が起動した事を確認できます。

  • Lambda Function Log

f:id:htnosm:20170219061837p:plain

  • Slack

f:id:htnosm:20170219061838p:plain


CloudFront を介す必要がありますが、 メッセージのカスタマイズもある程度可能なため、 SNSでの通知より柔軟に処理設定できるのではないでしょうか。

Datadog MonitorからSNS経由でLambda起動

Datadog から SNS(Amazon Simple Notification Service) への通知ができるということで、 Datadog → SNS → Lambda → Slack で通知情報に+αで情報付与をしてみます。

f:id:htnosm:20170218155648p:plain

題材

Lambda で実行可能な物でしたら何でも良いのですが、常々不便を感じていた CloudFront のエラー通知で試してみました。

CloudFront で ErrorRate 上昇時に Datadog から Slack へ通知します。 これは Datadog の通常の機能(Metric Monitor)で実現できます。ですが、CloudFront の Tag は取得していないようで通知メッセージには DistributionId しか出力されません。

f:id:htnosm:20170218155640p:plain

DistributionId で判別するのは中々敷居が高いので CNAME と OriginName を後追いで付与します。

1) Monitor 通知先の作成

通知先として使用する Slack と SNS の設定です。
Datadog の Integration ページの Configuration 記載内容に沿って各種設定を行います。

1-1) Slack

Incoming WebHook を作成します。

Datadog 側への設定に Webhook URL が必要になりますので、Slack の Configure Apps に追加します。

f:id:htnosm:20170218155641p:plain f:id:htnosm:20170218155642p:plain

1-2) SNS

使用する SNS Topic を作成します。

例では dd-alert という名前で作成しました。

f:id:htnosm:20170218155643p:plain

通知先設定の確認

正常に登録できていれば Datadog の Monitor 作成画面で選択可能になります。

f:id:htnosm:20170218155644p:plain

2) Datadog Monitor の作成

Lambdaの関数を起動するトリガーとする Monitor を作成します。
例では 5xx Error Rate のメトリクスを使用しました。 尚、5xx Error Rate 等の未発生のメトリクスは Edit で選択できないので、初回は直接 Source に書く必要があるかもしれません。

通知先として作成した Slack Incoming WebHook と SNS topic の2つを指定します。

f:id:htnosm:20170218155645p:plain

avg(last_15m):max:aws.cloudfront.5xx_error_rate{*} by {distributionid} > 10
5xx Error Rate over {{#is_alert}}{{threshold}}%{{/is_alert}}{{#is_warning}}{{warn_threshold}}%{{/is_warning}}
{{^is_recovery}} @slack-notice @sns-dd-alert {{/is_recovery}}

3) Lamdba Function の作成

アラート通知が発生した際に実行したい関数を作成します。 Slack通知がメインとなるため cloudwatch-alarm-to-slack-python をベースに作成しました。 IAM role へは CloudFrontReadOnlyAccess を付与しています。

f:id:htnosm:20170218155646p:plain

Environment variables に作成した Slack Incoming WebHook と SNS topic を設定します。

f:id:htnosm:20170218155647p:plain

例ではSNSを受け取り、通知された DistributionId を基に CNAME と OriginDomainName を取得します。

実行例

アラート発生した場合に通知される内容の例です。 タイミングにより通知される順序は前後します。

f:id:htnosm:20170218165648p:plain

f:id:htnosm:20170218165647p:plain


今回題材にした CloudFront の名称取得はその内 Datadog 側で対応してくれそうですが、
アラート受けてから別に確認していた情報を付与、実行していた処理を自動実行させる等、 色々と応用が効きそうです。

RDS for PostgreSQLへ CF/ELB/ALB/S3のアクセスログをそのままインポート(したかった)

f:id:htnosm:20170201001606p:plain

S3に出力された CloudFront/ELB/ALB/S3 アクセスログを特に加工せずにRDS PostgreSQLへインポート。。。したかったのですができませんでした。 (極力簡単にインポートし、整形・集計はDB側に任せようと思いました。)

PostgreSQL ベースである Redshift と RDS(PostgreSQL) の違いを確認しました。

\copy コマンドでインポート

\copy コマンドを使用して PostgreSQL DB インスタンス上のテーブルにデータをインポートする

ローカルの実行環境にtsvファイル(未圧縮・要変換)を置いた状態で、psqlから “\copy” を実行します。

# CloudFront
\copy cf_log FROM '〜.tsv'
# ELB
\copy elb_log FROM '〜.tsv'
# ALB
\copy alb_log FROM '〜.tsv'
# S3
\copy s3_log FROM '〜.tsv'
# シェル上から実行する場合
psql -h <host> -p <port> -U <user> -d <db> -c "\copy ..."

Redshift での \copy

syntax errorとなり実行できませんでした。

# \copy cf_log from '〜.tsv'
ERROR:  syntax error at or near "STDIN"
行 1: COPY  cf_log FROM STDIN

Redshift で S3以外からの COPY

マニフェストファイル作成等の事前準備をした上で、S3以外のファイルを対象に COPY が行えるようです。

COPYコマンド

COPYコマンドでS3からのインポートは不可です。そもそもCOPYコマンド自体実行できません。 (\copy を使いなさいとヒントをくれます)

ERROR:  must be superuser to COPY to or from a file
HINT:  Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone.

COPYはサーバ側、\copyはクライアント側依存になるので RDS では使えないということですね。


Redshift の COPY コマンドはAWS向けに拡張されていますが、RDSでも同様に使えるようになると混乱せずに済むのかなと思いました。

Redshiftへ ELB/ALB/S3のアクセスログをそのままインポート(したかった)

f:id:htnosm:20170201001605p:plain

S3に出力された ELB/ALB/S3 アクセスログを特に加工せずにRedshiftへインポート。。。したかったのですができませんでした。 (極力簡単にインポートし、整形・集計はDB側に任せようと思いました。)

スペース区切りで且つクォートが必要最低限しか付いていないという、Apacheアクセスログに似た形式のため加工が必要でした。 それぞれ書式が微妙に異なりますが、tsv等ログ内で使用されていない区切り文字に変換してしまえばインポート可能です。

アクセスログ形式

ELB

スペース区切り、未圧縮ファイル、ヘッダ無しです。 Request、UserAgent等はダブルクォートで囲まれた状態になります。

ALB

スペース区切り、圧縮(gzip)、ヘッダ無しです。 Request、UserAgent等はダブルクォートで囲まれた状態になります。

S3

スペース区切り、未圧縮ファイル、ヘッダ無しです。 Request、UserAgent等はダブルクォートで囲まれた状態になります。 また、タイムスタンプ部分は角括弧([])で囲まれています。

テーブル作成

各カラムは記号と予約語回避のため _ を使用しています。

ELB

CREATE TABLE elb_log (
 timestamp_    VARCHAR(4096)
,elb_    VARCHAR(4096)
,client_port_    VARCHAR(4096)
,backend_port_    VARCHAR(4096)
,request_processing_time_    VARCHAR(4096)
,backend_processing_time_    VARCHAR(4096)
,response_processing_time_    VARCHAR(4096)
,elb_status_code_    VARCHAR(4096)
,backend_status_code_    VARCHAR(4096)
,received_bytes_    VARCHAR(4096)
,sent_bytes_    VARCHAR(4096)
,request_    VARCHAR(4096)
,user_agent_    VARCHAR(4096)
,ssl_cipher_    VARCHAR(4096)
,ssl_protocol_    VARCHAR(4096)
);

ALB

CREATE TABLE alb_log (
 type_    VARCHAR(4096)
,timestamp_    VARCHAR(4096)
,elb_    VARCHAR(4096)
,client_port_    VARCHAR(4096)
,target_port_    VARCHAR(4096)
,request_processing_time_    VARCHAR(4096)
,target_processing_time_    VARCHAR(4096)
,response_processing_time_    VARCHAR(4096)
,elb_status_code_    VARCHAR(4096)
,target_status_code_    VARCHAR(4096)
,received_bytes_    VARCHAR(4096)
,sent_bytes_    VARCHAR(4096)
,request_    VARCHAR(4096)
,user_agent_    VARCHAR(4096)
,ssl_cipher_    VARCHAR(4096)
,ssl_protocol_    VARCHAR(4096)
,target_group_arn_    VARCHAR(4096)
,trace_id_    VARCHAR(4096)
);

S3

CREATE TABLE s3_log (
 bucket_owner_    VARCHAR(4096)
,bucket_    VARCHAR(4096)
,time_    VARCHAR(4096)
,remote_ip_    VARCHAR(4096)
,requester_    VARCHAR(4096)
,request_id_    VARCHAR(4096)
,operation_    VARCHAR(4096)
,key_    VARCHAR(4096)
,request_uri_    VARCHAR(4096)
,http_status_    VARCHAR(4096)
,error_code_    VARCHAR(4096)
,bytes_sent_    VARCHAR(4096)
,object_size_    VARCHAR(4096)
,total_time_    VARCHAR(4096)
,turn_around_time_    VARCHAR(4096)
,referrer_    VARCHAR(4096)
,user_agent_    VARCHAR(4096)
,version_id_    VARCHAR(4096)
);

インポート

スペース区切りをTAB区切りへ変換したファイルをgzip圧縮し、S3へ格納後にCOPYコマンドで取り込みます。

COPY { elb_log | alb_log | s3_log }
FROM 's3://bucket/key/〜.tsv.gz'
CREDENTIALS 'aws-auth-args'
GZIP
FORMAT CSV
DELIMITER '\t'

変換は様々な言語でツール化されていたりしますが、 今回は gawk で行いました。

Redshiftへ CloudFrontのアクセスログをそのままインポート

f:id:htnosm:20170201001604p:plain

S3に出力されたCloudFrontアクセスログを特に加工せずにRedshiftへインポートします。 (極力簡単にインポートし、整形・集計はDB側に任せようと思いました。)

S3->RedshiftへCOPYコマンドで実現可能です。

ログ形式

CloudFront が Amazon S3 バケットに保存する各ログファイルの名前には、次のファイル名形式が使用されます。

bucket-name.s3.amazonaws.com/optional-prefix/distribution-ID.YYYY-MM-DD-HH.unique-ID.gz

圧縮(gzip)されたtsvファイルで、ヘッダが2行存在します。

  • ヘッダ
#Version: 1.0
#Fields: date time x-edge-location sc-bytes c-ip cs-method cs(Host) cs-uri-stem sc-status cs(Referer) cs(User-Agent) cs-uri-query cs(Cookie) x-edge-result-type x-edge-request-id x-host-header cs-protocol cs-bytes time-taken x-forwarded-for ssl-protocol ssl-cipher x-edge-response-result-type cs-protocol-version

テーブル作成

各カラムは記号と予約語回避のため _ を使用しています。

CREATE TABLE cf_log (
 date_    VARCHAR(4096)
,time_    VARCHAR(4096)
,x_edge_location_    VARCHAR(4096)
,sc_bytes_    VARCHAR(4096)
,c_ip_    VARCHAR(4096)
,cs_method_    VARCHAR(4096)
,cs_Host_    VARCHAR(4096)
,cs_uri_stem_    VARCHAR(4096)
,sc_status_    VARCHAR(4096)
,cs_Referer_    VARCHAR(4096)
,cs_User_Agent_    VARCHAR(4096)
,cs_uri_query_    VARCHAR(4096)
,cs_Cookie_    VARCHAR(4096)
,x_edge_result_type_    VARCHAR(4096)
,x_edge_request_id_    VARCHAR(4096)
,x_host_header_    VARCHAR(4096)
,cs_protocol_    VARCHAR(4096)
,cs_bytes_    VARCHAR(4096)
,time_taken_    VARCHAR(4096)
,x_forwarded_for_    VARCHAR(4096)
,ssl_protocol_    VARCHAR(4096)
,ssl_cipher_    VARCHAR(4096)
,x_edge_response_result_type_    VARCHAR(4096)
,cs_protocol_version_    VARCHAR(4096)
);

インポート(COPY)

gzip、TAB区切り、ヘッダ2行を指定して実行します。

COPY cf_log
FROM 's3://bucket-name.s3.amazonaws.com/optional-prefix/distribution-ID.YYYY-MM-DD-HH.unique-ID.gz'
CREDENTIALS 'aws-auth-args'
DELIMITER '\t'
GZIP
IGNOREHEADER 2

aws-auth-args’ はIAMロールやアクセスキーを指定します。

認証情報 - Amazon Redshift

ロールベースのアクセスコントロールを指定するには、次の形式で aws-auth-args 文字列を指定します。
aws_iam_role=arn:aws:iam::<aws-account-id>:role/<role-name>’
キーに基づくアクセスコントロールを指定するには、次の形式で aws-auth-args を指定します。
aws_access_key_id=<access-key-id>;aws_secret_access_key=<secret-access-key>’

Mackerel事始め check-plugin ログ監視 仕様変更の影響

2017/02/24 追記

再度仕様変更が入ったようなので追記します。
以下の通り修正されたようです。そのため本記事の内容は一時的な変更となりました。

2017-01-27の告知ブログでお知らせしました通り、アラートの通知先が Slack やメールのときに、その件名が一定以上の長さになる場合は128文字で切り詰める対応を実施していました。 こちらの対象をメール通知の件名のみに変更し、 Slack などは元の仕様どおり、すべて表示されるように変更しています。

追記終わり。


通知メッセージの省略

実際はログ監視そのものではなく通知の仕様変更です。
元々ある程度(1,000 bytes位?)で文字数制限はあったようですが、 128文字制限 となりました。

通知の件名が一定以上の長さになる場合、省略するようにしました

Slack への通知メインで使用していたので、個人的にこの変更は少々悲しい結果となってます。

びふぉー

f:id:htnosm:20170129053050p:plain

あふたー

f:id:htnosm:20170129053051p:plain

変更前は通知内容(Slack)で内容を把握できたのですが、詳細を確認するため LINK を踏むという一手間が増えてしまいました。

メール通知

メール通知は使用していなかったので通知してみると、returnオプションで返されるログ部分も含めて件名に設定されています。

f:id:htnosm:20170129053052p:plain

この仕様ですと確かに省略したくなりますね。 メール件名のみログ本文内容を削るという変更だと良かったのですが。。。残念です。