CDKでEKSクラスタにnewrelic-bundleをインストールしてモニタリングする

kubernetesawsnewrelicprometheus

NewRelic の Kubernetes Integration の各種コンポーネントをまとめた nri-bundle という Helm Chart が提供されており、Guided install を進めると渡すパラメータが生成されるのでこれを CDK に書き写した。

Chart を見ると認証情報はそのまま文字列で渡すほか Secret を指定することもできたので、External Secrets で SecretsManager から取り込む。

CDK で External Secrets Operator をインストールし Secrets Manager のデータを Kubernetes の Secret として読めるようにする - sambaiz-net

const newrelicNamespace = cluster.addManifest('newrelic-namespace', {
  apiVersion: 'v1',
  kind: 'Namespace',
  metadata: {
    name: 'newrelic'
  }
})

const secretName = 'newrelic-bundle-secret'

const newrelicBundleSecret = cluster.addManifest('NewrelicBundleSecret', {
  apiVersion: 'external-secrets.io/v1beta1',
  kind: 'ExternalSecret',
  metadata: {
    name: secretName,
    namespace: 'newrelic'
  },
  spec: {
    refreshInterval: '1h',
    secretStoreRef: {
      name: 'secretsmanager',
      kind: 'ClusterSecretStore'
    },
    data: [{
      secretKey:  'newrelic-license-key',
      remoteRef: {
        key: secretName,
        property: 'newrelic-license-key'
      }
    }, {
      secretKey:  'pixie-api-key',
      remoteRef: {
        key: secretName,
        property: 'pixie-api-key'
      }
    }, {
			// key of the deploy key is "deploy-key".
			// https://github.com/pixie-io/pixie/blob/release/vizier/v0.14.8/k8s/operator/helm/values.yaml#L37
      secretKey:  'deploy-key',
      remoteRef: {
        key: secretName,
        property: 'pixie-deploy-key'
      }
    }]
  }
})

newrelicBundleSecret.node.addDependency(newrelicNamespace)
newrelicBundleSecret.node.addDependency(secretStore)

現状、Pixie のバージョンが古く ARM インスタンスで動かすとエラーになる。PRは出ている。

const newrelicBundle = cluster.addHelmChart('newrelic-bundle', {
  chart: 'nri-bundle',
  release: 'newrelic-bundle',
  repository: 'https://helm-charts.newrelic.com',
  namespace: 'newrelic',
  createNamespace: false,
  values: {
    global: {
      customSecretName: secretName,
      customSecretLicenseKey: 'newrelic-license-key',
      cluster: cluster.clusterName,
      lowDataMode: true,
    },
    "newrelic-infrastructure": {
      privileged: true,
    },
    "kube-state-metrics": {
      enabled: true,
      image: {
        tag: "v2.10.0"
      }
    },
    "kubeEvents": {
      enabled: true,
    },
    "newrelic-prometheus-agent": {
      enabled: true,
      lowDataMode: true,
      config: {
        kubernetes: {
          integrations_filter: {
            enabled: true,
          }
        }
      },
    },
    "newrelic-logging": {
      enabled: true,
      lowDataMode: true,
      fluentBit: {
        // excluding pods by adding the annotation fluentbit.io/exclude: "true"
        k8sLoggingExclude: true
      }
    },
    "newrelic-pixie": {
      enabled: true,
      customSecretApiKeyName: secretName,
      customSecretApiKeyKey: "pixie-api-key"
    },
    "pixie-chart": {
      enabled: true,
      customDeployKeySecret: secretName,
      clusterName: cluster.clusterName,
    }
  },
  wait: false
})

newrelicBundle.node.addDependency(newrelicBundleSecret)

インストールするとコンテナやノードレベルのメトリクスやログに加えeventsなども確認できる。

また、Linuxカーネル内のサンドボックスでネットワークなどのイベントによって動く eBPF プログラムでデータを収集する Pixie によってサービスマップや通信量などが可視化され、

アプリケーションのフレームグラフも見ることができる。

Pixie の PxL script で Kubernetes クラスタでの通信を取得する - sambaiz-net

newrelic-prometheus-agentAgent mode で Prometheus を動かして Exporter から取得したメトリクスを New Relic に remote_write するコンポーネント。クエリやアラート、Recording rules は使えない。

Prometheus の Recording rules で集計したデータを New Relic に Remote write してデータ送信量を節約する - sambaiz-net

$ kubectl port-forward -n newrelic $(kubectl get pod -n newrelic -l app.kubernetes.io/name=newrelic-prometheus-agent -o jsonpath="{.items[0].metadata.name}") 9090:9090
Forwarding from 127.0.0.1:9090 -> 9090
Forwarding from [::1]:9090 -> 9090

$ curl -G 'http://localhost:9090/api/v1/query' --data-urlencode 'query=up' | jq
{
  "status": "error",
  "errorType": "execution",
  "error": "unavailable with Prometheus Agent"
}

Exporter の Service discovery は Kubernetes の API を呼んで scrape_configs の kubernetes_sd_configs に対応するリソースを取得することで行われる。source_labels を separator で繋げた文字列と regex と比較し action が keep ならそれに合致しないものが drop され、replace なら target_label に replacement の値を設定する。つまり Pod に newrelic.io/scrape: true annotaion を付けると /metrics が返す Prometheus メトリクスが送られるようになる。

# /etc/prometheus/config/config.yaml
...
scrape_configs:
...
- job_name: newrelic-pod
  honor_timestamps: true
  scrape_interval: 30s
  scrape_timeout: 10s
  metrics_path: /metrics
  scheme: http
  follow_redirects: true
  enable_http2: true
  relabel_configs:
  - source_labels: [__meta_kubernetes_pod_annotation_newrelic_io_scrape]
    separator: ;
    regex: "true"
    replacement: $1
    action: keep
  - source_labels: [__meta_kubernetes_pod_phase]
    separator: ;
    regex: Pending|Succeeded|Failed|Completed
    replacement: $1
    action: drop
  - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scheme]
    separator: ;
    regex: (https?)
    target_label: __scheme__
    replacement: $1
    action: replace
  ...
  kubernetes_sd_configs:
  - role: pod
    kubeconfig_file: ""
    follow_redirects: true
    enable_http2: true
...
remote_write:
- url: https://metric-api.newrelic.com/prometheus/v1/write?collector_name=prometheus-agent&collector_version=1.11.0&prometheus_server=newrelic-bundle-newrelic-prometheus-agent-0
  remote_timeout: 30s
  write_relabel_configs:
  - source_labels: [__name__]
    separator: ;
    regex: kube_.+|container_.+|machine_.+|cadvisor_.+
    replacement: $1
    action: drop
  - source_labels: [__name__]
    separator: ;
    regex: timeseries_write_(.*)
    target_label: newrelic_metric_type
    replacement: counter
    action: replace
  - source_labels: [__name__]
    separator: ;
    regex: sql_byte(.*)
    target_label: newrelic_metric_type
    replacement: counter
    action: replace
  name: newrelic_rw
  authorization:
    type: Bearer
    credentials: <secret>
...

送られたメトリクスは FROM Metric の同名のフィールドで参照できる。 ただし、Prometheus の Counter は累積和を返すのに対して New Relic の Count はデルタ値なので変換される

/* container_memory_usage_bytes{id="/"} */
FROM Metric SELECT latest(container_memory_usage_bytes) WHERE id = '/' TIMESERIES

参考

eBPFとは何か、なぜそれがオブザーバビリティに関係するのか?