본문 바로가기

Infrastructure/Prometheus

Node.js 어플리케이션 모니터링

이번엔 Node.js 어플리케이션 (서버) 에서 직접 메트릭을 수집하고, 이를 Prometheus 로 모니터링해보도록 하겠습니다.

 

- Prometheus Client Libraries

 

직접 운영하는 서비스에서 별도의 메트릭을 추가해 모니터링하기 위해선, Prometheus 의 클라이언트 라이브러리를 설치하고 코드 레벨에 적절한 추가가 필요합니다. 이러한 라이브러리들은 Prometheus 의 4가지 메트릭타입을 구현하고 있습니다. (관련 링크)

 

Node.js 에서 사용할 수 있는 클라이언트 라이브러리는 prom-client 입니다. 이 라이브러리가 공식 라이브러리는 아닌데, 저도 실무에서 사용하고 있지만 완전하지 않다고 느끼긴 합니다. (pm2 와의 연동, pushgateway 기능 부족 등.. 제 개인적인 생각입니다.)

 

- prom-client

 

라이브러리의 자세한 사용방법은 예제를 작성해가며 보도록 하고, 몇가지 특징을 정리하도록 하겠습니다.

 

• Prometheus 의 4가지 메트릭 타입 지원 (counter, gauge, histogram, summary)

• 메트릭 리셋 가능

• cluster 모드 지원 (pm2 는 별도 구현 필요)

• pushgateway 지원 (이 부분은 제가 사용해보니 라이브러리를 사용하기보단 pushgateway api 를 직접 사용하는게 더 낫습니다)

 

- 샘플 프로젝트

 

• 먼저 Node.js 프로젝트를 하나 생성합니다.

 

> mkdir prom-client-sample
> cd prom-client-sample
> npm init

 

• 그리고 prom-client 라이브러리를 설치합니다.

 

> npm install prom-client

 

• 샘플 프로젝트는 koa 를 사용하려고 합니다. 관련 라이브러리도 설치합니다.

 

> npm install koa
> npm install koa-router

 

• 서버 파일은 index.js 로 두고, metric 관련된 파일은 metrics.js 로 만들겠습니다.

 

> touch index.js metrics.js

 

• 먼저 작성할 것은 metrics.js 파일입니다.

 

const { Registry, Counter, Gauge, Histogram, Summary, register } = require('prom-client');

const prometheus = () => {
  const registry = new Registry();
  const instances = {};

  const create = ({ type, name, help }) => {
    let instance;

    if (type === 'counter') {
      instance = new Counter({ name, help });
    } else if (type === 'gauge') {
      instance = new Gauge({ name, help });
    } else if (type === 'histogram') {
      instance = new Histogram({ name, help });
    } else if (type === 'summary') {
      instance = new Summary({ name, help });
    }

    if (instance) {
      registry.registerMetric(instance);
      instances[name] = { type, instance };
    }
  };

  const add = ({ name, data }) => {
    if (instances[name]) {
      const { type, instance } = instances[name];

      if (type === 'counter') {
        instance.inc(data);
      } else if (type === 'gauge') {
        instance.set(data);
      } else if (type === 'histogram') {
        instance.observe(data);
      } else if (type === 'summary') {
        instance.observe(data);
      }
    }
  };

  const get = async () => {
    return {
      metrics: await registry.metrics(),
      contentType: register.contentType,
    };
  };

  return { create, add, get };
};

const Prometheus = prometheus();
Prometheus.create({
  type: 'counter',
  name: 'counter',
  help: 'random counter for test',
});
Prometheus.create({
  type: 'gauge',
  name: 'gauge',
  help: 'random gauge for test',
});
Prometheus.create({
  type: 'histogram',
  name: 'histogram',
  help: 'random histogram for test',
});
Prometheus.create({
  type: 'summary',
  name: 'summary',
  help: 'random summary for test',
});

module.exports = {
  Prometheus,
};

 

사용하고 있는 Counter, Gauge, Histogram, Summary 는 prom-client 가 제공하는 메트릭 클래스입니다.

이 클래스들은 객체 생성 시 name, help 두 인자를 필수로 넣어야 합니다.

생성된 메트릭 객체들은 Registry 객체의 registerMetric 함수로 등록이 되어야 메트릭 수집이 가능해집니다.

 

중간에 add 함수 코드에서 보이듯이 Counter 는 inc, Gauge 는 set, Histogram 과 Summary 는 observe 로 메트릭을 객체에 저장하고, 이렇게 저장된 메트릭들은 registry.metrics() 함수로 조회할 수 있습니다.

(Counter, Gauge, Histogram, Summary 클래스는 생성시 다른 옵션 인자나 함수들이 있으니 추가로 확인해보시면 좋을 듯 합니다)

 

제가 예제를 작성하는데에 있어 편의를 위해 Prometheus 라는 함수를 만들고 create, add, get 이라는 내부 함수를 둔 것일뿐

prom-client 가 제공하는 api 들을 사용했습니다.

 

이제 서버 파일에서 이 metrics.js 를 통해 메트릭 수집 및 노출 시켜보겠습니다.

 

• index.js

 

코드 작성에 앞서 저는 /metrics/:name 이라는 경로로 들어오면 name 에 맞는 메트릭을 수집하고,

이렇게 수집된 메트릭은 /metrics 로 조회 가능하도록 할 것입니다.

여기서 name 은 위의 metrics.js 에서 Prometheus.create 함수 호출에 인자로 들어간 name 과 동일합니다.

 

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();
const port = 4000;

const { Prometheus } = require('./metrics');

router.get('/metrics', async (ctx) => {
  const { metrics, contentType } = await Prometheus.get();

  ctx.set('Content-Type', contentType);
  ctx.body = metrics;
});

router.get('/metrics/:name', (ctx) => {
  const { name } = ctx.params;
  const randomNumber = Math.round(Math.random() * 10);
  console.info(name, randomNumber);
  Prometheus.add({ name, data: randomNumber });
  ctx.body = 'done';
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(port, () => {
  console.info(`Test server is listening on port #${port}`);
});

 

metrics.js 에서 Prometheus.create({ ... }) 함수를 통해 name 이 counter, gauge, histogram, summary 인 4개의 객체를 만들어놨었습니다. 이제 index.js 에서 작성한 것처럼 /metrics/counter 라는 경로로 접근하면 랜덤한 숫자가 counter 타입의 객체에 메트릭으로 추가될 것입니다. 다른 타입도 마찬가지로 /metrics/gauge, /metrics/histogram, /metrics/summary 로 수집할 수 있습니다.

 

이제 브라우저에서 localhost:4000/metrics/:name 을 여러번 호출한 후, localhost:4000/metrics 에서 제대로 수집이 되었는지 확인하도록 하겠습니다. 확인을 위해 console.info(name, randomNumber) 를 넣었습니다.

 

 

5번씩 호출하려 했는데 counter 만 4번 불렸네요.

호출한 결과대로 메트릭이 수집되었다면 counter 는 받은 값들을 inc 하므로 30 이 되어야하고,

gauge 는 set 을 사용하니 마지막 4 로 세팅되야 합니다.

histogram 과 summary 는 메트릭이 어떻게 들어가있는지 직접 호출해서 확인해 보겠습니다.

 

 

브라우저에서 localhost:4000 을 호출한 결과입니다. 이 글에서 설명했던 것과 같은 포맷의 메트릭을 확인할 수 있습니다.

counter 와 gauge 는 예상한대로 메트릭이 들어가있습니다.

 

histogram 의 경우 bucket 이 메트릭명에 붙어있고 le 라는 라벨이 들어가있는데, histogram 은 특정 범위 (bucket) 내에서 값의 빈도수를 표현하는 메트릭 타입입니다. 별도로 설정하지 않을 경우 buckets 에는 [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] 가 들어가고, value 는 buckets 의 각 기준값보다 작거나 같은 데이터의 수 입니다.

 

summary 는 quantile 이라는 라벨이 들어가있습니다. 어찌보면 생긴건 histogram 과 비슷한데, 이 지표는 사분위수를 나타냅니다.

value 가 의미하는 바는 각 quantile 의 비율에 맞는 데이터의 사분위수 인데, 위 예시로 설명하자면

summary{quantile="0.5"} 5 -> 이 지표의 의미는 summary 에 쌓인 데이터 중 50% 는 5보다 작거나 같다 라는 의미입니다.

 

여기서 histogram 과 summary 에 대해 첨언하자면 histogram 은 들어온 데이터를 bucket 에 맞게 넣기만 하면 되지만, summary 는 사분위수를 클라이언트에서 계산해서 넣기 때문에 우리가 운영하는 Node.js. 서버에 부담을 줄 가능성이 있습니다.

사분위수 데이터는 histogram 메트릭으로도 histogram_quantile() 이라는 함수를 통해 계산이 가능하고 이 함수는 클라이언트가 아닌 Prometheus 서버에서 실행되기 때문에 운영 서버의 리소스를 생각한다면 histogram 타입을 사용하는 것이 좋다고 볼 수 있습니다.

 

예전 Prometheus 버전에선 sum 과 count 가 summary 에서만 제공되었다고 합니다. 이 때문에 어쩔 수 없이 summary 를 써야하는 경우도 있었지만, 지금은 위에서 보이는 것처럼 histogram 도 sum 과 count 를 제공하므로 summary 는 거의 사용할 일이 없다고 생각하시면 될 것 같습니다.

 

- Prometheus 설정

 

localhost:4000/metrics 로 메트릭 조회가 가능하도록 했으니, 이제 Prometheus 설정에서 타겟을 여기로 바꾸기만 하면 됩니다.

 

 

이후 Prometheus 를 실행하고, 자체 제공하는 UI 에서 메트릭을 확인해보도록 하겠습니다.

 

 

 

여기까지 Node.js 서버에서 prom-client 라이브러리를 사용해 직접 메트릭을 수집하고 Prometheus 를 통해 모니터링까지 진행해봤습니다. 위 예시엔 label 이 들어가있지 않은데, 어려운 부분이 아니니 직접 추가해보면 좋을 것 같습니다.

 

다음엔 이어서 cluster mode 에서의 메트릭 수집을 다뤄보도록 하겠습니다.

'Infrastructure > Prometheus' 카테고리의 다른 글

Prometheus Pushgateway with Node.Js  (2) 2021.04.10
Node.js Cluster mode 모니터링  (0) 2021.04.04
Prometheus Node Exporter 모니터링  (0) 2021.03.20
Prometheus 란?  (0) 2021.03.14