NestJS APM with Elastic and Docker
Eduardo Conti
Posted on January 4, 2024
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 {}
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 {}
apm.service.ts
import { Injectable } from '@nestjs/common';
import 'elastic-apm-node/start';
@Injectable()
export class ApmService {}
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"]
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:
- 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
you should see something similar to this
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
Click in Docker metrics dashboard
you should see something similar to this
Now you can explore Elastic metrics, happy learning!
repo: apm-example
Posted on January 4, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.