docker-network-for-communication-between-grpc-client-and-server

Docker network for gRPC

profile-image
Sathya Molagoda
  • August 10, 2023
  • 4 min read
Play this article

Intro

In a recent project that I have been working on, when I was connecting two services using gRPC, every time the server would respond with a "14 UNAVAILABLE: Name resolution failed for target DNS:http://0.0.0.0:40000" error. it took a reasonable time for me to fix it. So in this article, I am going to discuss why it happened and how I was able to fix it.

This is a GRPC microservices project which had several microservices developed in NestJs. It provides GraphQl APIs for question management and data query. The question service can handle all the questions using gRPC. The API Gateway provides GraphQL APIs to the question service, it also route the requests to some other microservices based on user-entered information in an authentication module. The services that have been deployed that are integrated as a part of this ecosystem are as follows.

Project Configuration

These are configurations in the project which caused the error mentioned above.

Question Service

Main.ts file content.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '@nestjs/microservices';
import { join } from 'path';

const microserviceOptions = {
  transport: Transport.GRPC,
  options: {
    package: 'questionPackage',
    protoPath: join(__dirname, '../src/question/question.proto'),
    url: '0.0.0.0:40000',
  },
};

async function bootstrap() {
  const app = await NestFactory.createMicroservice(
    AppModule,
    microserviceOptions,
  );
  app.listen();
}
bootstrap();

API Gateway

import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Client, ClientGrpc, ClientOptions, Transport } from '@nestjs/microservices';
import { CreateQuestionInput } from './dto/create-question.input';
import { QuestionGrpcService } from './grpc.interface';
import { Question } from './entities/question.entity';
import { firstValueFrom } from 'rxjs';

@Injectable()
export class QuestionService implements OnModuleInit {
  private logger = new Logger('QuestionService');
  const microserviceOptions: ClientOptions = {
  transport: Transport.GRPC,
  options: {
    package: 'questionPackage',
    protoPath: join(__dirname, '../question/question.proto'),
    url: '0.0.0.0:40000',
  },
};
  @Client(microserviceOptions)
  private client: ClientGrpc;

  private questionGrpcService: QuestionGrpcService;

  onModuleInit() {
    this.questionGrpcService =
      this.client.getService<QuestionGrpcService>('QuestionService');
  }

  createQuestion(createQuestionInput: CreateQuestionInput) {
    return this.questionGrpcService.createQuestion(createQuestionInput);
  }

  async getQuestions(): Promise<Question[]> {
    let questions = [];
    const res = await firstValueFrom(this.questionGrpcService.getQuestions({}));
    questions = res.questionsResposes;
    return questions;
  }
}

Two applications deployed using the following docker commands.

docker run -dit -p 3000:3000  ${{ env.REGISTRY }}/api-gateway:latest
docker run -dit -p 40000:40000  ${{ env.REGISTRY }}/question-service:latest

I ran our GRPC project with the two docker images and it worked just fine. I then launched the apollo server client and received the following error message: "14 UNAVAILABLE: Name resolution failed for target DNS:http://0.0.0.0:40000".

This happens due to the isolation of docker containers and does not allow any communication between two containers through the host network. A container is the smallest unit of deployable software that can be run on docker. By default, container are connected to the default bridge network. In case you want to communicate two containers using host network you can use a host network for a container, by passing --network host to the docker run command.

Here, I'm using user define custom bridge network. I will create a docker bridge network and connect two containers to it. Learn more about docker network basics in this article: Docker network basics for beginners.

docker network create web_server --driver bridge

In order to use the created network we have to pass it in run commands. Refer the below example.

docker run -dit -p 3000:3000 --name question-service --network web_server ${{ env.REGISTRY }}/api-gateway:latest
docker run -dit -p 40000:40000 --name question-service --network web_server  ${{ env.REGISTRY }}/question-service:latest

After making these changes, we should change the host to container name.

const microserviceOptions: ClientOptions = {
  transport: Transport.GRPC,
  options: {
  package: 'questionPackage',
  protoPath: join(__dirname, '../question/question.proto'),
  url: 'dns:///question-service:40000',
}

Conclusion

To communicate between containers, Docker uses different network types. There are many types of networks in which a container can be connected to: bridge, host , overlay and etc. To address the communication between containers we have to decide which network type to use and make sure that the containers are on the same network. For this operation, we should provide the connection URL with "host" field set to docker://<container name>

Docker

Network

Docker Network

gRPC

DNS

Containerization