MoTLab -GO Inc. Engineering Blog-MoTLab -GO Inc. Engineering Blog-

LGTM!オブザーバビリティ基盤第1話

SREAWSGCP
September 12, 2023

こんにちは、SREグループのカンタンです!

GO株式会社にある様々なサービスは、SREが提供しているKubernetes基盤上で動いています。ログ検索基盤として長年BigQueryを利用していましたが、利用体験と効率を向上させるためにGrafanaをベースとした新しいオブザーバビリティ基盤に移行しました。ログに加えてメトリックスとトレース情報を扱えるようになり、可視化が楽になり、ログベースのアラートなど様々な機能が使えるようになりました。

複数の記事に分けて説明させていただきますがこの記事ではSREが作ったオブザーバビリティ基盤の背景と全体像をご紹介できればと思います。


背景

オブザーバビリティ基盤をリニューアルする前はKubernetes (AWS EKS と GCP GKE)で動いているアプリケーションのログをfluent-bitで収集しリアルタイムでBigQueryに送っていました。大量のログデータを検索しても結果が素早い時間で返ってきたり、集計も簡単にできたり、テーブルを分けることでアプリケーション単位の細かい権限管理も可能になったりしていてメリットがそれなりにありましたが以下の課題もありました。

  • ユーザビリティが低い:簡単な検索のためでもSQLクエリを書かないといけない、検索したいアプリケーションやフィールドの候補が出ない、時間の絞り込みが手入力、結果が見づらい(特にjsonログの場合) …
  • 可視化ができない:例えばエラーログがいつからいつまで出ていたかはすぐわからない、ダッシュボードも作れない…
  • オブザーバビリティ機能がたりない:メトリックスやトレース情報を扱えないため別の仕組みを並行で利用する必要がある
  • 料金が高い:AWS EKSからのログはNATゲートウェイ料金とデータ転送料がかかってしまう、リアルタイム性のためにBigQueryのインジェスト料金がかかってしまう

障害の原因特定と解決を早めるため、または問い合わせ対応の効率を向上させるため、基盤をリニューアルすることになりました。ただのログ基盤だけではなく、ログ、メトリックス、トレース情報を全て扱えて、ダッシュボードやアラートを簡単に作れる本格的なオブザーバビリティ基盤が目標でした。

プロダクトの選定

基盤のリニューアルにあたって検証や移行作業が発生しそれなりの工数がかかってしまうため、世の中にあるプロダクトをしっかり検証し、これから長年GO Inc.を支える仕組みを選ぶようにしました。

実データを流しながら次のプロダクトを様々な観点で評価しました:OpenSearchGrafanaNew RelicGoogle Cloud Operations (旧Stackdriver)、Amazon CloudWatch。どのプロダクトも印象がとても良くて選ぶことが大変でした。参考まで評価の結果を以下にまとめますが、絶対的な評価ではなくあくまでもGO Inc.の今の状況の中でのSREの評価だけであって、ニーズによって異なります。

次の項目ごとに1〜5のスコアを付けて評価しました (1があまり良くない、5がとても良い):検索の速さ (Search Speed)、ユーザビリティ (Usability)、可視化の良さ (Visualization)、メトリックの扱い (Metrics)、トレースの扱い (Traces)、ログからトレースに飛ぶなどオブザーバビリティ基盤としての一体感 (Observability Platform)、セキュリティ (Security)、ログの集計と分析 (Aggregation)、運用の楽さ (Maintenance)、料金 (Pricing)

An image from Notion

プロダクトごとのメリットデメリット:

  • OpenSearch (Amazon OpenSearch Serviceでの運用を想定)
    • ❇️ 検索が早い、GUIの使い勝手が良い、可視化とダッシュボードも充実している、細かい権限管理も可能
    • ⛔ メトリックスの扱いが弱い、運用が大変、コンピュートとストレージの料金が高い
  • Grafana (AWS EKSでの自前運用を想定)
    • ❇️ ログ、メトリックス、トレースを全て扱えて一体感がある、可視化とダッシュボードも充実している、様々な他のデータソースを同じ基盤で見れる、自前運用の場合料金が安い、トレンド的にも好評
    • ⛔ 特徴的なインデックスの仕組みを利用しているためパフォーマンスが気になる、自前運用が大変そう、長期間のデータ集計に向いていない、検索GUIが少し使いづらい
  • New Relic (マネージド)
    • ❇️ 検索が早い、GUIの使い勝手が良い、運用の手間がない
    • ⛔ ログ内容の可視化が限られている、細かい権限管理が難しい、料金が高い、APMを利用しない場合ログとメトリックスとトレース基盤の一体感が低い
  • Google Cloud Operations (マネージド)
    • ❇️ ログ、メトリックス、トレースを全て扱えて一体感がある、ログ検索のGUIが良い、運用の手間がない、BigQueryとの連携が楽
    • ⛔ Cloud Loggingの検索が遅いと感じた、可視化とトレース周りが限られている、料金が高い
  • Amazon CloudWatch (マネージド)
    • ❇️ ログ、メトリックス、トレースを全て扱えて一体感がある、運用の手間がない
    • ⛔ 検索画面がいくつかあって混乱する、ダッシュボードとアラートが限られている、料金が高い

どのプロダクトもメリットとデメリットがあって、圧倒的に優位なものがなかったです。一方で検証することで以下の2つのユースケースの利用パターンが異なりすぎて分けて考えた方がいいとわかりました。

  • 障害対応、問い合わせ対応、パフォーマンスチューニング:メトリックスを見てからログを検索、ログからトレースを確認するなど何種類かのデータを同時に見て、検索条件を少しづつ変えていく。数時間〜数日間のデータしか同時に見ない、直近数ヶ月分のデータさえあれば十分
  • 長期間のログデータの検索と集計:時々しか発生しないユースケースだが、大量なデータを扱う必要がある

その気づきを踏まえて検証し、弊社エンジニアからのフィードバックをもらい、Grafanaをオブザーバビリティ基盤として採用することにしました。独自サービスを導入することでGrafanaのセキュリティを強化し、ダッシュボードとドキュメントを事前に用意することでユーザビリティを向上させ、準リアルタイムなBigQuery連携の仕組みを導入することで長期間のログ集計も対応できました。

最終的な基盤の評価が以下のようになりました。

An image from Notion

運用以外、全ての項目はかなり高いスコアになりました。自前運用の手間が気になるポイントでしたが、運用してみて半年以上経っていて、手間があまりかからないことがわかりました!そして前の基盤と比べて大凡30%のコスト削減を達成しながら本格的なオブザーバビリティ基盤を提供できました!ここからはGrafanaとSREのオブザーバビリティ基盤をご紹介します。

Grafana

Grafanaとは、Grafana Labs社が開発したOSSのデータ可視化ツールです。Grafana自体はあくまでもGUIですが、以下のツールと組み合わせることでオブザーバビリティ機能を提供できます (LGTMスタック)

  • Loki:ログの長期ストレージと検索ツール
  • Grafana : GUI
  • Tempo:トレース情報の長期ストレージと検索ツール
  • Mimir:Prometheusメトリックスの長期ストレージと検索ツール
  • Pyroscope : プロファイルデータの長期ストレージと検索ツール
An image from Notion

他に連携できるデータソースがたくさんあり、一つの可視化ツールで様々なデータを閲覧したり可視化したりできます。

An image from Notion

GO Inc.のオブザーバビリティ基盤

Grafana関連のプロダクトがOSSのため、自前で運用することが可能です。Grafana Cloudというマネージドサービスも存在していて運用が大変な場合はそういう選択肢もあります。ただしデータ量とアクティブユーザ数に比例する課金モデルになってしまうため、GO Inc.では自前で運用することにしました。

自前運用でもGrafana Enterpriseに加入すればデータソースの権限管理などセキュリティ関連の機能が増えるのとGrafanaの方が運用のサポートをしてくれます。毎月基本料金$3500+$36/アクティブユーザかかりますのでコストを抑えるために加入しませんでした。その代わりに、セキュリティを向上させるためにGrafanaと連携する独自のマイクロサービスを開発しました。

アーキテクチャ

Grafana、Loki、Mimir、Tempoを動かすための専用のAWS EKSオブザーバビリティクラスタを構築しました。タクシーアプリ『GO』のサービスと同じクラスタでGrafanaを動かしてしまうと、KubernetesやGrafanaのアップデートなどメンテナンスの際に万が一問題が発生した場合はサービスの問題なのかオブザーバビリティの問題なのか切り分けが難しくなり混乱してしまうため専用クラスタにしました。タクシーアプリ『GO』のサービスがAWS EKSとGCP GKEを両方利用していることもあって、オブザーバビリティ用の専用のクラスタがあった方が構成がシンプルになります。

全体のアーキテクチャが以下のようになります。

An image from Notion

プロダクトごとに別記事で詳細に説明しようと思いますので、今回はざっくりと説明します。

右側にオブザーバビリティクラスタがあります

左側にサービス用のAWS EKSとGCP GKEクラスタがあります

コンポーネントが少し多いですがサービスクラスタで動くものがエージェントのみになっているのとEKSとGKEの環境の差がほとんどないため運用が楽です。オブザーバビリティサービスが全て一箇所にまとめられているためサービスと分けて管理しやすいです。

権限管理と監査ログ

GO Inc.ではそれぞれのサービスへのアクセスを細かく管理しています。具体的にグループごとに権限管理を行なっていて、エンジニアが所属しているグループのサービスにしかアクセスできないようにしています。オブザーバビリティ基盤もログ、メトリックス、トレース情報へのアクセス権限も細かく管理する必要があります。

Mimir、Loki、Tempoはマルチテナントなシステムのため細かい権限管理が可能になっています。Promtail、Prometheus、OpenTelemetry Collectorそれぞれのエージェントがデータを送る際に、データのテナント(持ち主)を X-Scope-OrgID リクエストヘッダとして送っています。データが最終的にS3に保存されますがテナントごとにフォルダが分かれていて混ざらないようになっています。データのアクセス権限が基本的にテナント単位になりますので、データを送る時に適切なテナントを指定することが重要です。Kubernetesで動いているサービスの場合はKubernetesのネームスペースをテナントとして利用することが一番自然でそれを採用しています。

Grafanaは様々な認証方法を対応していますがSREではAzure ADのSSO認証を採用しています。ただしGrafana Entepriseを利用していないため、そのままだとLoki、Mimir、Tempoへのアクセスを細かく制御できずGrafanaに認証できる方全員が全てのメトリックス、ログ、トレース情報を見れてしまいます。認可レイヤを追加するためにアーキテクチャ図にあったLoki Oauth GatewayとMimir OAuth Gatewayを開発しました。

たとえばメトリックスを閲覧する際、GrafanaからMimirへの検索リクエストがOAuth Gatewayを経由させるようにしています。OAuth Gatewayは認証の際に取得されたOAuthトークンをパースしユーザ情報を抽出できます。その情報を元にリクエストを許可するかしないかを決めています。

An image from Notion

OAuth Gatewayは単純なOpenResty(Nginx)プロキシとして開発しました。LuaでOauthトークンをパースしユーザが所属しているAzure ADグループを抽出します。アクセスしようとしているテナント情報がGrafanaからのリクエストの X-Scope-OrgIDヘッダに入っているため、Azure グループごとにアクセス可能なテナントさえ決めれば認可仕組みを簡単に実装できます。参考までコードのサンプルを以下に残しています。

nginx.conf

lua_package_path '/etc/nginx/?.lua;;';

server {
  listen             8080;

  location = / {
    return 200 'OK';
  }

  ...

  location /prometheus {
    # check permissions
    rewrite_by_lua_file /etc/nginx/auth.lua;
    # send request to mimir
    set $mimir_backend my-mimir.mimir.svc.cluster.local;
    proxy_pass      http://$mimir_backend:8080$request_uri;
  }
}

auth.lua

local jwt = require "resty.jwt"

-- settings: list of allowed tenants per Azure AD group
local authorizations_table = {
  group1 = { "tenant1" },
  group2 = { "tenant2" },
  group3 = { "tenant1", "tenant2" },
}

-- retrieve JWT token containing user information
local id_token = ngx.req.get_headers()['x-id-token']
if not id_token then
  ngx.status = 403
  ngx.say("403 Forbidden: cannot find JWT token")
  return ngx.exit(403)
end

-- parse JWT token, retrieve user groups
local jwt_obj = jwt:load_jwt(id_token)
local jwt_payload = jwt_obj['payload']
local groups = jwt_payload['groups']

-- determine user authorizations
local authorizations = {}
if groups then
  for k, v in pairs(groups) do
    local group_authorizations = authorizations_table[v]
    if group_authorizations then
      for k2, v2 in pairs(group_authorizations) do
        authorizations[v2] = true
      end
    end
  end
end

-- ensure user is authorized to access X-Scope-OrgID header tenant
local scope_orgid = ngx.req.get_headers()['x-scope-orgid']
local scope_orgid_authorized = false
for k, v in pairs(authorizations) do
  if k == scope_orgid then
    scope_orgid_authorized = true
    break
  end
end

if not(scope_orgid_authorized) then
  ngx.status = 403
  ngx.say("403 Forbidden: not allowed to access this tenant")
  return ngx.exit(403)
end

MimirとLokiにアクセスする時にOAuth Gatewayを経由させることでユーザが所属しているグループによってログやメトリックスの細かいアクセス制御を実現できます。トレース情報を管理するTempoに関してはそこまで細かく権限管理する必要がなかったため現在はOAuth Gatewayを利用していないですが必要に応じて簡単に導入できます。

アクセス制御だけではなく、OAuth Gatewayを経由することで誰がいつどのデータにアクセスしたか、網羅的な監査ログも残せて便利です!

IP制限

セキュリティを向上させるためにマルチレイヤのセキュリティ対策が重要で、認証と認可に加えてデータにアクセスする際にIP制限をかけるようにしました。ただしIP制限をかけることで障害の際に状況が把握しづらくなり対応が遅れてしまうリスクもあるため、たとえばログデータに対してはIP制限をかけるが一部のメトリックスに対してはIP制限をかけないなどある程度制御できるような仕組みが必要でした。

先ほど紹介したOAuth Gatewayに新たな実装を追加することでアクセスするデータに応じてのIP制限を実現できました。注意点として、GrafanaがMimirとLokiに転送できるリクエスト情報が限られていてクライアントIPを転送できなかったためクライアントIPをCookieに入れる必要がありました。そのため、クライアントIPをCookieに入れるGrafana Gatewayを導入しました。最終的な構成が以下のようになります。

An image from Notion

Grafana GatewayもOpenRestyベースのサービスで、処理が単純のため割愛します。IP制限のためにOAuth Gatewayに以下のような実装を追加しました。

-- authorized ips
local authorized_ips = { "xxx.xxx.xxx.xxx", "yyy.yyy.yyy.yyy" }

-- retrieve client ip
local client_ip = ngx.var.cookie_Client_Ip

-- check allow list
if not(authorized_ips[client_ip]) then
  ngx.status = 403
  ngx.say("403 Forbidden: ip not allowed")
  return ngx.exit(403)
end

この例だと全てのリクエストを許可するかしないかという処理になりますが、ユーザが所属しているグループとアクセスしようとしているテナントデータに応じて制御が可能なため、会社のセキュリティポリシーに柔軟に対応できて、かなり便利です!

長期間のログ分析

「プロダクトの選定」のところで既述したように、長期間のログデータの検索と集計を別で考える必要があります。そのユースケースを対応できるように、ログデータをLokiだけではなく、大量なデータ検索と集計に優れているBigQueryに投入することにしました。ただしログを完全にリアルタイムでそのままにBigQueryに投入してしまうと、AWSのデータ転送料とNATゲートウェイ料金だったり、BigQueryのインジェスト料金がかかったりするためGCPのStorage Transfer Serviceをベースとした仕組みを用意しました。

An image from Notion

流れを簡単に説明すると、

  • Nodeごとに動いているfluent-bitがコンテナログを収集しバッチでS3にアップロードする
  • S3とSQSを連携させることで、ファイル作成イベントがSQSに流れる
  • GCPのStorage Transfer Serviceのイベントドリブン転送機能を使って、SQSに入っているイベントを元にデータがS3からGCSに同期される
  • GCSのファイル作成イベントをトリガーにCloud Functionが動いていて、データがGCSからBigQueryに移動される

少し複雑に見えるかもしれないですがこの仕組みのメリットがたくさんあります

  • ログがS3にアップロードされているため、
    • バックアップとして使える (万が一Grafana Lokiに問題があった場合)
    • コンプライアンスのためにログを長く保存しないといけないことがよくあるが、fluent-bitからS3にアップロードする時にログがgzipで圧縮されているため、データ量が非常に小さくなって保存料金が安い
    • そのログを閲覧することがほぼないため、S3のストレージクラスをうまく使うことで料金がさらに安くなる
    • 検索する必要になった時、Athenaを使えば楽
  • S3からBigQueryまでの仕組みの費用がとても安い
    • GCPのStorage Transfer Serviceが無料
    • SQSに流れるデータはアップロードされたログファイルのメタデータのみのためほぼ無料
    • GCSに同期されるデータが一時的しか保存されていないためほぼ無料
    • Cloud Functionの費用が少しかかりますが安い
    • BigQueryのストレージ料金がかかりますが最近値下げで安くなっている
    • S3からSQSへの設定で連携するデータを簡単に制御できるため、レガシーなシステムや開発が発生していないサービスのログをBigQueryに連携しないことでさらにコスト削減ができる
  • 完全にリアルタイムではないものの、ログが出力されてからBigQueryに保存されるまでに2分〜10分ぐらいかかっていてかなり早い
  • データをS3からBigQueryに同期するGCPのサービスなど他のやり方がありますが、クォータに引っかかったり1日に一回までの同期だったりして制約が多い

GrafanaだけではなくBigQuery連携の仕組みを設けることで、様々なユースケースに対応でき会社の方針に柔軟に対応可能になっています。普段はオブザーバビリティ基盤Grafanaでメトリックスとトレースと一緒にログを閲覧したり可視化したりしていますが、大量のログデータの集計または他のデータセットとジョインする必要が出た時にBigQueryを利用しています。

終わりに

オブザーバビリティ関連のプロダクトがたくさんあり選定することが難しかったですが、Grafanaを本番環境で使い始めてから半年経っていて非常に好印象です!問い合わせ対応のためのちょっとした可視化が非常に楽にできるようになり、メトリックスとログとトレースを一箇所で見れることも便利です。自前運用が少し恐れていましたが運用してみてあまり大変ではないことがわかりました!

参考までオブザーバビリティ基盤の最終的なアーキテクチャ図を以下にまとめています。

An image from Notion

Loki、Mimir、Tempoは今まであまりなかった非常に面白い仕組みを利用しているため、別記事で詳細に説明したいと思います。是非読んでみてください!


We're Hiring!

📢
GO株式会社ではともに働くエンジニアを募集しています。

興味のある方は 採用ページ も見ていただけると嬉しいです。

Twitter @goinc_techtalk のフォローもよろしくお願いします!