Goのカスタムアプリケーション監視

時々あなたはそれを自分でしなければならないだけです。 プロプライエタリアプリケーションを管理する場合、利用できるユーティリティのスイートはありません。独自のソリューションを実装する必要がある可能性があります。 ただし、カスタムソリューションには引き続き監視が必要です。 このブログでは、LogicMonitorのTechOpsが一部の内部ツールのカスタム監視をどのように実装したかを示します。

XNUMX月に、データを保存するための独自の時系列データベース(TSDB)のリリースを発表しました。 私達 しませんでした 新しいTSDBをサポートするために必要なすべての内部ツールを発表します。 開発は優れたTSDBを提供しましたが、本番環境で実行するために必要なツールは提供しませんでした。そのため、TechOpsチームは、新しいTSDBアーキテクチャからユーザーデータをバックアップするための運用インフラストラクチャの構築に着手しました。

すべてのTSDBデータをバックアップするためのシステムが整ったら、そのシステムを監視する必要がありました。 詳細には触れませんが、TSDBバックアップシステムには、メタデータデータベース、外部ストレージ、バックアップエージェント、一元化されたバックアップスケジューラなど、監視が必要ないくつかのコンポーネントと、ネットワークスループットなどのパフォーマンスメトリックがあります。 CPU使用率、メモリ使用率、およびディスクパフォ​​ーマンス。

これらのほとんどは、LogicMonitorを使用して箱から出してすぐに監視するための簡単なものですが、特注のバックアップエージェントとスケジューラは両方とも Go、Googleの効率的でスケーラブルなオープンソースプログラミング言語であり、本質的に監視には向いていませんでした。 このブログの残りの部分では、これらのGoアプリケーションからカスタムメトリックを公開し、LogicMonitorでそれらを監視することがいかに簡単であったかについて説明します。

概要

Goを使用すると、アプリケーション内に単純なHTTPサーバーを作成することが信じられないほど簡単になります。 この機能と組み合わせて LogicMonitorのWebページデータ収集方法、コードからカスタムメトリックをすばやく公開し、そのデータを使用してインフラストラクチャを監視できるようになりました。

アプリケーションメトリクスの公開

プロセスの最も難しい部分は、コードから公開するメトリックを正確に決定することです。 システムとアプリケーションはそれぞれ異なるため、基本から始めて、Goランタイムライブラリを使用してGoランタイム自体に関するパフォーマンスメトリックを公開する例を示します。

インポート(
 「エンコーディング/ json」
 "ログ"
 「net / http」
 "ランタイム"
)

func Performance(w http.ResponseWriter、req * http.Request){
 結果:= make(map [string] float32)

 //ゴルーチンの数を取得します
 // https://golang.org/pkg/runtime/#NumGoroutine 
 numRoutines:= runtime.NumGoroutine()
 results ["GoRoutines"] = float32(numRoutines)

 //メモリ統計を取得します
 // https://golang.org/pkg/runtime/#MemStats 
 var memStats runtime.MemStats
 runtime.ReadMemStats(&memStats)

 //割り当てられ、まだ解放されていないバイト
 results ["MemAlloc"] = float32(memStats.Alloc) 

 //無料の数
 results ["MemFrees"] = float32(memStats.Frees) 
 
 //割り当てられ、まだ解放されていないバイト
 results ["MemHeapAlloc"] = float32(memStats.HeapAlloc) 
 
 //アイドルスパンのバイト
 results ["MemHeapIdle"] = float32(memStats.HeapIdle) 

 //非アイドルスパンのバイト
 results ["MemHeapInUse"] = float32(memStats.HeapInuse) 

 //割り当てられたオブジェクトの総数
 results ["MemHeapObjects"] = float32(memStats.HeapObjects) 

 //システムから取得したバイト
 results ["MemHeapSys"] = float32(memStats.HeapSys) 

 // mallocの数
 results ["MemMallocs"] = float32(memStats.Mallocs) 

 //ガベージコレクションの総数
 results ["MemNumGc"] = float32(memStats.NumGC) 

 //ガベージコレクタがプログラムを一時停止した合計時間
 results ["MemPauseTotalNs"] = float32(memStats.PauseTotalNs) 

 //システムから取得したバイト 
 結果["MemSys"] = float32(memStats.Sys)

 resp、err:= json.Marshal(results)
 if err!= nil {
  log.Printf( "エラー:キューメトリックをjsonにマーシャリングできませんでした")
  w.WriteHeader(http.StatusInternalServerError)
 場合} else {
  w.Write(resp)
 }
}


このコードブロックは、次の例に示すように、Goランタイムに関するメトリックを含むJSON文字列を返します。

{"GoRoutines":56、 "MemAlloc":6986048、 "MemFrees":950790800、 "MemHeapAlloc":6986048、 "MemHeapIdle":34209790、 "MemHeapInUse":13205504、 "MemHeapObjects":33145、 "MemHeapSys":47415296 MemMallocs ":950823940、" MemNumGc ":142465、" MemPauseTotalNs ":40569120000、" MemSys ":52869370}


公開できる指標は、想像力によってのみ制限されます。 アプリケーションから特定のメトリックを取得し、それをJSONに変換して応答するために必要な、あらゆるコードを記述できます。 基本的な概要は次のとおりです。

import( "encoding / json" "log" "net / http")func FooMetric(w http.ResponseWriter、req * http.Request){//ここでデータポイントを計算または取得しますresults:= getFooMetric()resp、err:= json.Marshal(results)if err!= nil {log.Printf( "エラー:キューメトリックをjsonにマーシャリングできませんでした")w.WriteHeader(http.StatusInternalServerError)} else {w.Write(resp)}}


アプリケーションメトリクスの提供

必要なメトリックをコンパイルするためにいくつかの関数を組み立てたら、それは 単純なHTTPサーバーを起動し、アプリからメトリックを公開するのは簡単です。

import( "log" "net / http")func StartServer()error {// https://golang.org/pkg/net/http //リクエストルーティングを処理するマルチプレクサを作成しますh:= http.NewServeMux()/ /リクエストをルーティングするためのリソースハンドラーの追加//最初の引数 'pattern'は、リクエストパスを照合し、//そのリクエストを// 8080番目の引数 'handler'で指定された関数に転送するために使用されます// https:// golang.org/pkg/net/http/#HandleFunc // //この場合、URLを// https:// localhost:8080 / stats / performance //上記で作成した関数にマッピングしています。マッピング// https:// localhost:8080 / stats / fooから関数FooMetric h.HandleFunc( "/ stats / performance"、Performance)h.HandleFunc( "/ stats / foo"、FooMetric)// HTTPサーバーを作成します、目的のリスナーポート、//マルチプレクサ、およびいくつかのタイムアウト構成を渡す// https://golang.org/pkg/net/http/#Server srv:=&http.Server {Addr:10、Handler:h、ReadTimeout :10 * time.Second、WriteTimeout:XNUMX * time.Second、} // HTTPサーバーを起動します// https://golang.org/pkg/net/http/#Server.ListenAndServe log.Printf( "info:Stats server Started on localhost" + statsPort)log.Fatal(srv.ListenAndServe())return nil}


ボーナス

Goを使用すると、次の例に示すように、リアルタイムのスタックトレースを非常に簡単に出力できます(これは、LogicMonitorでアプリケーションを使用する場合は特に適用できませんが、共有しないと便利です)。

import( "net / http" "runtime")func Stacktrace(w http.ResponseWriter、req * http.Request、){buf:= make([] byte、1 << 16)runtime.Stack(buf、true)w .Write(buf)}


それでおしまい! これで、HTTPサーバーからこの関数を提供し、ブラウザーでスタックトレースを表示できます。 追加するだけです  h.HandleFunc(“ / stacktrace”、Stacktrace) HTTPサーバーハンドラーに。

すべてをまとめる

コードを既存のアプリケーションに簡単に挿入できるように、すべてを構造体にまとめてから、このHTTPサーバーをアプリケーションのスタートアップに含める方法を示します。

完成した構造体は次のとおりです。

package app

 import (
  "encoding/json"
  "log"
  "net/http"
  "runtime"
 )

 const statsPort = ":8080"

 type StatsServer struct {}

 func s *StatsServer) Performance(
  w http.ResponseWriter, 
  req *http.Request,
 ) {
  results := make(map[string]float32)

  // get number of Goroutines
  // https://golang.org/pkg/runtime/#NumGoroutine 
  numRoutines := runtime.NumGoroutine()
  results["GoRoutines"] = float32(numRoutines)

  // get memory stats
  // https://golang.org/pkg/runtime/#MemStats 
  var memStats runtime.MemStats
  runtime.ReadMemStats(&memStats)

  // bytes allocated and not yet freed
  results["MemAlloc"] = float32(memStats.Alloc) 

  // number of frees
  results["MemFrees"] = float32(memStats.Frees) 
 
  // bytes allocated and not yet freed
  results["MemHeapAlloc"] = float32(memStats.HeapAlloc) 
 
  // bytes in idle spans
  results["MemHeapIdle"] = float32(memStats.HeapIdle) 

  // bytes in non-idle span
  results["MemHeapInUse"] = float32(memStats.HeapInuse) 

  // total number of allocated objects
  results["MemHeapObjects"] = float32(memStats.HeapObjects) 

  // bytes obtained from system
  results["MemHeapSys"] = float32(memStats.HeapSys) 

  // number of mallocs
  results["MemMallocs"] = float32(memStats.Mallocs) 
  // total number of garbage collections
  results["MemNumGc"] = float32(memStats.NumGC) 

  //total time that the garbage collector has paused the program
  results["MemPauseTotalNs"] = float32(memStats.PauseTotalNs) 

  // bytes obtained from system
  results["MemSys"] = float32(memStats.Sys) 

  resp, err := json.Marshal(results)
  if err != nil {
   log.Printf("error: couldn't marshal queue metrics to json")
   w.WriteHeader(http.StatusInternalServerError)
  } else {
   w.Write(resp)
  }
 }

 func (s *StatsServer) Stacktrace(
 w http.ResponseWriter,
 req *http.Request,
 ) {
  buf := make([]byte, 1<<16)
  runtime.Stack(buf, true)

  w.Write(buf)
 }

 func s *StatsServer) StartServer() error {
  // https://golang.org/pkg/net/http 

  // Create a multiplexer to handle request routing
  h := http.NewServeMux()

  // Add resource handlers to route requests
  // The first argument, 'pattern', is used to match a request path 
  // and forward that request to the function specified by the
  // second argument, 'handler'
  // https://golang.org/pkg/net/http/#HandleFunc 
  //
  // In this case, we're mapping the URL 
  // https://localhost:8080/stats/performance 
  // to the function we created above name Performance and mapping
  // https://localhost:8080/stats/foo to the function FooMetric 
  h.HandleFunc("/stats/performance", Performance)
  h.HandleFunc("/stats/foo", FooMetric)

  // Create the HTTP server, passing in desired listener port, 
  // our multiplexer, and some timeout configurations
  // https://golang.org/pkg/net/http/#Server 
  srv := &http.Server{
   Addr: 8080,
   Handler: h,
   ReadTimeout: 10 * time.Second,
   WriteTimeout: 10 * time.Second,
  }

  // Start the HTTP server
  // https://golang.org/pkg/net/http/#Server.ListenAndServe 
  log.Printf("info: Stats server started on localhost" + statsPort)
  log.Fatal(srv.ListenAndServe())
  return nil
 }


これで、HTTPサーバーをアプリに含めるのは、アプリケーションのスタートアップ関数に以下の行を追加するのと同じくらい簡単です。

//監視メトリックサーバーを初期化します//注:サーバーをgo関数として起動するか、ListenAndServe //それ以上のコード実行をブロックしますgofunc(){stats:= StatsServer {} stats.StartServer()}()


HTTPリクエストをアプリケーションに送信することで、指標を表示できるようになりました。

> curl https:// localhost:8080 / stats / performance | jq。 {"GoRoutines":56、 "MemAlloc":6986048、 "MemFrees":950790800、 "MemHeapAlloc":6986048、 "MemHeapIdle":34209790、 "MemHeapInUse":13205504、 "MemHeapObjects":33145、 "MemHeapSys":47415296 MemMallocs ":950823940、" MemNumGc ":142465、" MemPauseTotalNs ":40569120000、" MemSys ":52869370}


アプリがデータを提供しているので、監視を開始します。

LogicMonitor内ですべてをまとめる

公開されたメトリクスの消費は、 Webページのデータソース.

たとえば、次のパフォーマンスを監視するようにデータソースを構成した方法は次のとおりです。 TSDBバックアップスケジューラ:

blog_1

Applies-Toの値system.hostnameは、このアプリケーションを実行している可能性のあるすべてのサーバーと一致するように構成されています。

blog_2

GoHTTPサーバーで構成されたリソースエンドポイントを使用していることに注意してください。

blog_3

最後のステップとして、JSONデータポイントをデータソースに追加する例を次に示します。

blog_4

JSON応答内の特定のデータポイントへのパスに一致するように、JSONパスフィールドを更新してください。

それでおしまい! LogicMonitorは現在、アプリケーションからデータを収集しています。 メトリックは、監視対象のデバイスに移動してデータソースを見つけることで表示できます。

blog_5

Goコードで公開されているランタイムメトリックに対応するデータポイントに注意してください。

blog_6

これで、何らかの理由で顧客データのバックアップがタイムリーに完了しなかった場合でも、運用チームがタイムリーにそれを知ることができると確信できます。 結局のところ、カスタムアプリケーションをインストルメント化していない場合でも、問題について通知するXNUMXつの監視システム(顧客と上司)があります。 これらのXNUMXつのシステムがトリガーされる前に、問題を認識し、対処できることを確認したいと思います。