DEV Community

Eduardo Conti
Eduardo Conti

Posted on

NestJS APM with Elastic and Docker

Introduction

Observability is a crucial aspect of modern software development, offering a comprehensive view into the performance and behavior of applications. As applications become more complex and distributed, understanding how they function in real-time becomes paramount. Application Performance Monitoring (APM) plays a pivotal role in observability, providing developers with the tools to track, analyze, and optimize their applications.

In this article, we'll delve into the implementation of APM with NestJS and Elasticsearch. By harnessing the power of these technologies, developers can gain valuable insights into their application's health, identify bottlenecks, and enhance overall performance.

Pre requirements

  • NestJs CLI
  • Docker

1. Init NestJS app

$ npm i -g @nestjs/cli
$ nest new apm-example

2. Add elastic-apm-node lib

$ yarn add elastic-apm-node

3. Create env file

.env
ELASTIC_APM_SERVICE_NAME=apm-example
ELASTIC_APM_SERVER_URL=http://apm-server:8200

4. Add @nestjs/config

$ yarn add @nestjs/config

5. Import ConfigModule

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApmModule } from './apm/apm.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ApmModule,
    ConfigModule.forRoot({
      envFilePath: '.env',
      isGlobal: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Enter fullscreen mode Exit fullscreen mode

6. Compiler options

Add compiler options configurations for correct functioning of the agent
Elastic config agent with ts
tsconfig.json
"esModuleInterop": true,
"moduleResolution": "node"

7. Create apm module and service

$ nest g module apm
$ nest g service apm

apm.module.ts

import { Module } from '@nestjs/common';
import { ApmService } from './apm.service';

@Module({
  providers: [ApmService],
})
export class ApmModule {}

Enter fullscreen mode Exit fullscreen mode

apm.service.ts

import { Injectable } from '@nestjs/common';
import 'elastic-apm-node/start';

@Injectable()
export class ApmService {}

Enter fullscreen mode Exit fullscreen mode

8. Create files Dockerfile and docker-compose

Create Dockerfile and docker-compose.yml in src folder:
Dockerfile

FROM node:18.12.0-alpine AS build

WORKDIR /app

COPY package.json yarn.lock ./

RUN yarn 

COPY . .

RUN yarn build

FROM node:18.12.0-alpine

WORKDIR /app

COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/yarn.lock ./yarn.lock

EXPOSE ${PORT}

CMD ["yarn", "start:prod"]
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

version: '3.7'

services:
  apm-example:
    hostname: apm-example
    restart: on-failure
    build:
      context: .
      dockerfile: ./Dockerfile
    volumes:
      - .:/app
    env_file:
      - .env
    ports:
      - "3000:3000"
    command: npm run start:dev
    depends_on:
      - apm-server
    cpus: 1
    mem_limit: 1024M
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    healthcheck:
      interval: 30s
      retries: 10
      test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"'
  kibana:
    image: docker.elastic.co/kibana/kibana:7.12.1
    container_name: kibana
    ports:
      - 5601:5601
    environment:
      ELASTICSEARCH_URL: http://elasticsearch:9200
    cpus: 0.1
    mem_limit: 256M 
    depends_on:
      elasticsearch:
        condition: service_healthy
    healthcheck:
      interval: 40s
      retries: 20
      test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:5601/api/status
  apm-server:
    image: docker.elastic.co/apm/apm-server:7.17.16
    depends_on:
      elasticsearch:
        condition: service_healthy
      kibana:
        condition: service_healthy
    cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"]
    cap_drop: ["ALL"]
    ports:
    - 8200:8200
    command: >
       apm-server -e
         -E apm-server.rum.enabled=true
         -E setup.kibana.host=kibana:5601
         -E setup.template.settings.index.number_of_replicas=0
         -E apm-server.kibana.enabled=true
         -E apm-server.kibana.host=kibana:5601
         -E output.elasticsearch.hosts=["elasticsearch:9200"]
    healthcheck:
      interval: 10s
      retries: 12
      test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:8200/
    cpus: 0.1
    mem_limit: 256M 
volumes:
  es-data:
Enter fullscreen mode Exit fullscreen mode

9. Run docker containers

$ docker-compose up --build -d --timeout 180

10. GET /

access the application endpoint to collect the first metrics
http://localhost:3000

11. Open elastic app

http://localhost:5601

you should see something similar to this

Elastic APM

Elastic APM Trace

12. Collect docker metrics with Metricbeat

http://localhost:5601/app/home#/tutorial/dockerMetrics

After install and configure Metricbeat, you should run
$ sudo metricbeat setup
$ sudo service metricbeat start

you should see something similar to this

Docker metrics received data

Click in Docker metrics dashboard

you should see something similar to this

Docker metrics dashboard

Now you can explore Elastic metrics, happy learning!

repo: apm-example

Top comments (0)