# How to deploy Nest.js microservices using Kubernetes

> The aim of the project is to use Nest.js to build a microservice project that communicate via gRPC and a gateway service to expose API, use RabbitMQ to handle message queues, and ELK for log management. Finally, Docker and Kubernetes will be used for building and deploying those services.

To view the full example, please visit [learn-backend](https://github.com/yancey-learning-tech/learn-backend) on GitHub.

## Code Structure

```bash
.
├── apps
│   ├── gateway
│   │   ├── src
│   │   │   ├── gateway.module.ts
│   │   │   ├── gateway.controller.ts
│   │   │   └── main.ts
│   │   └── tsconfig.app.json
│   ├── product
│   │   ├── src
│   │   │   ├── main.ts
│   │   │   ├── product.module.ts
│   │   │   └── product.controller.ts
│   │   └── tsconfig.app.json
├── docker
│   ├── Dockerfile.gateway
│   └── Dockerfile.product
├── k8s
│   ├── gateway-deployment.yaml
│   └── product-deployment.yaml
├── proto
│   └── product.proto
├── types
│   └── proto
│       └── product.ts
├── tsconfig.build.json
├── tsconfig.json
├── nest-cli.json
├── package.json
├── pnpm-lock.yaml
```

## Using Nest.js to create monorepo

First, follow the [Workspaces](https://docs.nestjs.com/cli/monorepo) guide to create a Nest.js monorepo project.

```ts
nest new gateway
cd gateway
nest generate app product
```

## Using gRPC for communication

A microservice is essentially an application that uses a different transport layer than HTTP. We can use gRPC, Thrift, or even just TCP as the transport layer. In this case, we will use gRPC.\
[gRPC](https://grpc.io/) is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

![image.png](https://static.yancey.app/aighp64f-1731989931539.png)

### Protocol Buffers

Since microservice code can be written in various programming languages, we need a language-neutral, platform-neutral, and extensible mechanism for serializing structured data. By default, gRPC uses [Protocol Buffers](https://protobuf.dev/).\
The follows code defines protobuf to the `product` microservice.

```text
syntax = "proto3";
package product;
message Product {
  string id = 1;
  string name = 2;
  string description = 3;
  double unitPrice = 4;
  int32 inventoryQuantity = 5;
  string createdAt = 6;
  string updatedAt = 7;
}
message CreateProductRequest {
  string name = 1;
  string description = 2;
  double unitPrice = 3;
  int32 inventoryQuantity = 4;
}
message CreateProductResponse { Product product = 1; }
message GetProductRequest { string id = 1; }
message GetProductResponse { Product product = 1; }
message UpdateProductRequest {
  string id = 1;
  string name = 2;
  string description = 3;
  double unitPrice = 4;
  int32 inventoryQuantity = 5;
}
message UpdateProductResponse { Product product = 1; }
message DeleteProductRequest { string id = 1; }
message DeleteProductResponse { bool success = 1; }
service ProductService {
  rpc CreateProduct(CreateProductRequest) returns (CreateProductResponse);
  rpc GetProduct(GetProductRequest) returns (GetProductResponse);
  rpc UpdateProduct(UpdateProductRequest) returns (UpdateProductResponse);
  rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse);
}
```

### protoc

Since this is a TypeScript project, it is better to transform the protobuf definitions into TypeScript interfaces. We use `ts-protoc` to compile protobuf files to TypeScript interfaces.\
First, you need to install `protoc` on your computer. If you are using macOS, simply run:

```bash
brew install protobuf
```

Next, add the follows command to the scripts section of your package.json:

```bash
"proto-ts": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./types ./proto/*.proto --ts_proto_opt=nestJs=true,addGrpcMetadata=true,addNestjsRestParameter=true,outputEncodeMethods=false,outputJsonMethods=false,outputClientImpl=false"
```

This command not only generates TypeScript interfaces, but also generates controller interface for Nest.js.

```ts
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
//   protoc-gen-ts_proto  v2.2.5
//   protoc               v5.28.3
// source: proto/product.proto
/* eslint-disable */
import { Metadata } from '@grpc/grpc-js'
import { GrpcMethod, GrpcStreamMethod } from '@nestjs/microservices'
import { Observable } from 'rxjs'
export const protobufPackage = 'product'
export interface Product {
  id: string
  name: string
  description: string
  unitPrice: number
  inventoryQuantity: number
  createdAt: string
  updatedAt: string
}
export interface CreateProductRequest {
  name: string
  description: string
  unitPrice: number
  inventoryQuantity: number
}
export interface CreateProductResponse {
  product: Product | undefined
}
export interface GetProductRequest {
  id: string
}
export interface GetProductResponse {
  product: Product | undefined
}
export interface UpdateProductRequest {
  id: string
  name: string
  description: string
  unitPrice: number
  inventoryQuantity: number
}
export interface UpdateProductResponse {
  product: Product | undefined
}
export interface DeleteProductRequest {
  id: string
}
export interface DeleteProductResponse {
  success: boolean
}
export const PRODUCT_PACKAGE_NAME = 'product'
export interface ProductServiceClient {
  createProduct(
    request: CreateProductRequest,
    metadata: Metadata,
    ...rest: any
  ): Observable<CreateProductResponse>
  getProduct(
    request: GetProductRequest,
    metadata: Metadata,
    ...rest: any
  ): Observable<GetProductResponse>
  updateProduct(
    request: UpdateProductRequest,
    metadata: Metadata,
    ...rest: any
  ): Observable<UpdateProductResponse>
  deleteProduct(
    request: DeleteProductRequest,
    metadata: Metadata,
    ...rest: any
  ): Observable<DeleteProductResponse>
}
export interface ProductServiceController {
  createProduct(
    request: CreateProductRequest,
    metadata: Metadata,
    ...rest: any
  ):
    | Promise<CreateProductResponse>
    | Observable<CreateProductResponse>
    | CreateProductResponse
  getProduct(
    request: GetProductRequest,
    metadata: Metadata,
    ...rest: any
  ):
    | Promise<GetProductResponse>
    | Observable<GetProductResponse>
    | GetProductResponse
  updateProduct(
    request: UpdateProductRequest,
    metadata: Metadata,
    ...rest: any
  ):
    | Promise<UpdateProductResponse>
    | Observable<UpdateProductResponse>
    | UpdateProductResponse
  deleteProduct(
    request: DeleteProductRequest,
    metadata: Metadata,
    ...rest: any
  ):
    | Promise<DeleteProductResponse>
    | Observable<DeleteProductResponse>
    | DeleteProductResponse
}
export function ProductServiceControllerMethods() {
  return function (constructor: Function) {
    const grpcMethods: string[] = [
      'createProduct',
      'getProduct',
      'updateProduct',
      'deleteProduct'
    ]
    for (const method of grpcMethods) {
      const descriptor: any = Reflect.getOwnPropertyDescriptor(
        constructor.prototype,
        method
      )
      GrpcMethod('ProductService', method)(
        constructor.prototype[method],
        method,
        descriptor
      )
    }
    const grpcStreamMethods: string[] = []
    for (const method of grpcStreamMethods) {
      const descriptor: any = Reflect.getOwnPropertyDescriptor(
        constructor.prototype,
        method
      )
      GrpcStreamMethod('ProductService', method)(
        constructor.prototype[method],
        method,
        descriptor
      )
    }
  }
}
export const PRODUCT_SERVICE_NAME = 'ProductService'
```

## Starting RabbitMQ Docker Container

[RabbitMQ](https://www.rabbitmq.com/) is a reliable and mature messaging and streaming broker, which is easy to deploy on cloud environments, on-premises, and on your local machine. We use `docker compose` to start a simply container.

```yaml
services:
  rabbitmq:
    image: rabbitmq:3-management
    restart: always
    container_name: rabbitmq
    ports:
      - 5672:5672
      - 15672:15672
    environment:
      - RABBITMQ_DEFAULT_USER=root
      - RABBITMQ_DEFAULT_PASS=password
    volumes:
      - /var/lib/docker/volumes/rabbitmq:/var/lib/rabbitmq
```

Then, visit http://localhost:15672/ to open the management platform.

![rabbitmq management platform](https://static.yancey.app/92i5z46c-1732004913576.png)

## Starting ELK Docker Container

The ELK Stack is a powerful suite of open-source tools designed for searching, analyzing, and visualizing log data in real-time. It consists of three main components: Elasticsearch, a search and analytics engine; Logstash, a data processing pipeline that collects, parses, and stores logs; and Kibana, a visualization tool that allows users to explore and interact with data stored in Elasticsearch. Together, these tools provide a robust solution for managing and analyzing large volumes of log data from various sources, making it easier to monitor and troubleshoot applications and infrastructure.\
First, create `logstash/pipeline/logstash.conf` and add the configuration as follows:

```bash
input {
  beats {
    port => 5000
  }
}
filter {
  # Add your filters here
}
output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "%{[@metadata][beat]}-%{+YYYY.MM.dd}"
  }
  stdout { codec => rubydebug }
}
```

Then, run the follows `docker-compose.yaml` file via `docker compose up -d`:

```yaml
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.16.0
    container_name: elasticsearch
    restart: 'always'
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
      - "9300:9300"
    networks:
      - elk
  logstash:
    image: docker.elastic.co/logstash/logstash:8.16.0
    container_name: logstash
    restart: 'always'
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
    ports:
      - "5044:5044"
      - "9600:9600"
    networks:
      - elk
    depends_on:
      - elasticsearch
  kibana:
    image: docker.elastic.co/kibana/kibana:8.16.0
    container_name: kibana
    restart: 'always'
    environment:
      - ELASTICSEARCH_URL=http://elasticsearch:9200
    ports:
      - "5601:5601"
    networks:
      - elk
    depends_on:
      - elasticsearch
networks:
  elk:
    driver: bridge
volumes:
  esdata:
    driver: local
```

## Nest.js Microservice

Instantiating a microservice is as straightforward as setting up a normal HTTP service. Simply follow the [Nest.js Microservices](https://docs.nestjs.com/microservices/basics) instructions. In this case, we also integrateELK and RabbitMQ.

### apps/product/main.ts

There are two important points to note:

1. If `product.proto` imports other `.proto` files, such as `common.proto`, you must also include the `common.proto` file. There are two ways to address this:

Option 1: Directly import `common.proto` into the `protoPath`:    

```bash
protoPath: [join(process.cwd(), 'proto/product.proto'), join(process.cwd(), 'proto/common.proto')],
```

Option 2: Load the entire `proto` directory.

```bash
loader: {
  includeDirs: [process.cwd(), 'proto']
}
```

2. Configure the service to listen on `0.0.0.0:10087` instead of `localhost:10087`. This is because each Kubernetes Pod operates within its own network namespace, and `localhost` just refers to the Pod’s loopback address. Other Pods cannot communicate with it using `localhost`.

```ts
import { ecsFormat } from '@elastic/ecs-winston-format'
import { NestFactory } from '@nestjs/core'
import { MicroserviceOptions, Transport } from '@nestjs/microservices'
import { WinstonModule } from 'nest-winston'
import { join } from 'path'
import * as winston from 'winston'
import { ElasticsearchTransport } from 'winston-elasticsearch'
import { ProductModule } from './product.module'
// Using winston and ELK instead of the official logger
export const logger = WinstonModule.createLogger({
  instance: winston.createLogger({
    level: 'info',
    format: ecsFormat(),
    transports: [
      new ElasticsearchTransport({
        level: 'info',
        indexPrefix: 'learn-backend-logging', // Remember this line and we'll use it to config Kibana Data View
        indexSuffixPattern: 'YYYY-MM-DD',
        clientOpts: {
          node: 'http://0.0.0.0:9200',
          maxRetries: 5,
          requestTimeout: 10000,
          sniffOnStart: false,
          tls: { rejectUnauthorized: false }
        }
      })
    ]
  })
})
async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    ProductModule,
    {
      transport: Transport.GRPC,
      options: {
        package: 'product',
        protoPath: join(process.cwd(), 'proto/product.proto'),
        url: '0.0.0.0:10087'
      },
      logger
    }
  )
  await app.listen()
}
bootstrap()
```

### apps/product/product.module.ts

```ts
import { ConfigModule } from '@app/config'
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'
import { Logger, Module } from '@nestjs/common'
import { ProductController } from './product.controller'
import { ProductService } from './product.service'
@Module({
  imports: [
    ConfigModule,
    RabbitMQModule.forRoot(RabbitMQModule, {
      exchanges: [{ name: 'MQ_SERVICE', type: 'topic' }],
      uri: 'amqp://root:password@0.0.0.0:5672',
      connectionInitOptions: { wait: false },
      deserializer: (message: Buffer) => {
        return message
      },
      serializer: (msg: unknown) => {
        const encodedMessage = JSON.stringify(msg)
        return Buffer.from(encodedMessage)
      }
    })
  ],
  controllers: [ProductController],
  providers: [ProductService, Logger]
})
export class ProductModule {}
```

### apps/product/product.controller.ts

After running the `pnpm run proto-ts` command, you will generate the `ProductServiceController` interface. You can implement it and create the template code as follows:

```ts
import { AmqpConnection, RabbitSubscribe } from '@golevelup/nestjs-rabbitmq'
import { Controller, Logger } from '@nestjs/common'
import { GrpcMethod } from '@nestjs/microservices'
import { Observable } from 'rxjs'
import {
  CreateProductRequest,
  CreateProductResponse,
  DeleteProductRequest,
  DeleteProductResponse,
  GetProductRequest,
  GetProductResponse,
  ProductServiceController,
  UpdateProductRequest,
  UpdateProductResponse
} from 'types/proto/product'
@Controller()
export class ProductController implements ProductServiceController {
  constructor(private readonly logger: Logger, private readonly amqpConnection: AmqpConnection,) {}
  @GrpcMethod('ProductService', 'CreateProduct')
  createProduct(
    request: CreateProductRequest
  ): Promise<CreateProductResponse> | CreateProductResponse {
     throw new Error('Method not implemented.')
  }
  @GrpcMethod('ProductService', 'GetProduct')
  getProduct(
    request: GetProductRequest
  ):
    | Promise<GetProductResponse>
    | Observable<GetProductResponse>
    | GetProductResponse {
    // record log to elasticsearch
    this.logger.log('call getProduct rpc')
    // publish rabbitmq message
    this.amqpConnection.publish(
      'MQ_SERVICE',
      'get_product_by_id',
      request
    )
    return {}
  }
  updateProduct(
    request: UpdateProductRequest
  ):
    | Promise<UpdateProductResponse>
    | Observable<UpdateProductResponse>
    | UpdateProductResponse {
    throw new Error('Method not implemented.')
  }
  deleteProduct(
    request: DeleteProductRequest
  ):
    | Promise<DeleteProductResponse>
    | Observable<DeleteProductResponse>
    | DeleteProductResponse {
    throw new Error('Method not implemented.')
  }
  @RabbitSubscribe({
    exchange: 'MQ_SERVICE',
    routingKey: 'get_product_by_id'
  })
  public async subscribe(content: Buffer, msg: unknown) {
    const message = JSON.parse(content.toString())
    console.log(message, msg)
  }
}
```

## Nest.js Gateway

The Gateway service is simply a HTTP service, we want to use it call Product microservice service and expose the APIs.

### apps/gateway/main.ts

```ts
import { NestFactory } from '@nestjs/core'
import { GatewayModule } from './gateway.module'
async function bootstrap() {
  const app = await NestFactory.create(GatewayModule)
  await app.listen(10086)
}
bootstrap()
```

### apps/gateway/gateway.module.ts

To call the Product microservice, you need to register it at first.\
Kubernetes uses an internal `DNS name` system for service discovery. We will name the Product microservice service `product-service` at the follows chapter. In production, you can reach the Product microservice using `product-service:10087`, while in development, you just use `0.0.0.0:10087`.

```ts
import { ConfigModule } from '@app/config'
import { Logger, Module } from '@nestjs/common'
import { ClientsModule, Transport } from '@nestjs/microservices'
import { join } from 'path'
import { GatewayController } from './gateway.controller'
@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'ORDER_SERVICE',
        transport: Transport.GRPC,
        options: {
          url:
            process.env.NODE_ENV === 'production'
              ? 'product-service:10087' // For Kubernetes production environment
              : '0.0.0.0:10087', // For local development environment 
          package: 'product',
          protoPath: join(process.cwd(), 'proto/product.proto'),
          loader: {
            includeDirs: [process.cwd(), 'proto']
          }
        }
      }
    ]),
  ],
  providers: [GatewayController]
})
export class GatewayModule {}
```

### apps/gateway/gateway.controller.ts

```ts
import { Metadata } from '@grpc/grpc-js'
import {
  Controller,
  Get,
  Inject,
  Logger,
  OnModuleInit,
  Param
} from '@nestjs/common'
import { ClientGrpc } from '@nestjs/microservices'
import { Observable } from 'rxjs'
import {
  GetProductResponse,
  ProductServiceController
} from 'types/proto/product'
@Controller()
export class GatewayController implements OnModuleInit {
  private productService: ProductServiceController
  constructor(@Inject('PRODUCT_SERVICE') private productClient: ClientGrpc) {}
  onModuleInit() {
    this.productService =
      this.productClient.getService<ProductServiceController>('ProductService')
  }
  @Get(':id')
  getProduct(
    @Param('id') id: string
  ):
    | Promise<GetProductResponse>
    | Observable<GetProductResponse>
    | GetProductResponse {
    const metadata = new Metadata()
    return this.productService.getProduct(
      {
        id
      },
      metadata
    )
  }
}
```

## Dockerfile

With the code phrase completed, we are now writing Dockerfiles for the Gateway service and the Product service.

### docker/Dockerfile.gateway

```yaml
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm
RUN pnpm install
COPY . .
RUN pnpm build gateway
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist/apps/gateway ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
COPY proto ./proto
ENV NODE_ENV=production
EXPOSE 10086
CMD ["node", "dist/main"]
```

### docker/Dockerfile.product

```yaml
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm
RUN pnpm install
COPY . .
RUN pnpm build product
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist/apps/product ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
COPY proto ./proto
ENV NODE_ENV=production
EXPOSE 10087
CMD ["node", "dist/main"]
```

Finally, run the follows commands in the root path:

```bash
docker build -t commerce/gateway-service:0.0.1 -f docker/Dockerfile.gateway .
docker build -t commerce/product-service:0.0.1 -f docker/Dockerfile.product .
```

![Docker Images](https://static.yancey.app/5fk50ukj-1732005307311.png)

## Set up Kubernetes Dashboard

Firstly, install `helm` on your computer. If you are using macOS, simply run:

```bash
brew install helm
```

Then, install Kubernetes Dashboard package:

```bash
# Add kubernetes-dashboard repository
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
# Deploy a Helm Release named "kubernetes-dashboard" using the kubernetes-dashboard chart
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard
```

![k8s-1.png](https://static.yancey.app/heaaswp4-1731642990477.png)

Once Kubernetes Dashboard is installed, you can verify it using the follows command:

```bash
kubectl get svc -n kubernetes-dashboard
```

![k8s-2.png](https://static.yancey.app/z4tvhtp1-1731643006669.png)

### Creating Account and Token

>  Follow the [instruction](https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md).\
> First, create a Kubernetes manifest file to create a service account. For example: 

```bash
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system
```

Next, apply the above configuration to the Kubernetes cluster.

```bash
kubectl create -f k8s-dashboard-account.yaml
```

Next, generate a token using the follows command:

```bash
kubectl -n kube-system create token admin-user
```

### Starting Kubernetes Dashboard Server

```bash
kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443
```

Finally, fill out your token on the field.

![k8s-3.png](https://static.yancey.app/e8o4uh2v-1731642976338.png)

## Kubernetes Deployment

### k8s/gateway-deployment.yaml

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gateway-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: gateway
  template:
    metadata:
      labels:
        app: gateway
    spec:
      containers:
        - name: gateway
          image: commerce/gateway-service:0.0.1
          ports:
            - containerPort: 10086
---
apiVersion: v1
kind: Service
metadata:
  name: gateway-service
spec:
  selector:
    app: gateway
  ports:
    - port: 10086
      targetPort: 10086
  type: LoadBalancer
```

### k8s/product-deployment.yaml

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: product
  template:
    metadata:
      labels:
        app: product
    spec:
      containers:
        - name: product
          image: commerce/product-service:0.0.1
          ports:
            - containerPort: 10087
          env:
            - name: NODE_ENV
              value: production
---
apiVersion: v1
kind: Service
metadata:
  name: product-service
spec:
  selector:
    app: product
  ports:
    - port: 10087
      targetPort: 10087
  type: ClusterIP
```

Finally, run `kubectl apply -f k8s/` to deploy your services.

![image.png](https://static.yancey.app/p99e9ux4-1732006033687.png)

## Kibana Configuration

Submit some logs in advance to let ElasticSearch generate **index**, visit https://localhost:9200/_cat/indices?v to check whether your index successfully added.

![show all index](https://static.yancey.app/yv0nuib6-1732006146359.png)

Visit http://localhost:5601/app/management/kibana/dataViews to create data view, the `index pattern` matches the `indexPrefix` you configured.

![create data view](https://static.yancey.app/26aeg366-1732006176923.png)

Finally visit http://localhost:5601/app/discover and select your data view, and the logs will be displayed on the right panel.

![display your logs](https://static.yancey.app/5qfm13po-1732006203729.png)

To install the kube-prometheus-stack, you can use a Helm chart. This is a Kubernetes monitoring solution that integrates Prometheus, Alertmanager, Grafana, and other tools. Below are the installation steps:

## Add the Prometheus Community Repository

Add and update the Helm repository:

```bash
helm repo add prometheus-community <https://prometheus-community.github.io/helm-charts>
helm repo update
```

### Install kube-prometheus-stack

Choose a namespace (it is recommended to create a new one):

```bash
kubectl create namespace monitoring
```

Install the kube-prometheus-stack:

```bash
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -n monitoring --set prometheus-node-exporter.hostRootFsMount.enabled=false
```

### Verify Installation

Check if all related pods are running correctly:

```bash
kubectl get pods -n monitoring
```

![image.png](https://static.yancey.app/74wxe9kv-1732106064278.png)

If the pods are running, services like Grafana and Prometheus will be exposed as ClusterIP. Use the following command to view the services:

```bash
kubectl get svc -n monitoring
```

![image.png](https://static.yancey.app/l7klkfnb-1732106006361.png)

### Access Grafana

The default username is `admin`, and you can retrieve the `password` with this command:

```bash
kubectl get secret -n monitoring kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 --decode
```

Expose the Grafana service (e.g., using kubectl port-forward):

```bash
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 9999:80
```

Then, visit http://localhost:9999 and log in with the credentials retrieved in the previous step.

![image.png](https://static.yancey.app/xp3nwz4d-1732106384649.png)

## Summary

This is an MVP version of a Nest.js microservices project, covering the basics of coding, building, and deploying. However, there’s still a long way to go. Challenges such as security, high availability, and performance optimization remain to be addressed. If you have ideas or suggestions, feel free to reach out to me [@YanceyOfficial](https://twitter.com/YanceyOfficial) on X or comment this article directly. Your input is always welcome as we work towards making this project more robust and production-ready.


---

Source: https://yanceyleo.com/post/6dcea851-2ccc-48ca-bbd3-d18cb4e40cd9
Published: 2024-11-15
Tags: Backend, Kubernetes