New Relicでインフラやアプリケーションをモニタリングする

newrelicinfraawsloggolangfluentd

New RelicはインフラやアプリケーションをモニタリングするSaaSで、同種のサービスとしてはDatadogがある。 2020年に製品や課金体系変わり、転送量とユーザー数に対して課金されるようになったようだ。 ホストや機能に対して課金されるDatadogと比べると、少人数で多くのインスタンスを管理している場合に有利ということになる。また、Datadogの課金体系はやや複雑で頻繁に台数が増減する環境では請求がどの程度になるか読みづらいところがあるが、こちらは分かりやすい。新しい機能を使っても単価が上がることもないのでいろいろと試しやすいのも良いと思う。

一方、大人数で管理していたり、捌くリクエストが膨大な場合APMの転送量がかさむことでDatadogより高くなるケースがある。 多くの場合はユーザー課金の割合が大きくなるので、Full platform userの数を減らして半額のCore userや無料のBasic userにすればコストが抑えられるが、 DashboardやAlertについては全てのユーザーが使える一方APMの画面が見られるのはFull platform userのみだったりするのが悩ましい。 転送量についてはDrop dataすることで減らすことができるが現状画面からは設定できずNerdGraphを用いる必要がある。次はTerraformで設定を行う例。

New RelicのGraphQL API、NerdGraphでリソースを取得する - sambaiz-net

resource "newrelic_nrql_drop_rule" "heavy_path" {
  account_id  = *****
  description = "Drop transactions data"
  action      = "drop_data"
  nrql        = "SELECT * FROM Transaction WHERE `request.uri` = '/heavy_path'"
}

トラブルシューティングは比較的難しく感じていて、なぜかメトリクスが送られないといったことがあったり、古いドキュメントを参照してしまったりしたがライブラリやドキュメントはOSSになっており、いざとなれば実装を読んだり修正のPRを出せば良いと考えている。ただドキュメントに明示されていないことがあったり、されていても見つけられないことがあったりするので、特に初めて使う場合はユーザー単価が上がってしまうがサポートが付くPro以上のプランにすると安心かもしれない。去年買収したCodeStreamを含め、新機能の開発は活発に行われている印象があるので、トラブルシューティングについても改善されると良いなと思っている。

NewRelic CodeStreamでコードの質問やデバッグを効率的に行えるようにする - sambaiz-net

AWS連携

New RelicのアカウントからAssumeRoleするReadOnlyAccessbudgets:ViewBudgetを付与したRoleを作る。

AWSのAssumeRole - sambaiz-net

{
  "Statement": [
    {
      "Action": [
        "budgets:ViewBudget"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ],
  "Version": "2012-10-17"
}

メトリクスの取得は従来の定期的にAPIを呼んでポーリングする方法と、FirehoseにストリーミングするCloudWatch Metric Steamsを用いる方法がサポートされていて、後者が推奨されている。用意されているCloudFormationのテンプレートにLicense Keyを渡してスタックを作成すると次のリソースが作成され、New Relicの画面上に各種リソースのメトリクスが表示されるようになる。

作成されたMetric Streamsの設定を見るとすべての名前空間のメトリクスが送られるようになっている。

メトリクスが送られない場合は誤ったKeyやKey IDをコピーしていないか確認する。

infrastructure monitoring agentのインストール

インスタンスのCPU使用率やネットワークI/Oなどを取得するAgentをインストールする。以下はAmazon Linux 2 (arm64)の例。

$ echo "license_key: ****-" | sudo tee -a /etc/newrelic-infra.yml
$ sudo curl -o /etc/yum.repos.d/newrelic-infra.repo https://download.newrelic.com/infrastructure_agent/linux/yum/amazonlinux/2/aarch64/newrelic-infra.repo
$ sudo yum -q makecache -y --disablerepo='*' --enablerepo='newrelic-infra'
$ sudo yum install newrelic-infra -y
$ sudo systemctl start newrelic-infra

New Relic Flex

/etc/newrelic-infra/integrations.d 下に次のようなyamlファイルを置くとコマンドの出力HTTPのレスポンスファイルの内容を定期的に送ることができる。

Fluentdをモニタリングするのにmonitor_agentの値をTelemetry SDKで送ることもできるが、この機能を用いれば集約サーバーでアプリケーションを動かす必要がない。

fluentdのmonitor_agentのデータをGoでGoogle Stackdriverに送って監視する - sambaiz-net

$ cat /etc/newrelic-infra/integrations.d/fluentd.yml
integrations:
- name: nri-flex
  config:
    name: fluentd
    apis:
    - event_type: fluentd
      url: http://localhost:24220/api/[email protected]=forward

次のようなNRQLで値を確認できる。

FROM fluentd SELECT rate(average(buffer_total_queued_size), 1 minute) FACET `config.@id` TIMESERIES 

APM

Goroutineの数やGCの実行時間といったランタイム情報や、X-Rayのようなリクエストトレースやそのサマリーを見ることができる。 C, Go, Java, .NET, Node.js, PHP, Python, Rubyのライブラリが提供されている。

$ go get github.com/newrelic/go-agent

echoやginといった主要なWAFにはintegrationが提供されていて、このmiddlewareを使うとリクエストを紐づけたTransactionContextに書き込まれ、nrecho.FromContext(c)で取り出せるようになる。 リクエストが紐づいていないTransactionBackground jobsとして扱われる。

import "github.com/newrelic/go-agent/v3/integrations/nrecho-v4"
app, err := newrelic.NewApplication(
    newrelic.ConfigAppName("app_name"),
    newrelic.ConfigLicense("*****"),
    newrelic.ConfigDistributedTracerEnabled(true),
)
  
http.HandleFunc(newrelic.WrapHandleFunc(app, "/users", usersHandler))
e.Use(nrecho.Middleware(app))

// in middleware
/*
txn.SetWebRequestHTTP(c.Request())
c.Response().Writer = txn.SetWebResponse(rw)
c.SetRequest(c.Request().WithContext(newrelic.NewContext(c.Request().Context(), txn)))
*/

func handler(c echo.Context) error {
	txn := nrecho.FromContext(c)
	return c.String(http.StatusOK, "ok")
}

NewApplication()するとデータを集めて送るgoroutineが動き出すのでShutdown()することなく都度Applicationを作成するとgoroutineがリークしてしまう。

Segmentを切るとその区間の実行時間がタイムライン上に表示される。

func do(ctx context.Context) {
  defer newrelic.FromContext(ctx).StartSegment("do").End()
}

HTTPリクエストをwrapすると外部リクエストのSegmentが作られ、Service Map上に各サービスの平均レイテンシなどが表示されるようになる。

client := &http.Client{}
client.Transport = newrelic.NewRoundTripper(client.Transport)

request, _ := http.NewRequest("GET", "http://example.com", nil)
request = newrelic.RequestWithTransactionContext(request, txn)

response, err := client.Do(request)

カスタムメトリクス

app.RecordCustomMetric(key,value)を呼ぶとkeyにCustom/prefixを付けたメトリクスが送られる。

app.RecordCustomMetric("CustomMetricName", 132)

Data explorerには表示されずAPMのMetrics explorerから値を確認できる。

NRQLでは次のようにnewrelic.timeslice.value参照する

FROM Metric SELECT count(newrelic.timeslice.value) 
  WHERE appName = 'MY APP' 
  WITH METRIC_FORMAT 'Custom/{name}' 
  TIMESERIES FACET name

ログの転送

FluentdやFluent Bit, Logstashのpluginでログを転送できる。

$ td-agent-gem install fluent-plugin-newrelic
<filter foo>
  @type record_transformer
  <record>
    service_name ${tag}
    hostname "#{Socket.gethostname}"
  </record>
</filter>

<match foo>
  @type newrelic
  api_key ****
</match>

Goの場合logrusのintegrationが提供されていて、これを用いると次のフィールドがログに追加され、リクエストトレースとログを紐づけることができるようになる。同様のことを自前で行えば他のloggerも使える。

func AddLinkingMetadata(m map[string]interface{}, md newrelic.LinkingMetadata) {
	metadataMapField(m, KeyTraceID, md.TraceID)
	metadataMapField(m, KeySpanID, md.SpanID)
	metadataMapField(m, KeyEntityName, md.EntityName)
	metadataMapField(m, KeyEntityType, md.EntityType)
	metadataMapField(m, KeyEntityGUID, md.EntityGUID)
	metadataMapField(m, KeyHostname, md.Hostname)
}

ログを紐づけるとErrors Eventからも飛べるようになるので原因調査の際に役立つ。

CloudWatch Logsを介さずにLambdaのテレメトリを行うnewrelic-lambda-extensionとその仕組み - sambaiz-net

参考

NewRelic でtd-agent(fluentd) にメトリクスを監視する - y-ohgi’s blog