学习Docker多时,但是从来没有打包过稍微复杂些的镜像,未免有些遗憾。这次借乘着学习hadoop的机会,顺带折腾折腾比较复杂的dockerfile。
Hadoop
Docker只是一个容器运行工具,要部署hadoop,首先需要了解hadoop的组成。但鉴于篇幅有限(我也不懂),所以就简单讲讲,点到为止。
Hadoop集群由5个部分组成:
- NameNode
- DataNode
- HistoryServer
- SourceManager
- 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
COPY hostname /etc/hostname
COPY sources-http.list /etc/apt/sources.list
RUN apt update && apt install -y apt-transport-https ca-certificates
COPY sources-https.list /etc/apt/sources.list
RUN apt update && apt install -y openssh-server default-jdk
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
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
|
配置文件基本如上。值得注意的是:
- 对于文件配置,我选择写好配置文件,在构建镜像时COPY进去,而非在RUN中通过指令进行编辑。这样可以大大简化Dockerfile,提高可读性,坏处是需要外挂文件才能使用Dockerfile,而不能一个Dockerfile走天下了。
- 为了使用apt在国内进行下载,需要配置apt源。其中还有闹心事:https源无法解析,还需要先添加http源,apt安装证书解析,然后再添加https源,最后再下载需要的软件。
- Hadoop集群间使用ssh通信,为了实现免密通信,直接使用ssh-keygen使用无密码生成密匙,再将自己的公匙放入自己的认证列表,这样,同一个镜像生成的所有容器都可以使用相同的密匙进行通信;如果需要为每个节点特制镜像的话,就不能这么做了。
- 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" tty: true entrypoint: ["sh"]
slave1: image: hadoop-logres/1.0 networks: virtual_net: ipv4_address: 192.169.0.2 tty: true entrypoint: ["sh"]
slave2: image: hadoop-logres/1.0 networks: virtual_net: ipv4_address: 192.169.0.3 tty: true entrypoint: ["sh"]
networks: virtual_net: driver: bridge ipam: driver: default config: - subnet: 192.169.0.0/16
|
仅需配置好容器集群的虚拟网络,以及每一个容器的ip,即完成了集群的配置。
但是,重头戏还在后头呢!
输入指令:
集群启动!
然后:
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等指令执行完毕后,容器就关闭了。这当然是与我们的初衷:建立一个长期运行的稳定集群,相违背的。
解决
问题分析完了,怎么解决呢?
预想方案:
- 开个无限循环的主进程
- 把hadoop相关进程作为主进程
容器启动时仅能指定一条指令,连 && 都不好使,所以以上方案对于需要在启动真正程序前启动一些辅助进程的情况,比如,hadoop所需的ssh服务,就需要执行 service ssh start开启,就不适用了。
那么不妨开个万能程序——bash。
于是,我将entrypoint设置为 “sh”。
部署
设置为“sh”姑且算是解决了容器莫名消失的问题,但是,集群的事前准备还是需要单独处理的。
于是,怎么部署呢?
老样子
集群启动!
然后,一个一个地进入容器执行指令喽~
1
| docker exec -it container bash
|
虽然有点low,但生活就是这样。兴许有更好的方法,但我目前还不知道,以后再探索吧!