Hadoop in Docker

学习Docker多时,但是从来没有打包过稍微复杂些的镜像,未免有些遗憾。这次借乘着学习hadoop的机会,顺带折腾折腾比较复杂的dockerfile。

Hadoop

Docker只是一个容器运行工具,要部署hadoop,首先需要了解hadoop的组成。但鉴于篇幅有限(我也不懂),所以就简单讲讲,点到为止。

Hadoop集群由5个部分组成:

  1. NameNode
  2. DataNode
  3. HistoryServer
  4. SourceManager
  5. SecondaryNameNode

具体干啥,这篇文章就不细讲,大致就是NameNode管理DataNode,SourceManager负责资源分配,HistoryServer负责日志管理,而SecondaryNameNode负责备份。

从简起见,本次集群搭建仅涉及NameNode和DataNode,SecondaryNameNode其他部分可以省略。

其中,NameNode与SecondaryNameNode在Master节点上,而DataNode则在slave节点上。

基础镜像构建

说是Master与Slave,但其实其中的配置文件基本一致,所以,通过构建一个基础镜像,就能够启动一个Hadoop集群中的所有节点,只需定义不同节点的行为,就能顺利运行集群了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
FROM debian:stable

LABEL "auther"="Logres"

WORKDIR /root

#配置ip解析
COPY hostname /etc/hostname

#容器换http源
COPY sources-http.list /etc/apt/sources.list

# 获取https证书验证器
RUN apt update && apt install -y apt-transport-https ca-certificates

#配置https源
COPY sources-https.list /etc/apt/sources.list

#下载 openssh-server, openjdk and wget
RUN apt update && apt install -y openssh-server default-jdk

#安装hadoop

COPY hadoop-3.2.3.tar.gz /root/hadoop-3.2.3.tar.gz

RUN tar -xzvf hadoop-3.2.3.tar.gz && \
mv hadoop-3.2.3 /usr/local/hadoop && \
rm hadoop-3.2.3.tar.gz

#配置环境变量
ENV JAVA_HOME=/usr/lib/jvm/default-java
ENV HADOOP_HOME=/usr/local/hadoop
ENV PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

#ssh配置 密匙生成 及 公钥添加
RUN ssh-keygen -t rsa -f ~/.ssh/id_rsa -P '' && \
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

#生成所需文件夹
RUN mkdir -p ~/hdfs/namenode && \
mkdir -p ~/hdfs/datanode && \
mkdir $HADOOP_HOME/logs

#复制配置文件
COPY config/* /temp/

#移动配置文件到正确的位置
RUN mv /temp/hadoop-env.sh $HADOOP_HOME/etc/hadoop/hadoop-env.sh && \
mv /temp/hdfs-site.xml $HADOOP_HOME/etc/hadoop/hdfs-site.xml && \
mv /temp/core-site.xml $HADOOP_HOME/etc/hadoop/core-site.xml && \
mv /temp/mapred-site.xml $HADOOP_HOME/etc/hadoop/mapred-site.xml && \
mv /temp/workers $HADOOP_HOME/etc/hadoop/workers && \
mv /temp/masters $HADOOP_HOME/etc/hadoop/masters && \
mv /temp/start.sh ~/start.sh && \
mv /temp/init.sh ~/init.sh

#文件执行权限
RUN chmod +x $HADOOP_HOME/sbin/* && \
chmod +x ~/start.sh && \
chmod +x ~/init.sh && \
chmod +x $HADOOP_HOME/bin/*

RUN /usr/local/hadoop/bin/hdfs namenode -format

配置文件基本如上。值得注意的是:

  1. 对于文件配置,我选择写好配置文件,在构建镜像时COPY进去,而非在RUN中通过指令进行编辑。这样可以大大简化Dockerfile,提高可读性,坏处是需要外挂文件才能使用Dockerfile,而不能一个Dockerfile走天下了。
  2. 为了使用apt在国内进行下载,需要配置apt源。其中还有闹心事:https源无法解析,还需要先添加http源,apt安装证书解析,然后再添加https源,最后再下载需要的软件。
  3. Hadoop集群间使用ssh通信,为了实现免密通信,直接使用ssh-keygen使用无密码生成密匙,再将自己的公匙放入自己的认证列表,这样,同一个镜像生成的所有容器都可以使用相同的密匙进行通信;如果需要为每个节点特制镜像的话,就不能这么做了。
  4. hostname,为了使hadoop能解析hostname,还需要专门配置hostname文件,当然,也可以通过设置hadoop的解析方式来直接解析ip——集群设定的ip,这样就不需要配置hostname文件了(代价是,进一步耦合了)

集群

虽然基础镜像十分复杂,但容器集群的配置就相对简单了。Docker-Compose如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
version: "3"
services:
master:
image: hadoop-logres/1.0
ports:
- "9870:9870"
networks:
virtual_net:
ipv4_address: 192.169.0.100
volumes:
- "./words:/root/words"
#stdin_open: true # -i interactive
tty: true # -t tty

entrypoint: ["sh"]

slave1:
image: hadoop-logres/1.0
networks:
virtual_net:
ipv4_address: 192.169.0.2
#stdin_open: true # -i interactive
tty: true # -t tty
entrypoint: ["sh"]

slave2:
image: hadoop-logres/1.0
networks:
virtual_net:
ipv4_address: 192.169.0.3
#stdin_open: true # -i interactive
tty: true # -t tty
entrypoint: ["sh"]

networks:
virtual_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 192.169.0.0/16

仅需配置好容器集群的虚拟网络,以及每一个容器的ip,即完成了集群的配置。

但是,重头戏还在后头呢!

输入指令:

1
docker-compose up

集群启动!

然后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@MSI:~/HadoopLogres# docker-compose up
Recreating hadooplogres_master_1 ... done
Starting hadooplogres_slave1_1 ... done
Starting hadooplogres_slave2_1 ... done
Attaching to hadooplogres_slave2_1, hadooplogres_slave1_1, hadooplogres_master_1
master_1 | Starting OpenBSD Secure Shell server: sshdExtra argument &&.
master_1 | failed!
slave2_1 | Starting OpenBSD Secure Shell server: sshdExtra argument &&.
slave1_1 | Starting OpenBSD Secure Shell server: sshdExtra argument &&.
slave1_1 | failed!
slave2_1 | failed!
hadooplogres_slave1_1 exited with code 0
hadooplogres_slave2_1 exited with code 0
hadooplogres_master_1 exited with code 0

我集群呢?我那么大个集群呢?

问题分析

我们都知道,容器是应用程序的封装,而非服务器的封装,当其封装的应用进程结束后,容器的生命也就结束了。

在我们的hadoop集群中,hadoop程序事实上都是以后台守护进程的形式运行的,也就是说,用于启动其的指令start-dfs.sh等指令执行完毕后,容器就关闭了。这当然是与我们的初衷:建立一个长期运行的稳定集群,相违背的。

解决

问题分析完了,怎么解决呢?

预想方案:

  1. 开个无限循环的主进程
  2. 把hadoop相关进程作为主进程

容器启动时仅能指定一条指令,连 && 都不好使,所以以上方案对于需要在启动真正程序前启动一些辅助进程的情况,比如,hadoop所需的ssh服务,就需要执行 service ssh start开启,就不适用了。

那么不妨开个万能程序——bash。

于是,我将entrypoint设置为 “sh”。

部署

设置为“sh”姑且算是解决了容器莫名消失的问题,但是,集群的事前准备还是需要单独处理的。

于是,怎么部署呢?

老样子

1
docker-compse up

集群启动!

然后,一个一个地进入容器执行指令喽~

1
docker exec -it container bash

虽然有点low,但生活就是这样。兴许有更好的方法,但我目前还不知道,以后再探索吧!