NestJS APM with Elastic and Docker

eduardoconti

Eduardo Conti

Posted on January 4, 2024

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



  1. 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

💖 💪 🙅 🚩
eduardoconti
Eduardo Conti

Posted on January 4, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

NestJS APM with Elastic and Docker
nestjs NestJS APM with Elastic and Docker

January 4, 2024