1

I created a really simple bookstore with a Books, Customer, and a main service. This particular problem involves the main and books service.

I'm currently making a gRPC request called: "createBook", which creates a book in our DB, and also console logs.

When running the gRPC server (booksServer) without docker, the process runs smoothly.

But as soon as I use docker, it seems as if a gRPC request doesn't go into the gRPC server...

By "using docker" I mean using docker to run the booksServer. (As shown below)

Result: Without Docker

As you can see, without docker, the request is fulfilled, and everything works as it should. 
Our gRPC client makes a call to the gRPC server (in which metadata is created) and the metadata is also sent back to the client. 
(Scroll down to see the gRPC server file with the method called "getBooks".)

enter image description here

booksServer (without docker) enter image description here

*** Notice the console logs in the booksServer!!! ***

Let me run the booksServer (with docker)

(Dockerfile below)

FROM node:12.14.0
WORKDIR /usr/src/app
COPY package*.json ./
COPY . /usr/src/app
RUN npm install
RUN npm install nodemon -g
EXPOSE 30043
CMD ["nodemon", "booksServer.js"



Here's my main service docker file too which initiates the request:

FROM node:12.14.0
WORKDIR /usr/src/app
COPY package*.json ./
COPY . /usr/src/app
# COPY wait-for-it.sh . 
# RUN chmod +x /wait-for-it.sh
RUN npm install
EXPOSE 4555
CMD ["node", "main.js"]

enter image description here enter image description here

^^^ Notice how when dockerfile is used to run booksServer
it doesn't go/run inside the booksServer file 
***It does NOT produce any console.logs when I fire off a gRPC requesst***

This is what the booksServer.js file looks like enter image description here

Heres the Books Stub

//use this for bookInitiator
const path = require('path');
const PROTO_PATH = path.join(__dirname, "../protos/books.proto");

const grpc = require("grpc");
const protoLoader = require("@grpc/proto-loader");

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  arrays: true
});

const BooksService = grpc.loadPackageDefinition(packageDefinition).BooksService;

// potential issues to fix 1) making localhost port dynamic 2) docker containerization may cause conflict

const client = new BooksService (
  "172.17.0.2:30043",
  grpc.credentials.createInsecure()
);

console.log("Creating stub inside booksStub");
module.exports = client;

Here's the gRPC Server file (with the binded ports).

// const PROTO_PATH = "../protos/books.proto";
const path = require('path');
const PROTO_PATH = path.join(__dirname, './protos/books.proto');
const grpc = require("grpc");
const protoLoader = require("@grpc/proto-loader");
const express = require("express");
const controller = require("./booksController.js");
const app = express();
app.use(express.json());

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  arrays: true,
});

const booksProto = grpc.loadPackageDefinition(packageDefinition);

const { v4: uuidv4 } = require("uuid");

const server = new grpc.Server();

server.addService(booksProto.BooksService.service, {
  CreateBook: (call, callback) => {
    console.log("call to CreateBook");

    //sample will take the call information from the client(stub)
    const book = {
      title: call.request.title,
      author: call.request.author,
      numberOfPages: call.request.numberOfPages,
      publisher: call.request.publisher,
      
      id: call.request.id,
    };

    controller.createBook(book);

    let meta = new grpc.Metadata();
    meta.add("response", "none");
    console.log("metadata in createBook...: ", meta);
    call.sendMetadata(meta);

    callback(
      null,
      //bookmodel.create
      {
        title: `completed for: ${call.request.title}`,
        author: `completed for: ${call.request.author}`,
        numberOfPages: `completed for: ${call.request.numberOfPages}`,
        publisher: `completed for: ${call.request.publisher}`,
        id: `completed for: ${call.request.id}`,
      }
    );
  },
  GetBooks: (call, callback) => {
    console.log("call to GetBooks");
    // read from database
    let meta = new grpc.Metadata();
    meta.add('response', 'none');
    call.sendMetadata(meta);

    controller.getBooks(callback);
  }
});

server.bind("0.0.0.0:30043", grpc.ServerCredentials.createInsecure());
console.log("booksServer.js running at 0.0.0.0:30043");
console.log("Inside Books Server!");
console.log("call from books server");

server.start();

horus.js (custom made simple tracing tool), grab trace grabs the journey of a certain request and sends it back to the gRPC client as metadata

const fs = require("fs");
const grpc = require("grpc");
const path = require("path");

class horus {
  constructor(name) {
    this.serviceName = name; // represents the name of the microservices
    this.startTime = null;
    this.endTime = null;
    this.request = {};
    this.targetService = null; // represents the location to which the request was made
    this.allRequests = []; // array which stores all requests
    this.timeCompleted = null;
    this.call;
  }

  static getReqId() {
    // primitive value - number of millisecond since midnight January 1, 1970 UTC
    // add service name/ initials to the beginning of reqId?
    return new Date().valueOf();
  }

  // start should be invoked before the request is made
  // start begins the timer and initializes the request as pending
  start(targetService, call) {
    this.startTime = Number(process.hrtime.bigint());
    this.request[targetService] = "pending"; // {books: 'pending', responseTime: 'pending'}
    this.request.responseTime = "pending";
    this.targetService = targetService;
    this.call = call;
    this.request.requestId = horus.getReqId();
  }
  // end should be invoked when the request has returned
  end() {
    this.endTime = Number(process.hrtime.bigint());
    this.request.responseTime = (
      (this.endTime - this.startTime) /
      1000000
    ).toFixed(3); //converting into ms.
    this.sendResponse();
    this.request.timeCompleted = this.getCurrentTime();
  }
  // grabTrace accepts inserts trace into request
  // trace represents the "journey" of the request
  // trace expects metaData to be 'none when the server made no additional requests
  // trace expects metaData to be the request object generated by the server otherwise
  // in gRPC, the trace must be sent back as meta data. objects should be converted with JSON.parse
  grabTrace(metaData) {
    //console.log("incoming meta data ", metaData);
    console.log("Inside Grab Trace Method.");
    console.log("Metadata inside grabTrace: ", metaData);
    if (metaData === "none" || metaData === undefined) this.request[this.targetService] = "none";
    else {
      metaData = JSON.parse(metaData);
      this.request[this.targetService] = metaData;
    }
    this.allRequests.push(this.request);
    this.sendResponse();
  }
  // displayRequests logs to the console all stored requests
  // setTimeout builds in deliberate latency since metadata may be sent before or after a request is done processing
  displayRequests() {
    console.log("\n\n");
    console.log("Logging all requests from : ", this.serviceName);
    this.allRequests.forEach((request) => {
      console.log("\n");
      console.log(request);
    });
    console.log("\n\n");
  }
  // sends response via metadata if service is in the middle of a chain
  sendResponse() {
    if (
      this.request.responseTime === "pending" ||
      this.request[this.targetService] === "pending" ||
      this.call === undefined
    )
      return;
    console.log("Inside send response");
    let meta = new grpc.Metadata();
    meta.add("response", JSON.stringify(this.request));
    console.log('meta in send response: ', meta)
    this.call.sendMetadata(meta);
  }
  writeToFile() {
    console.log("call to writeToFile");
    console.log("logging request obj ", this.request);
    let strRequests = "";
    for (let req of this.allRequests) {
      // First write to file - contains Total
      // subsequent - chained requests
      strRequests += `Request ID: ${req.requestId}\n`;
      strRequests += `"${
        Object.keys(req)[0]
      }" service -> Response received in ${Object.values(req)[1]} ms (Total)\n`;
      strRequests += `Timestamp: ${req.timeCompleted}\n`;
      // while we don't hit an empty object on the 1st key, go inside
      // add numbering in order for nested requests inside original?!
      let innerObj = Object.values(req)[0];
      while (innerObj !== "none") {
        strRequests += `"${
          Object.keys(innerObj)[0]
        }" service -> Response received in ${Object.values(innerObj)[1]} ms\n`;
        strRequests += `Timestamp: ${innerObj.timeCompleted}\n`;
        innerObj = Object.values(innerObj)[0];
      }
      strRequests +=
        "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
    }
    console.log('strRequests', strRequests)
    fs.writeFile(this.serviceName + 'data' + '.txt', strRequests, { flag: "a+" }, (err) => {
      if (err) {
        console.error(err);
      }
    }); //'a+' is append mode
  }

module.exports = horus;


main.js (initiates gRPC client request)

const path = require('path');
// const grpc = require("grpc");
const customersStub = require("./stubs/customersStub.js");
const booksStub = require("./stubs/booksStub.js");
const horusTracer = require(path.join(__dirname, "./horus/horus.js"));

//In master branch
console.log("Stub is Inside main service!!!");

const book = {
  title: "ITttttt",
  author: "Stephen King",
  numberOfPages: 666,
  publisher: "Random House",
  id: 200,
};

const bookId = {
  id: 200
}

const customer = {
  id: 123,
  name: "Lily",
  age: 23,
  address: "Blablabla",
  favBookId: 100
};

const customerId = {
  id: 123
}

let ht = new horusTracer("main");


function CreateBook () {
  ht.start('books')
  booksStub.CreateBook(book, (error, response) => {
    if (error) console.log("there was an error ", error);
    ht.end();
    ht.displayRequests();
    ht.writeToFile();
  }).on('metadata', (metadata) => {
    console.log("Before grab trace is invoked!");
    ht.grabTrace(metadata.get('response')[0]);
  });
}

}


CreateBook(); //Works

What I think is the issue.


Edit: murgatroid99 mentioned that it was a networking issue with docker!


~~~~~~~~~
I initially thought this was a networking issue, but I don't think it is 
because all my docker files are running on the default bridge network. 
So they all technically can communicate with one another...

Is it something wrong with nodemon interacting with Docker? 
Does the server not output the console logs...? 
Is the server actually running and working...?

Do I need a reverse proxy like nginx?

``
TheRoadLessTaken
  • 531
  • 2
  • 7
  • 15
  • The bug that you asked about in [your previous question](https://stackoverflow.com/q/63184625/159388) is clearly still present in the code you are asking about in this question, and the error that the client is throwing as a result is almost surely shadowing another error with more relevant information about the connectivity failure. You should really fix that bug first, or at least omit that function call entirely to ensure that the gRPC call completes and you can see why it fails. – murgatroid99 Aug 03 '20 at 20:56
  • In addition to that, you are asking about network connectivity problems, so you should include the code that binds a port on the server, and the code that establishes the connection on the client. – murgatroid99 Aug 03 '20 at 20:57
  • @murgatroid99 Just added the gRPC server (where the port is binded), and the client Stub file. Yeah, I was insistent that it was a docker issue because it was working without the dockerfile... I'm just a bit lost at the moment as to how to debug it at the moment. I don't understand why the client's requests isn't reaching the gRPC server when docker is activited. – TheRoadLessTaken Aug 03 '20 at 21:06

1 Answers1

3

The problem is that your server is binding to "127.0.0.1:30043". You say that you are running the docker images using the default bridge network. In that mode the docker image has a different (virtual) network than the host machine has, so its loopback address is different from the host machine's loopback address. To fix that, you can instead bind the server to 0.0.0.0:30043 or [::]:30043to bind to other network interfaces that the client can connect to from outside of the docker container.

For the same reason, connecting the client to localhost:30043 will not work: its "localhost" address also refers to the loopback interface within the docker container. You should instead replace "localhost" with the IP address of the server container.

Alternatively, as described in this question, you can network the docker containers in "host" mode so that they share the same network with the host machine.

murgatroid99
  • 19,007
  • 10
  • 60
  • 95
  • Thank you so much murgatroid! It fixed that issue (when I run 'node main.js', but not when I run docker image for main initiator which makes the gRPC client request). I still get the JSON error in grab trace (I added the code above in horus.js). It's like when I run dockerfile (of main.js) it doesn't reach into the gRPC server. – TheRoadLessTaken Aug 04 '20 at 02:03
  • I don't understand why you keep bringing up this JSON error. It's not relevant to the networking problem, and it's probably preventing you from seeing an error with more relevant information. I already explained what you need to do to fix it in the answer to your previous question. In any case, I made an edit to my answer to address the client problem. – murgatroid99 Aug 04 '20 at 02:20
  • Hey murgatroid, sorry, I'll try to be more precise. I did use the : for the client, and binded the server to 0.0.0.0: ... But networking issues oddly still persists. It currently seems to fail to connect to the server based on this error: Error: 14 UNAVAILABLE: failed to connect to all addresses at Object.exports.createStatusError at Object.onReceiveStatus ... I double checked for any extra bridges, and made sure every stub has their respective IP & port, and every server is modified for 0.0.0.0: ... – TheRoadLessTaken Aug 04 '20 at 03:47
  • I'm sorry. If this doesn't solve it, then I don't know. – murgatroid99 Aug 04 '20 at 06:09