node.jsdockerdocker-composedistributed-computing

Problem Writing in file in a Docker Nodejs Image


I'm doing an activity where I need to write information to a file when an image requires access to this resource, as shown below. This is how my folders are organized. node2 requests access to node1.

enter image description here

The code below is responsible for applying a type of mutual exclusion, preventing simultaneous access to the file. The problem is when access is granted, nothing is being written to the file, despite console.log(logEntry) showing the result that was supposed to be written

const sharedFilePath = path.join(__dirname, 'data', 'shared-file.txt');

let isResourceLocked = false;

app.get('/request-access/:deviceId', (req, res) => {
  const deviceId = req.params.deviceId;
  
  if (!isResourceLocked) {
    isResourceLocked = true;

    // Registrar no arquivo compartilhado
    const timestamp = new Date().toISOString();
    const logEntry = `Device ${deviceId} accessed the resource at ${timestamp}\n`;
    console.log(logEntry)
   
    fs.appendFileSync(sharedFilePath, logEntry, (err) => {
      if (err) return console.log(err);
      console.log('Log entry added to shared file:', logEntry);
    });
    

    // Liberar o recurso após um período simulado (por exemplo, 5 segundos)
    setTimeout(() => {
      isResourceLocked = false;
    }, 5000);

    res.send(`Access granted to Device ${deviceId}`);
  } else {
    res.send(`Resource is currently locked. Device ${deviceId} must wait.`);
  }
});

In this code I require access to write to the file

app.get('/', async (req, res) => {
  const deviceId = 'Node1'; // Identificador único para cada nó
  try {
    const response = await axios.get(`${coordenadorUrl}/request-access/${deviceId}`);
    console.log(coordenadorUrl)
    res.send(response.data);
  } catch (error) {
    res.send('Error requesting access to the resource.');
  }
});

Dockerfile of every node is like this

FROM node:14

WORKDIR /usr/app

COPY package*.json ./

RUN npm install

RUN mkdir -p data

COPY . .

CMD ["npm", "start"]

docker-compose.ymml

version: '3.8'
services:
  app:
    build:
      context: ./node1
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - EXTERNAL_PORT=3000
    volumes:
      - ./node1/data:/usr/src/app/data
    networks:
      - my-network # Adiciona o serviço à rede personalizada

  app2:
    build:
      context: ./node2
      dockerfile: Dockerfile
    ports:
      - "3002:3002"
    environment:
      - EXTERNAL_PORT=3002
    networks:
      - my-network # Adiciona o serviço à rede personalizada

networks:
  my-network: # Defina uma rede personalizada
    driver: bridge

Solution

  • You need to share the same volume with both of the containers. Suppose that you have a simple add that logs to a file:

    🗎 app.js

    const fs = require('fs');
    
    const path = '/usr/src/app/data/shared-log.txt';
    
    const id = process.env.ID || 'Default';
    
    const appendContent = () => {
        const now = new Date();
        const timestamp = now.toISOString();
        
        const contentToAdd = `${timestamp}: Data from: ${id}\n`;
    
        fs.appendFile(path, contentToAdd, err => {
            if (err) {
            console.error('An error occurred:', err);
            return;
            }
            console.log('Line appended successfully');
        });
    };
    
    setInterval(appendContent, 5000);
    

    Wrap that up in a Docker image.

    🗎 Dockerfile

    FROM node:14
    
    WORKDIR /usr/app
    
    COPY package*.json ./
    
    RUN npm install
    
    RUN mkdir -p data
    
    COPY . .
    
    CMD ["node", "app.js"]
    

    There's a minimal package.json.

    Now a Docker Compose configuration that creates two instances of the container.

    🗎 docker-compose.yml

    version: '3.8'
    services:
      first:
        build:
          context: .
          dockerfile: Dockerfile
        environment:
          - ID=first
        volumes:
          - .:/usr/src/app/data
          
      second:
        build:
          context: .
          dockerfile: Dockerfile
        environment:
          - ID=second
        volumes:
          - .:/usr/src/app/data
    

    Note that both of those have the same location volume mounted and that the script is writing to the mounted location.

    When you bring up Docker Compose you'll find that the two containers write to the same log file (which will be located in the current directory on the host).

    enter image description here

    🚨 This does not set up locking to prevent simultaneous writing to the file. However it does demonstrate the general principle of how you can set up two containers to write to the same shared file.