为什么Dockerized Hadoop datanode注册了错误的IP地址?

Mat*_*och 6 java hadoop hdfs docker

我有针对Hadoop(2.7.1)名称节点和数据节点的单独Docker(1.9.1)映像.我可以从这些容器中创建容器,并让它们通过用户定义的Docker网络进行通信.但是,datanode似乎报告自己拥有网络网关的IP地址而不是自己的IP地址.虽然这不会导致单个数据节点出现任何问题,但在添加其他数据节点时会产生混淆.它们都使用相同的IP地址注册,名称节点在它们之间翻转,只报告单个数据节点是活动的.

为什么服务器(namenode)在用户定义的Docker网络上运行时从客户端(datanode)套接字连接读取错误的IP地址,我该如何解决?

更新:此问题似乎在Docker端

使用--net=bridge并执行netcat服务器运行两个容器:

nc -v -l 9000
Run Code Online (Sandbox Code Playgroud)

在一个容器中,另一个容器中的netcat客户端:

nc 172.17.0.2 9000
Run Code Online (Sandbox Code Playgroud)

导致第一个容器正确打印:

Connection from 172.17.0.3 port 9000 [tcp/9000] accepted

但是创建一个用户定义的网络:

sudo docker network create --driver bridge test
Run Code Online (Sandbox Code Playgroud)

并在启动时在容器中执行相同的命令,--net=test错误地打印网关/用户定义的网络接口的IP地址:

Connection from 172.18.0.1 port 9000 [tcp/9000] accepted

HDFS/Docker详细信息

dfs.datanode.address每个datanode hdfs-site.xml文件中的属性都设置为其主机名(例如,hdfs-datanode-1).

网络创建如下:

sudo docker network create --driver bridge hadoop-network
Run Code Online (Sandbox Code Playgroud)

namenode的开头是这样的:

sudo docker run -d \
                --name hdfs-namenode \
                -v /hdfs/name:/hdfs-name \
                --net=hadoop-network \
                --hostname hdfs-namenode \
                -p 50070:50070 \
                hadoop:namenode
Run Code Online (Sandbox Code Playgroud)

datanode就像这样开始:

sudo docker run -d \
                --name hdfs-datanode-1 \
                -v /hdfs/data_1:/hdfs-data \
                --net=hadoop-network \
                --hostname=hdfs-datanode-1 \
                --restart=always \
                hadoop:datanode
Run Code Online (Sandbox Code Playgroud)

两个节点连接正常,在查询(使用sudo docker exec hdfs-namenode hdfs dfsadmin -report)时,连接报告为:

...
Live datanodes (1):

Name: 172.18.0.1:50010 (172.18.0.1)
Hostname: hdfs-datanode-1
...

但是,运行输出:

 sudo docker exec hdfs-namenode cat /etc/hosts
Run Code Online (Sandbox Code Playgroud)

表示该namenode认为它正在运行172.18.0.2并且datanode正在运行172.18.0.3:

172.18.0.2      hdfs-namenode
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.3      hdfs-datanode-1
172.18.0.3      hdfs-datanode-1.hadoop-network

而datanode上的等价物显示相同:

172.18.0.3      hdfs-datanode-1
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2      hdfs-namenode
172.18.0.2      hdfs-namenode.hadoop-network

ip route在两者上运行都证实了这一点:

sudo docker exec hdfs-namenode ip route
Run Code Online (Sandbox Code Playgroud)
default via 172.18.0.1 dev eth0
172.18.0.0/16 dev eth0  proto kernel  scope link  src 172.18.0.2
sudo docker exec hdfs-datanode-1 ip route
Run Code Online (Sandbox Code Playgroud)
default via 172.18.0.1 dev eth0
172.18.0.0/16 dev eth0  proto kernel  scope link  src 172.18.0.3

然而,当datanode启动时,namenode将datanode的IP地址报告为172.18.0.1:

... INFO hdfs.StateChange: BLOCK* registerDatanode: from DatanodeRegistration(172.18.0.1:50010, datanodeUuid=3abaf40c-4ce6-47e7-be2b-fbb4a7eba0e3, infoPort=50075, infoSecurePort=0, ipcPort=50020, storageInfo=lv=-56;cid=CID-60401abd-4793-4acf-94dc-e8db02b27d59;nsid=1824008146;c=0) storage 3abaf40c-4ce6-47e7-be2b-fbb4a7eba0e3
... INFO blockmanagement.DatanodeDescriptor: Number of failed storage changes from 0 to 0
... INFO net.NetworkTopology: Adding a new node: /default-rack/172.18.0.1:50010
... INFO blockmanagement.DatanodeDescriptor: Number of failed storage changes from 0 to 0
... INFO blockmanagement.DatanodeDescriptor: Adding new storage ID DS-4ba1a710-a4ca-4cad-8222-cc5f16c213fb for DN 172.18.0.1:50010
... INFO BlockStateChange: BLOCK* processReport: from storage DS-4ba1a710-a4ca-4cad-8222-cc5f16c213fb node DatanodeRegistration(172.18.0.1:50010, datanodeUuid=3abaf40c-4ce6-47e7-be2b-fbb4a7eba0e3, infoPort=50075, infoSecurePort=0, ipcPort=50020, storageInfo=lv=-56;cid=CID-60401abd-4793-4acf-94dc-e8db02b27d59;nsid=1824008146;c=0), blocks: 1, hasStaleStorage: false, processing time: 3 msecs

并且使用tcpdump捕获两者之间的流量(在连接到主机网络的Docker容器中运行 - 使用docker run --net=host)似乎显示发生错误(br-b59d498905c5是Docker为其创建的网络接口的名称hadoop-network):

tcpdump -nnvvXS -s0 -i br-b59d498905c5 \
        "(src host 172.18.0.3 or src host 172.18.0.2) and \
         (dst host 172.18.0.3 or dst host 172.18.0.2)"
Run Code Online (Sandbox Code Playgroud)

IP地址似乎在registerDatanode通话中正确发送:

...
172.18.0.3.33987 > 172.18.0.2.9000: ...
    ...
    0x0050:  f828 004d 0a10 7265 6769 7374 6572 4461  .(.M..registerDa
    0x0060:  7461 6e6f 6465 1237 6f72 672e 6170 6163  tanode.7org.apac
    0x0070:  6865 2e68 6164 6f6f 702e 6864 6673 2e73  he.hadoop.hdfs.s
    0x0080:  6572 7665 722e 7072 6f74 6f63 6f6c 2e44  erver.protocol.D
    0x0090:  6174 616e 6f64 6550 726f 746f 636f 6c18  atanodeProtocol.
    0x00a0:  01a7 010a a401 0a51 0a0a 3137 322e 3138  .......Q..172.18
    0x00b0:  2e30 2e33 120f 6864 6673 2d64 6174 616e  .0.3..hdfs-datan
    0x00c0:  6f64 652d 311a 2433 6162 6166 3430 632d  ode-1.$3abaf40c-
    ...

但在随后的调用中它是不正确的.例如,在sendHeartbeat之后的几分之一秒内调用:

...
172.18.0.3.33987 > 172.18.0.2.9000: ...
    ...
    0x0050:  f828 004a 0a0d 7365 6e64 4865 6172 7462  .(.J..sendHeartb
    0x0060:  6561 7412 376f 7267 2e61 7061 6368 652e  eat.7org.apache.
    0x0070:  6861 646f 6f70 2e68 6466 732e 7365 7276  hadoop.hdfs.serv
    0x0080:  6572 2e70 726f 746f 636f 6c2e 4461 7461  er.protocol.Data
    0x0090:  6e6f 6465 5072 6f74 6f63 6f6c 1801 9d02  nodeProtocol....
    0x00a0:  0aa4 010a 510a 0a31 3732 2e31 382e 302e  ....Q..172.18.0.
    0x00b0:  3112 0f68 6466 732d 6461 7461 6e6f 6465  1..hdfs-datanode
    0x00c0:  2d31 1a24 3361 6261 6634 3063 2d34 6365  -1.$3abaf40c-4ce
    ...

通过datanode代码进行调试清楚地显示了根据namenode返回的信息更新datanode注册详细信息BPServiceActor.register()时发生的错误:

bpRegistration = bpNamenode.registerDatanode(bpRegistration);
Run Code Online (Sandbox Code Playgroud)

调试namenode会显示它从datanode套接字连接读取错误的 IP地址并更新datanode注册详细信息.

补充说明

我可以通过在用户定义的Docker网络上运行的代码重现此问题:

import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws Exception {
        // 9000 is the namenode port
        ServerSocket server = new ServerSocket(9000);

        Socket socket = server.accept();
        System.out.println(socket.getInetAddress().getHostAddress());
    }
}
Run Code Online (Sandbox Code Playgroud)

import java.net.Socket;

public class Client {
    public static void main(String[] args) throws Exception {
        // 172.18.0.2 is the namenode IP address
        Socket socket = new Socket("172.18.0.2", 9000);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用ServerClient运行172.18.0.2这个正确输出172.18.0.2Client运行172.18.0.3不正确输出172.18.0.1.

不使用用户定义的网络(在默认bridge网络/ docker0接口和公开端口上9000)运行相同的代码可以得到正确的输出.

我在namenode的文件中dfs.namenode.datanode.registration.ip-hostname-check设置了属性,以防止反向DNS查找错误.如果我让DNS工作,将来可能没有必要这样做但是现在,由于数据节点报告了错误的IP地址,我怀疑DNS工作会有所帮助.falsehdfs-site.xml

我相信相关的有线协议registerDatanode,sendHeartbeat并且blockReport都是RegisterDatanodeRequestProto,HeartbeatRequestProto而且BlockReportRequestProto他们的定义可以在这里找到.这些都包含DatanodeRegistrationProto作为他们的第一个数据成员.此消息在此处定义,如下所示:

/**
 * Identifies a Datanode
 */
message DatanodeIDProto {
  required string ipAddr = 1;    // IP address
  required string hostName = 2;  // hostname
  ...
}
Run Code Online (Sandbox Code Playgroud)

Mat*_*och 4

这是由已知的 docker 问题引起的(我还提出并关闭了这个重复项,它描述了问题中列出的步骤)。

有一个合并的拉取请求应该可以解决该问题,并计划包含在 Docker 1.10.0 中。但与此同时,可以使用以下解决方法:

  1. 删除所有用户创建的网络sudo docker network rm
  2. 停止 docker 守护进程sudo service docker stop
  3. 清理 iptablessudo iptables -F && sudo iptables -F -t nat
  4. 重新启动 docker 守护进程sudo service docker start
  5. 重新创建用户定义的网络
  6. 运行容器