How to get docker mapped ports from node.js application?

前端 未结 4 680
無奈伤痛
無奈伤痛 2021-02-06 05:44

I would like to get mapped port from inside node.js application.

ex.

docker-compose:

my-app:
    build:
        context: ./my-app
           


        
相关标签:
4条回答
  • 2021-02-06 05:55

    So the first thing is that currently, docker doesn't give the metadata information inside the container in any direct manner. There are workarounds to the same though

    Now passing a docker socket inside your container is something that you can live with when it concerns a development environment but not prod environment

    So what you want to do is build your own metadata service and then let it give the information back to your container without exposing the docker socket itself.

    Now to get this metadata you can use two approaches

    Metadata service based on container id

    In this case, you create a simple endpoint like /metadata/container_id and then return the data you want.

    This service can run on the main host itself or on the container which has the docker socket mapped.

    Now in your NodeJS service, you will get the current container ID and then use the same to call this service. This can be done in different ways as listed in below thread

    Docker, how to get container information from within the container?

    The data returned you can restrict, in case I only want ports to be exposed, I will only return the detail of the ports only.

    The problem with this approach is that you are still letting the service figure out its own container id and also at the same time trusting that it is sending the correct container id

    Metadata service without passing any information

    To get metadata information without passing information, we need to identify which pad made a call to the metadata server

    This can be identified by using the source IP. Then we need to identify which container belongs to this IP. Once we have the container ID, we can return the metadata.

    Below is a simple bash script that does the same

    #!/bin/bash
    CONTAINER_IP=$SOCAT_PEERADDR
    CONTAINER_ID=$(docker ps -q | xargs docker inspect -f '{{.Id}}|{{range .NetworkSettings.Networks}}{{.IPAddress}}|{{end}}' | grep "|$CONTAINER_IP|" | cut -d '|' -f 1)
    echo -e "HTTP/1.1 200 OK\r\nContent-Type: application/json;\r\nServer: $SOCAT_SOCKADDR:$SOCAT_SOCKPORT\r\nClient: $SOCAT_PEERADDR:$SOCAT_PEERPORT\r\nConnection: close\r\n";
    METADATA=$(docker inspect -f '{{ json .NetworkSettings.Ports }}' $CONTAINER_ID)
    echo $METADATA
    

    Now to convert this to a web server we can use socat.

    sudo socat -T 1 TCP-L:10081,pktinfo,reuseaddr,fork EXEC:./return_metadata.sh
    

    Now inside the container when we call this metadata server

    root@a9cf6dabdfb4:/# curl 192.168.33.100:10081
    {"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"}]}
    

    and the docker ps for the same

    CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
    a9cf6dabdfb4        nginx                 "nginx -g 'daemon of…"   3 hours ago         Up 3 hours          0.0.0.0:8080->80/tcp   nginx
    

    Now how you create such a metadata server is upto you. You can use any approach

    • socat approach I showed
    • build one in nodejs
    • build one using https://github.com/avleen/bashttpd
    • build one using openresty

    You can also add the IP of the service using extra_hosts in your docker-compose.yml and make the call like curl metaserver:10081

    This should be a decently secure approach, without compromising on what you need

    0 讨论(0)
  • 2021-02-06 06:01

    You can use docker port for this.

    docker port my-app 80
    

    That will include the listener IP. If you need to strip that off, you can do that with the shell:

    docker port my-app 80 | cut -f2 -d:
    

    Unfortunately, this will only work with access to the docker socket. I wouldn't recommend mounting that socket for just this level of access inside your container.

    Typically, most people solve this by passing a variable to their app and controlling what port is published in their docker-compose file. E.g.:

    my-app:
        build:
            context: ./my-app
        ports:
            - "8080:80"     
        environment:
            - PUBLISHED_PORT=8080
        volumes:
            - /var/run/docker.sock:/tmp/docker.sock
        networks:
            - private
    

    Then the app would look at the environment variable to reconfigure itself.

    0 讨论(0)
  • 2021-02-06 06:02

    I used a dockerode library for this.

    Solution:

    const Docker = require( 'dockerode' );
    const os = require( 'os' );
    
    const docker = new Docker( { socketPath: '/tmp/docker.sock' } );
    const container = docker.getContainer( os.hostname() );
    
    container.inspect( function( error, data ) {
        if ( error ) {
            return null;
        }
    
        const mappedPort = data.NetworkSettings.Ports[ '80/tcp' ][ 0 ].HostPort
    } );
    
    0 讨论(0)
  • 2021-02-06 06:15

    What about using shelljs?

    const shelljs = require('shelljs');
    
    const output = shelljs.exec('docker ps --format {{.Ports}}', {silent: true}).stdout.trim();
    
    console.log(output); // 80/tcp
                         // 19999/tcp
                         // 9000/tcp
                         // 8080/tcp, 50000/tcp
    

    The output is a string, now let's map the response.

    const shelljs = require('shelljs');
    
    const output = shelljs.exec('docker ps --format {{.Ports}}', {silent: true}).stdout.trim();
    
    const ports = output.split('\n').map(portList => portList.split(',').map(port => Number(port.split('/')[0].trim())));
    
    

    This way ports return an array of arrays containing the ports:

    [ [ 80 ], [ 19999 ], [ 9000 ], [ 8080, 50000 ] ]
    

    In your case, you want the number between : and ->. So you can do this:

    const shelljs = require('shelljs');
    
    const output = shelljs
      .exec('docker ps --format {{.Ports}}', { silent: true })
      .stdout.trim();
    
    const aoa = output.split('\n').map(portList => portList.split(','));
    
    console.log(aoa); // [ [ '9000/tcp', ' 0.0.0.0:9000->123/tcp ' ], [ ' 80/tcp' ] ]
    
    let ports = [];
    
    aoa.forEach(arr => {
      arr.forEach(p => {
        // match strings which contains :PORT-> pattern;
        const match = p.match(/:(\d+)->/);
        if (match) {
          ports.push(Number(match[1]));
        }
      });
    });
    
    console.log(ports); // [ 9000 ]
    

    Finally, you need to install docker inside your container and connect docker socks as explained here.

    Update 29/06/2019:

    As said by @Tarun Lalwani, if sharing docker socks is a problem, you may create an app which shares the network with your main app and has a GET method that returns ports.

    0 讨论(0)
提交回复
热议问题