Docker:暴露应用程序在容器内使用的多个端口,这些端口是未预先确定的

时间:2018-02-13 17:26:36

标签: perl docker

server.pl

sub sock_initialize {
    my $sock  = q{};
    my $port  = q{};

    # Get a port for our server.
    $sock = IO::Socket::INET->new(
        Listen    => SOMAXCONN,    # listen queue depth
        LocalPort => 0,
        Reuse     => 1
    );

    die "Unable to bind a port: $!" if !$sock;

    $port      = $sock->sockport();
    my $ip = "";
    my $uid = (getpwuid( $> ))[2];
    my $queue = join(":", $ip, $port, $$, $uid);

    print sprintf("put started on port $port ($$), SOMAXCONN=%d\n", SOMAXCONN);
    return $sock;
} ## end sub sock_initialize

my $listen_sock = sock_initialize();
while (1) {
    #my $xsock = Accept();
    my $xsock;
    while (1) {
        $! = 0;
        # Accept can block.  Need to use nonblocking poll (Stevens)
        $xsock = $listen_sock->accept;    # ACCEPT
        last if defined $xsock;
        next if $! == EINTR;
        die "accept error: $!";
        if ( defined $xsock ) {
            $xsock->blocking(0);    # mark executor socket nonblocking
            $xsock->sockopt( SO_KEEPALIVE() => 1 ) or die "sockopt: $!";
        }


    #my $rbufp = $conn->readbufref;
    #my $rdstatus = Read( $sock, $rbufp );
    my $buff = "";

    while (1) {
        $! = 0;
        # Accept can block.  Need to use nonblocking poll (Stevens)
        $xsock = $listen_sock->accept;    # ACCEPT
        last if defined $xsock;
        next if $! == EINTR;
        die "accept error: $!";
        if ( defined $xsock ) {
            $xsock->blocking(0);    # mark executor socket nonblocking
            $xsock->sockopt( SO_KEEPALIVE() => 1 ) or die "sockopt: $!";
        }
    }

    #my $rbufp = $conn->readbufref;
    #my $rdstatus = Read( $sock, $rbufp );
    my $buff = "";
    while (1) {
        my $nbytes = sysread $xsock, $buff, 32768, length($buff);    # SYSCALL

        if ( !defined $nbytes ) {                            # read error
            next if $! == EINTR;
            last if $! == EWOULDBLOCK;                       # normal
            return;
        }
        last if $nbytes == 0;                            # EOF
    }
    print "received $buff\n";
    last;
}

client.pl

my $host = "localhost";
my $port = 37402; # get port number from server.pl


my $s = IO::Socket::INET->new (PeerAddr => $host,
                                        PeerPort => $port,
                                        Type     => SOCK_STREAM,
                                        Proto    => 'tcp',
                                        Timeout  => 1);
if ($s) {
    $s->blocking (0) ;
}

my   $nbytes = syswrite $s, "hi from X";   # SYSCALL

首先,我将启动server.pl

$perl test_socket_server.pl 
$put started on port 37402 (16974), SOMAXCONN=128

然后我将端口号放在client.pl

perl test_socket_client.pl

然后,在server.pl shell上,我看到了

received hi from X

所以,它按预期工作。 现在,当我通过

将server.pl放入容器时
docker run ubuntu perl server.pl
put started on port 38170 (1), SOMAXCONN=128

然后,我会在client.pl中编写端口号并运行它,但server.pl没有收到消息

我的理解是容器端口不会通过EXPOSE暴露给主机

现在,即使问题可以通过EXPOSE修复,server.pl也会在未分配的端口上连接,即色情内容每次运行时都会发生变化LocalPort => 0, server.pl在容器内运行。我的理解是您必须在容器运行时公开端口,但此时,您不知道将运行哪个端口server.pl。我希望它是这样的,没有指定的端口,因为server.pl的多个实例可以在容器中运行(因此需要能够使用不同的端口)。是否有适应这种策略?

你可以在启动容器时公开端口范围,可能是30000及以上吗? [我已经阅读了一些关于暴露端口范围的其他堆栈溢出问题,但它似乎有一些性能问题,因为实际进程是按端口分叉的(?)Docker expose all ports or range of ports from 7000 to 8000理想的解决方案是以某种方式暴露正在使用的端口在运行时驻留在容器中的应用程序。也许这是由一个协调者完成的?

1 个答案:

答案 0 :(得分:3)

选项1 - Docker swarm&覆盖网络

Docker swarm的overlay network内的容器(更准确地说:服务)默认显示所有端口。在这种情况下,覆盖网络或多或少地作为专用网络。因此,您的客户端应用程序只能连接到服务器容器/服务,如果它也是群组的一部分。
但是,如果不是这种情况,您仍然可以使用service update --publish-add API解决此问题。此命令提供了在运行时更改端口暴露的可能性(这或多或少是您要求的),有关详细信息,请查看Publish ports on an overlay network部分:

  

连接到同一覆盖网络的群集服务有效地将所有端口相互暴露。对于可在服务外部访问的端口,必须使用docker service create或docker service update上的-p或--publish标志发布该端口。支持遗留冒号分隔语法和较新的逗号分隔值语法。较长的语法是首选,因为它有点自我记录。

如果您想使用Docker API解决问题,这将是最佳选择,它也将完美地处理多主机方案。但是对于高级部分(您的客户端不是swarm的一部分而您想使用service update),您必须将容器包装到服务中。
注意:--publish-add并未真正考虑过,因为docker service update通过重新启动容器来工作,因此您的应用程序很可能会切换到另一个端口,这可能是一个稍微不同的用例的选项。

选项2 - --net = host

如果所有服务器容器都应该在一台主机上运行,​​那么您可以轻松使用Docker的--net=host功能,而且不必明确公开任何端口。< / p>

示例:

docker run --net=host ubuntu perl server.pl

选项3 - 端口代理

您还可以使用socatssh等应用程序手动将服务器应用程序的端口映射到每个容器的预定义端口。例如,请参阅this answer中的Exposing a port on a live Docker container