CubicLouve

Spring_MTの技術ブログ

Cloud Traceを使ってGKE上で動いているモノリシックなRailsアプリのパフォーマンスを分析する

Cloud TraceはGCPが提供するマネージドな分散トレースシステムです。

cloud.google.com

Cloud Traceはマイクロサービスのような分散アーキテクチャで便利に使えるものですが、もちろんモノリシックなアプリケーションでも十分に使う価値があるサービスです。

どのように設定しているか自分の例も交えて紹介できればと思います。

GKEの設定

私達のアプリではWorkload Identityを利用して、KubernetesサービスアカウントとGCPサービスアカウントを紐付けています。

cloud.google.com

Railsアプリを動かすためのサービスアカウントに Cloud Trace エージェントのroleを含めてください。

cloud.google.com

Railsの設定

gemの追加

google-cloud-trace または stackdriver をinstallしてください。

github.com

github.com

これらのgemのインストールにはgrpc gemをビルドできる環境が必要です。

glibcが入っているOSであれば問題ないですが、alpineを使っているとビルドできない可能性があるのでご注意ください。

google-cloud-trace gemを使った場合は config/application.rbrequire "google/cloud/trace/rails" をしてください。

github.com

stackdriver gemを入れると、google-cloud-trace gemも合わせてインストールされます。

また、stackdriver gemは、require "google/cloud/trace/rails" のrequireが自動で行います。(RailsBundler.require の部分を削除していない限り)

便利なので自分は stackdriver gemを使っています。

RailsでのCloud Traceの設定

RAILS_ENVが production の場合であれば、gemをinstallさえすればもCloud Traceにデータが送信されます。

github.com

止める場合は下記でできます。

Google::Cloud.configure.use_trace = false

or

# config/environments/production.rbとかで
config.google_cloud.use_trace = false

google-cloud-trace はActive SupportのInstrumentationと連携したトレーススパンを出してくれます。

railsguides.jp

デフォルトだと、下記5つが設定されています。

"sql.active_record",
"render_template.action_view",
"send_file.action_controller",
"send_data.action_controller",
"deliver.action_mailer"

github.com

なので、GKE上でRailsアプリを普通に使っていれば、stackdriver gemを入れるだけで本番環境でCloud Traceにデータがでます!

自分の例

デフォルトの設定だけでは不足しているので、いくつかnotificationを追加しています。

config/environments/production.rb にこんな感じで設定しています。

  notifications = [
    "cache_read.active_support",
    "cache_generate.active_support",
    "cache_fetch_hit.active_support",
    "cache_write.active_support",
    "cache_delete.active_support",
    "cache_exist?.active_support",
    "request.faraday",
  ]
  # https://googleapis.dev/ruby/stackdriver/latest/file.INSTRUMENTATION_CONFIGURATION.html
  config.google_cloud.trace.notifications = Google::Cloud::Trace::Railtie::DEFAULT_NOTIFICATIONS + notifications

カスタムスパンの設定

RailsのInstrumentationだけではなくて、自分のアプリ独自のスパンを設定することもできます。

スパンを取得したいところを Google::Cloud::Trace.in_span のブロックで囲むだけで設定できます。

Google::Cloud::Trace.in_span "my_task" do |_span|
  # なんらかの処理

  Google::Cloud::Trace.in_span "my_subtask" do |_subspan|
      # ネストされたspanにも対応
  end
end

実際の例

検証環境での例ですが、こんな感じです。

f:id:Spring_MT:20200709105626p:plain

f:id:Spring_MT:20200709110622p:plain

f:id:Spring_MT:20200709110633p:plain

こんな感じでspanを出しています。

調査やパフォーマンスの把握はしやすくなり、開発効率の向上の一助になること間違いなしです。

ぜひ使ってみてください。

GKE上で動いているRailsアプリでアクセスログとアプリケーションログをCloud Loggingでグルーピングする

medium.com

こちらに書いてある内容をRailsで実現するためにやったことを書きます。

StackdriverはOperation Suiteと名前が変わっており、それに伴ってStackdriver loggingも Cloud Logging と名前を変えています。

GKEで動かしているアプリケーションのログ

GKE上で動かしているアプリケーションであれば、標準出力、標準エラー出力にログを出力すれば、Cloud Loggingに送ることができます。

送るための設定方法は下記を参照ください。

cloud.google.com

Cloud Loggingで構造化ログにするためには、ログはJSONオブジェクトとして出力する必要があります。

また、上記の GAE のログに憧れて にあるように、ログのグルーピングの要件を満たすために、下記の対応を行います。

Railsアクセスログ

Railsアクセスログを出す場合は、Railsから出すのではなくrackから出したほうがよいです。

というわけで、アクセスログの出力に関してはrackのmiddlewareとして実装し、Railsに導入します。

rack middlewareの実装

rack middlewareの実装はこんな感じになっています。実際の実装とは少々異なりますが、雰囲気を掴んでいただければ。

gist.github.com

重要なポイント

"logging.googleapis.com/trace": env["action_dispatch.request_id"] || "",

アクセスログとアプリケーションログをグルーピングするために、同一のリクエストであることを示すリクエスト毎にユニークな識別子を使ってください。

上記の要件を満たせれば、action_dispatch.request_id を使う必然性はありません。

httpRequest: {
}

parentとなるアクセスログはHTTPのアクセスログである必要があります。

下記ドキュメントを参考に、httpRequest の内容を埋める必要があります。

cloud.google.com

 severity: 

グルーピングには severityの設定は必須ではないのですが、Cloud Loggingでfilterするときに役に立つので設定しておきましょう。

Railsからの呼び出し方

これをRailsのinitializerで呼び出すようにします。

アクセスログ標準エラー出力に出します。

# config/initializers/access_log.rb こんなファイルを用意して下記のようにする
Rails.configuration.middleware.insert_before(0, Rack::GkeJsonStructuredLogs, $stderr)

参照したライブラリ

github.com

これでRailsアクセスログをCloud Logging用にだすことができました。

Railのアプリケーションログ

RailsのアプリケーションログをCloud Loggingの形式に合わせるために専用のlog formatterを作っています。

gist.github.com

重要なポイント

params["logging.googleapis.com/trace"] = params[:request_id]

必ず アクセスログlogging.googleapis.com/trace と同じ内容をセットしてください。

呼び出し方

Loggerのformatterをセットします。

ログは標準出力に出します。

# config/environments/production.rb とかに書く
logger = ActiveSupport::Logger.new($stdout)
logger.formatter = Rrp::Logger::StackdriverFormatter.global_formatter                                                           
config.logger = ActiveSupport::TaggedLogging.new(logger)

request_idはリクエスト毎に変わるので、それもうまくセットできるようにします。

# app/controllers/application_controller.rbとかに書く
  prepend_before_action :set_params_for_logger
  # メモリーリークしそう。。。
  private def set_params_for_logger
    params_proc = proc do
      {
        request_id: request.env["action_dispatch.request_id"] || "-"
      }
    end
    Rrp::Logger::StackdriverFormatter.global_formatter.params_proc = params_proc
  end

これで準備が完了です。

結果どういう感じにでるのか?

検証環境での例は下記の感じです。

f:id:Spring_MT:20200708100408p:plain
CloudLoggin

検証環境ではdebugログも出しているためかなりログの量が出ていますが、アクセスログに紐付いてアプリケーションログもでています!

まとめ

グルーピングすることによって、まずはアクセスログに出ているユーザーIDやsevirityなどから絞り込んでさらにそこで何が起こったかをかなり追いやすくなりました。

これによって、調査タスクの効率が上がっていることは実感としてあります。

ぜひ試してみてください。

2020/07/06 気になった記事メモ

1

y-anz-m.blogspot.com

developer.android.com

く、わかるようでわからん。

2

medium.com

大分サボってたけど、これやってみるか

3

asnokaze.hatenablog.com

UUIDv6では、特殊な操作なく単純なソートで時系列順にでき、利用時にアクセスの局所性を持つ様になっている。(ULIDと似ている)

時系列順にソートできるようなバージョンらしいが、draftの議論が止まっているように見える。。。

github.com

今のプロジェクトはULIDを使っている。

2020/07/03 気になった記事メモ

1

mrasu.hatenablog.jp

Zanzibarはデータを非正規化をせずに(Leopardは例外)、代わりにキャッシュを使って対処しています。 なるほど

2

speakerdeck.com

qiita.com

ワークフロー定義はどうしようかな、、Composerつらそう

2020/07/01 気になった記事メモ

1

tech.mercari.com

環境整備の話もそうなんですが、tput とか使ったことなかった。。。

2

blog.jxck.io

3rdPCが死んでいく過渡期にいるから、対応が大変だ

3

logmi.jp

運用でカバー、とりあえず に関してはbotでも作ろうかなw