wwwave'sTechblog |ウェイブのエンジニアブログ

株式会社ウェイブのエンジニアによるテックブログです。会社の話や Ruby、Vue.jsについてなど技術的な話をしていきたいと思います。

株式会社ウェイブの人事部ブログです。社内の雰囲気やイベント、福利厚生などについてお伝えいたします!

株式会社ウェイブのエンジニアブログです。 エンジニアの目線から会社の話や技術的な話をしていきます。

Railsで例外発生時、prometheus-client のメトリクスが取得できていない問題の解決方法

執筆者:SRE シラト

今回のお話

海外向けコミック配信サービス「Coolmic」では、Prometheus を使用してシステム監視を行っております。
Prometheus のメトリクスを確認すると、Railsで例外発生時にメトリクスが取得できていないことが判明いたしました。
今回は、その解決方法をご紹介いたします。

状況整理

今回取得したかったメトリクスはhttp_server_requests_totalです。
Kibanaのログと照らし合わせると、数値が合わなかったので気が付きました。。。

原因

Rails は標準で ActionDispatch::ShowExceptions の仕組みで例外を捕捉して、エラーページを表示しております。
ソースコードを確認すると、処理の途中で以下の値を上書きしている箇所を発見。

request.path_info = "/#{status}"
request.request_method = "GET"

実はこの2つの値、prometheus-client で参照している値だったため、期待する値ではなかったです。

path method
参照 path_info request_method
実際の値 /422 get
期待する値 /user/sessions post

解決策

ShowExceptions を無効化することもできるみたいですが、レスポンスにデバッグコードが含まれてしまうため却下しました。
ShowExceptions が値を上書きする前の情報も取得することができましたので、モンキーパッチで処理を上書きしました。

module Prometheus
  module Middleware
    class Collector
      def record(env, code, duration)
        method = if env["action_dispatch.original_request_method"].present?
                   env["action_dispatch.original_request_method"].downcase
                 else
                   env["REQUEST_METHOD"].downcase
                 end

        counter_labels = {
          code: code,
          method: method,
          path: strip_ids_from_path(env["REQUEST_PATH"], method),
        }

        duration_labels = {
          method: method,
          path: strip_ids_from_path(env["REQUEST_PATH"], method),
        }

        @requests.increment(labels: counter_labels)
        @durations.observe(duration, labels: duration_labels)
      rescue StandardError
        # 例外発生時ここでキャッチできるのでデバッグも可能
        nil
      end
    end
  end
end

まとめ

実際のところこの方法が適切だったかはわかりません。。。
調べても情報が出てこなかったので、少しでもお役に立てれば幸いです!

Amazon EventBridgeでバッチ処理のスケジュール管理を改善しました!

ウェイブTechブログとは?
ウェイブの仕事をエンジニア目線でお伝えするWebメディアです。毎月15日の更新を目指しています。肩の力を抜いて執筆し、ありのままのウェイブをお見せできるよう心がけています。

本日の担当:SRE ヤギ
 ・2016年4月入社
 ・ゲーム好きの超インドア派
 ・最近はelden ring

今回のお話

電子コミック配信サービス「ComicFesta」が抱えるバッチ処理スケジュールの管理課題をAmazon EventBridgeを使って改善しました!

画像上が改善前、下が改善後になります。

 

 

課題

ComicFestaではバッチのスケジュール管理をAmazon EC2上にあるComicFestaのシステムとは別のバッチ管理サーバーで設定していました。  

この設定ではバッチ管理サーバーに障害が発生した場合、バッチの実行にも影響が出てしまいます。また、バッチ管理サーバーは別プロジェクトとも関わっているため、依存関係の面でも別の管理方法に移行したいと考えました。

解決方法

ComicFestaはAmazon ECSで稼働しています。  
課題を解決するにあたりマネージドのサービスに移したいと思いECSとの連携を考えてEventBridgeを使うことに決めました。

EventBridgeのドキュメントを見ると「サーバーレスのイベントバスサービス」と書かれています。  
この説明ではイメージが沸かないですが、私はEventBridgeを「指定したルールに一致するイベントをトリガーに、別のサービスに処理をリクエストしてくれるサービス」と理解しています。

指定したルールは、cronにより時間指定も可能なため、スケジュール管理を行うことができます。またマネージドなAWSサービスとしてECSなど他のAWSサービスとの連携も容易で、今回の課題を解決するものとして採用することにしました。

改善内容

StepFunctionの利用

ECSで行っているバッチ処理のタスク定義は1つだけです。この定義のタスクを起動する際に実行コマンドを引数で渡すことで異なるバッチを実行しています。  

初めEventBridgeからECSを呼び出す処理をしたのですが、引数として実行コマンドを上手く渡すことが出来ませんでした(こちらに関しては今振り返ると出来そうな気もするので検証時のやり方が悪かっただけかもしれません)  

また、EventBridgeからは本来cronで設定しているトリガーを手動で実行することができません。バッチ管理サーバーで運用している時は手動でのバッチ実行をする機会があるため代わりの手段が必要でした。  

これらを解決するためにEventBridgeとECSの間にStepFunctionを挟むことにしました。  
これによりECSへの実行コマンドを渡せるようになり、StepFunctionからECSを呼び出すことで手動でのバッチ実行を実現しました。  

スケジュールの登録方法

EC2で管理している時は自作のスクリプトを用意してymlで記載された定義ファイルから一括でバッチのスケジュールを登録していました。EventBridgeに移行後も同様の運用を行いたいと思い、スクリプトを修正してAWS CLI経由で一括登録を行うようにしました。  

例として毎朝9時にバックアップを動かすバッチがあった場合、ymlファイルで下記のように定義してスクリプトからEventBridgeへ反映しています。

Backup:
  cron: "0 9 * * ? *"
  description: バックアップ
  batch_class: "Batches::Daily::Backup"
  is_active: true

また、EventBridgeに登録するcronはUTC時間での登録になります。しかし運用上UTC時間での記載はコードを見た際にわかりにくいため、定義ファイルではJST時間で登録し、スクリプトでUTC時間に置き換えて登録しています。

アラートの検知

バッチのスケジュールではありませんが、今回の対応をきっかけにEventBridgeでECSタスクの異常終了を検知できることを知りました。

バッチのスケジュール登録の時はcronでルールを指定しましたが、EventBridgeでは特定のAWSイベントを検知してトリガーすることも出来ます。そのためバッチのECSタスクが異常終了したイベントを検知してアラート通知を行うよう設定を行いました。  

cloudformationにて設定しているのですが設定コードを一部記載します。

Resources:
  BatchAlertEventsRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern: !Sub |
        {
          "source": [
            "aws.ecs"
          ],
          "detail-type": [
            "ECS Task State Change"
          ],
          "detail": {
            "clusterArn": [
              "arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ECSClusterName}"
            ],
            "group": [
              "family:${ECSTaskFamily}"
            ],
            "containers": {
              "exitCode": [
                {"anything-but": 0},
                {"exists": false}
              ]
            },
            "lastStatus": [
              "STOPPED"
            ]
          }
        }
      State: 'ENABLED'
      Targets:
        - Arn: !Ref BatchAlertSNSTopic
         Id: 'BatchAlert'

 

結果

移行後はサーバレスな環境であり、サーバー管理を気にする必要がなくなりました。SLAの観点からもEC2単体と比べて高いため、安定性が向上しています。

サービス SLA
EC2 99.5%
EventBridge 99.99%
StepFunction 99.9%

EventBridgeへの移行から数ヶ月経過しましたが、特に問題も発生しておらず運用を続けることが出来ています。  
今まではEventBridgeに関してほとんど触ってきませんでしたが、今回の対応を通してスケジュール管理以外にも、他イベントを検知してアラートするなどの活用方法を知ることが出来て良かったです。

 

ウェイブではともにチャレンジしてくれるエンジニアを募集しています。

現在の募集職種はこちらをご覧ください。

recruit.wwwave.jp

トップに戻る