Shogo's Blog

Apr 17, 2019 - 3 minute read - Comments - go golang aws cloudformation mackerelio

CloudFormationのMackerel用インテグレーションを作ってる話

Mackerelmkr コマンドを用いて cli から操作ができます。 mkr コマンドを用いると 監視ルールを GitHub で管理 したり、 カスタムダッシュボードを管理したり、といったことができます。 しかし、個人的に以下のような不満があります。

  • サービス、ロール、ホスト、新ダッシュボード等々、監視設定以外のリソースに対応していない
    • 旧ダッシュボードは対応しているんだけど、新ダッシュボード対応がまだ
    • 新ダッシュボードのUIは使いやすくてすごくいいんだけど、コピペや一斉置換ができないので、テキストで管理したい
  • 出力がJSONなのつらい
    • JSON手で書くの難しくないですか?
  • メトリックスの送信設定と監視設定の管理が別になってしまう
    • カスタムメトリックス送っているのに監視設定を忘れた、みたいなことが起こる

メトリックスの送信設定については、以前 サーバーレスでCloudWatchメトリクスをMackerelに転送する で CloudFormation上での管理を実現しました。 ここにさらに Mackerel の監視設定を追加できれば、最強なのでは?とやってみました。

あれこれ説明する前に例を見てもらったほうがわかりやすいと思うので、こんなことができますよ、という設定例から。

例1: レスポンスタイムの99%パーセンタイルを監視する

Mackerel の AWSインテグレーション は ALB に対応していますが、 レスポンスタイムのメトリックスは平均レスポンスタイムだけです。 「平均」は代表的な統計値ですが、全体としては速いんだけど一部のリクエストだけ遅い、という状況を見逃してしまいます。 レスポンスタイムの大まかな分布をパーセンタイルで把握したい、ということはよくありますよね? (K社でZabbixを使って監視していたときによくお世話になった)

今回作ったインテグレーションを使えば、以下のように「Mackerelのサービス定義」「メトリックスの転送設定」「監視設定」が CloudFormation のテンプレートとして表現できます。

AWSTemplateFormatVersion: 2010-09-09

# Type: Mackerel::* を使うためのおまじない
Transform:
  - AWS::Serverless-2016-10-31
  - Mackerel
  - JSONString

Resources:
  MackerelService:
    Type: Mackerel::Service
    Properties:
      Name: "awesome-service"

  # メトリックスを転送する Lambda 関数
  MetricsForwarder:
    Type: AWS::Serverless::Application
    Properties:
      Location:
        ApplicationId: arn:aws:serverlessrepo:us-east-1:445285296882:applications/mackerel-cloudwatch-forwarder
        SemanticVersion: 0.0.9
      Parameters:
        ParameterName: "/api-keys/api.mackerelio.com/headers/X-Api-Key"
        ForwardSettings: !GetAtt MetricsForwarderSettings.Query

  # CloudWatch から99%パーセンタイルを取得する
  MetricsForwarderSettings:
    Type: JSON::String
    Properties:
      Query:
        - service: !GetAtt MackerelService.Name
          name: request_percentile.p99
          metric:
            - AWS/ApplicationELB
            - TargetResponseTime
            - LoadBalancer
            - !GetAtt LoadBalancer.FullName
            - TargetGroup
            - !GetAtt TargetGroup.TargetGroupFullName
          stat: p99

  MonitorP99:
    Type: Mackerel::Monitor
    Properties:
      Type: service
      Name: request_percentile.p99
      Duration: 1
      Service: !Ref MackerelService
      Metric: request_percentile.p99
      Operator: ">"
      Warning: 3 # 3秒を超えたらWarning
      Critical: 10 # 10秒を超えたらCritical
      MaxCheckAttempts: 3

例2: ログに吐かれたエラーを監視する

check-aws-cloudwatch-logs を使うと、 CloudWatch Logs に吐かれたログを検索して、エラーを見つけたらアラートを飛ばすということができます。

しかし、check pluginを使うには mackerel-agent をインストールするサーバーが必要です。 運用が大変になるので管理するサーバーはあまり増やしたくありません。

いろいろと調べた結果、 CloudWatch Logs のメトリックスフィルターが使えそうです。 CloudFormation のテンプレートは以下のようになります。

AWSTemplateFormatVersion: 2010-09-09

# Type: Mackerel::* を使うためのおまじない
Transform:
  - AWS::Serverless-2016-10-31
  - Mackerel
  - JSONString

Resources:
  MackerelService:
    Type: Mackerel::Service
    Properties:
      Name: "awesome-service"

  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: awesome-service-log

  # ログの検索結果をメトリックスとして保存し、傾向をモニタリングできる
  # https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/Counting404Responses.html
  MetricFilter:
    Type: AWS::Logs::MetricFilter
    Properties:
      # JSON形式のログに対応しているので、エラーのみをフィルタリング
      FilterPattern: '{ $.level = "error" }'
      LogGroupName: !Ref LogGroup
      MetricTransformations:
        - DefaultValue: 0
          MetricName: error_count
          MetricNamespace: awesome-service
          MetricValue: 1

  # メトリックスを転送する Lambda 関数
  MetricsForwarder:
    Type: AWS::Serverless::Application
    Properties:
      Location:
        ApplicationId: arn:aws:serverlessrepo:us-east-1:445285296882:applications/mackerel-cloudwatch-forwarder
        SemanticVersion: 0.0.9
      Parameters:
        ParameterName: "/api-keys/api.mackerelio.com/headers/X-Api-Key"
        ForwardSettings: !GetAtt MetricsForwarderSettings.Query

  # エラー発生数を Mackerel に転送する設定
  MetricsForwarderSettings:
    Type: JSON::String
    Properties:
      Query:
        - service: !GetAtt MackerelService.Name
          name: error_count
          metric:
            - awesome-service
            - error_count
          stat: Sum

  # 監視設定
  MonitorError:
    Type: Mackerel::Monitor
    Properties:
      Type: service
      Name: error_count
      Duration: 1
      Service: !Ref MackerelService
      Metric: error_count
      Operator: ">"
      Warning: 0 # 1件でもエラーが発生したらWarning
      Critical: 10 # 10件でCritical
      MaxCheckAttempts: 3

仕組み

CloudFormation マクロとトランスフォーム

CloudFormation マクロはテンプレートを実際に反映する前に、テンプレートの中身を書き換えることのできる機能です。 CloufFormationのテンプレートはどうしても長くなってしまいがちですが、 マクロを使うことでショートハンドを自作することができます。

AWS Serverless Application Model (SAM) も、実態はAWSが提供するCloudFormationマクロです。 テンプレートの Transform セクションに指定した AWS::Serverless-2016-10-31 マクロが SAMのショートハンドを展開し、最終的に通常の CloudFormation テンプレートへと変換します。

マクロは Lambda 関数で自作ができるので、好きな言語を使ってテンプレートを自由に書き換えることができます。 GitHubで公開されている MacrosExamples を見てもらうとわかりますが、 ショートハンドを定義するのはもちろん、 テンプレート内でPythonを実行できるようにしたりと、 入出力の形式さえ守っていれば何でもできます。 何でも出来すぎて怖いですね。

上に挙げた例では Type: Mackerel::Monitor が指定されたリソースをカスタムリソースに書き換えることで、 他のAWSリソースと同じような記法で Mackerel のリソースを定義できるようにしています。 (MacrosExamplesに比べると、非常におとなしい書き換えに見えますね)

CloudFormation カスタムリソース

CloudFormation カスタムリソースは、リソースの「作成」「更新」「削除」の処理をユーザーが定義することで、 CloudFormation 自体が対応していないリソースであっても、CloudFormation上で扱うことのできる機能です。 リソースの操作には Lambda 関数を使用できるので、 Mackerel のような外部サービスであっても、 「作成」「更新」「削除」の処理さえ実行できればOKです。

嬉しいことに Go の Lambda ライブラリには、カスタムリソースを扱うためパッケージが含まれるので、 カスタムリソースを提供する Lambda 関数を簡単に作れます。

また、作成したリソースの属性を Fn:GetAtt 組み込み関数を利用して参照可能できます。 さらにリソースIDさえテキトウに発行すれば、実リソースを持たず、CloudFormation上で属性だけを持つリソースを作成可能です。 このことを利用して JSON::String カスタムリソースは、実リソースを作成せず、 Properties をJSONでエンコードした文字列を属性として返します。 これにより YAML の中に JSON 書く苦行から開放されました。

使い方

ソースコードとテンプレートはすべて GitHub に公開しています。

マクロを利用するには、自分の AWS アカウントにマクロをインストールする必要があります。 S3にテンプレートとビルド済みバイナリを上げておいたので、インストールは awscli でいっぱつです。

aws cloudformation create-stack \
    --template-url https://s3-ap-northeast-1.amazonaws.com/shogo82148-cloudformation-template-ap-northeast-1/cfn-mackerel-macro/latest.yaml \
    --stack-name cfn-mackerel-macro \
    --capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND

aws cloudformation create-stack \
    --template-url https://s3-ap-northeast-1.amazonaws.com/shogo82148-cloudformation-template-ap-northeast-1/cfn-json-string-macro/latest.yaml \
    --stack-name json-macro \
    --capabilities CAPABILITY_IAM CAPABILITY_AUTO_EXPAND

あとはマクロを使うための「おまじない」をテンプレートに追加すればOKです。

Transform:
  - AWS::Serverless-2016-10-31
  - Mackerel
  - JSONString

プロパティー名は Mackerel API ドキュメント(v0) を参考にしてください。 とはいえ、ドキュメントがまったくないのは不便なので、徐々に整備していこうと思います・・・。

まとめ

CloudFormation の「マクロ」「トランスフォーム」「カスタムリソース」を利用して、 Mackerel の構成管理に挑戦しているお話を紹介しました。

CloudFormation 意外と遊べて便利ですね()

参考