前言

之前在学Hadoop基础的时候,需要部署环境进行测试,其实那个时候部署遇到了不少的坑,今天突然想起来,所以跟大家分享下,让大家少踩点坑。我的Ubuntu版本是18.10

1. 安装Docker

安装Docker的话基本上没有遇到什么坑,我是按照官方文档去做的,官方文档连接如下: https://docs.docker.com/install/linux/docker-ce/ubuntu/

下面我还是简单说说文档里面的步骤吧,如果说大家看的懂英文的就直接跳过这一part吧

// 删除掉之前安装过的docker版本
sudo apt-get remove docker docker-engine docker.io containerd runc
// 安装以下包,让apt支持https
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
//添加Docker官方的Key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
//根据版本增加库地址
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
//安装
sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io

其实上面的命令用 && 可以直接一次性安装的,不过官网分开了步骤说明每一步的作用,我这里也是简单的搬运而已。用上这些命令之后,安装是没有遇到坑的。

2. 安装JDK 8 和 Hadoop 3.2

因为Hadoop最新版是3.2,所以我这里也装了最新版,对了,先要在这里说一下一个坑,那就是 ubuntu 的 14.04,我尝试过网上 ubuntu 14.04 安装 jdk8 的大多数教程,发现好多都不太行,所以我这里用了另外一个办法,在宿主机上下载好 jdk8 的压缩包,再复制进去镜像里面,接着再改里面的环境变量就行了,同理 Hadoop 也是,接下来我来说下Dockerfile的编写

FROM ubuntu:14.04

# Step 1 安装ssh-server,因为Hadoop的NameNode和DataNode之间是靠ssh进行节点确认和传输的
# 第一步安装这个的原因是它时间长,但成功率高,这步成功之后,会有一层image cache,如果往后的
# 执行失败了,调整后再执行docker build 的时候就不用再执行这步,减少时间

RUN apt-get update -y \
    && apt-get install -y openssh-client openclient-server \
    && apt-get autoclean

# Step 2 复制tar文件到指定目录,并且自动解压,这个就是ADD的好处
# 注意,执行docker build 所在的目录是工作目录,要注意根据实际情况
# 更改压缩包的位置

ADD ./java.tar.gz /usr/lib/jvm
ADD ./hadoop.tar.gz /usr/local/

# Step 3 创建hdfs的tmp,name,data目录

RUN mkdir /usr/local/hadoop/hdfs \
    && mkdir /usr/local/hadoop/hdfs/name \
    && mkdir /usr/local/hadoop/hdfs/data

# Step 4 修改环境变量,这个命令用到的都是绝对路径,不要用下面的变量
# 来填充path路径,因为没有生效,所以会出错
RUN echo "export JAVA_HOME=/usr/lib/jvm/java" >> ~./bashrc \
    && echo "export HADOOP_HOME=/usr/local/hadoop" >> ~./bashrc \
    && ehco "export HADOOP_CONF_DIR=/usr/local/hadoop/etc/hadoop" >> ./bashrc \
    && ehco "export PATH=$PATH:/usr/lib/jvm/java/bin:/usr/local/hadoop/bin" >> ./bashrc \

###Dockerfile end

接着,在对应的目录执行

## -t 后面跟的是镜像标签 -f 后面跟的是Dockerfile所在文件目录
docker build -t hadoop:3.2 -f "Dockerfile所在文件目录"

3. 配置Hadoop的相关配置文件

3.1 Master配置

首先,新建 master 文件目录,并放置如下几个文件夹

config //用于映射config文件
hdfs //用于映射hdfs的tmp,name,data目录
ssh //用于映射镜像里面的.ssh文件

在 config 文件目录里面,新建如下目录

etc ## 基本配置文件
sbin ## 部分脚本,因为需要更改脚本才能正常运行

etc 文件夹

里面包含如下文件,这些文件都可以在 hadoop 的 /etc 里面可以找到

core-site.xml
hoodoop-env.sh
hdfs-site.xml
mapred-site.xml
workers
yarn-env.sh
yarn-site.xml

core-site.xml 主要设置 tmp 目录以及 master 网络地址

<configuration>
 <property>
   <!--tmp 目录地址 -->
   <name>hadoop.tmp.dir</name>
   <value>file:/home/hadoop/hdfs/tmp</value>
   <description>A base for other temporary directories.</description>
 </property>
 <property>
  <name>io.file.buffer.size</name>
   <value>131072</value>
 </property>
 <property>
   <!-- 默认FS地址 -->
   <name>fs.defaultFS</name>
   <value>hdfs://master:9000</value>
 </property>
</configuration>

hsfs-site.xml 设置 replication 为1 ,因为我现在只部署一个 NameNode, 接着 NameNode 和 DataNode 存放的路径,具体更改如下

<configuration>
 <property>
   <name>dfs.replication</name>
   <value>1</value>
 </property>
 <property>
   <name>dfs.namenode.name.dir</name>
   <value>file:/usr/local/hadoop/hdfs/name</value>
   <final>true</final>
</property>
 <property>
   <name>dfs.datanode.data.dir</name>
   <value>file:/usr/local/hadoop/hdfs/data</value>
   <final>true</final>
 </property>
 <property>
  <name>dfs.namenode.secondary.http-address</name>
   <value>master:9001</value>
 </property>
 <property>
   <name>dfs.webhdfs.enabled</name>
   <value>true</value>
 </property>
 <property>
   <name>dfs.permissions</name>
   <value>false</value>
 </property>
</configuration>

mapred-site.xml MapReduce 用上的 yarn 配置:

<configuration>
 <property>
   <name>mapreduce.framework.name</name>
   <value>yarn</value>
 </property>
</configuration>

yarn-site.xml 用上的配置(这个主要是 yarn 对应的 ResourceManger 和 NodeMananger 配置):

<configuration>
 <property>
 <name>yarn.resourcemanager.address</name>
   <value>master:18040</value>
 </property>
 <property>
   <name>yarn.resourcemanager.scheduler.address</name>
   <value>master:18030</value>
 </property>
 <property>
   <name>yarn.resourcemanager.webapp.address</name>
   <value>master:18088</value>
 </property>
 <property>
   <name>yarn.resourcemanager.resource-tracker.address</name>
   <value>master:18025</value>
 </property>
 <property>
   <name>yarn.resourcemanager.admin.address</name>
   <value>master:18141</value>
 </property>
 <property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
 </property>
 <property>
     <name>yarn.nodemanager.auxservices.mapreduce.shuffle.class</name>
     <value>org.apache.hadoop.mapred.ShuffleHandler</value>
 </property>
</configuration>

workers 文件也需要更改一下

worker1 ## 表示DataNode的网络名称

hadoop-env.sh: start-dfs.sh

export JAVA_HOME=/usr/lib/jvm/java

同理 yarn-env.sh

sbin 文件夹

我的是包含以下脚本

start-dfs.sh
stop-dfs.sh
start-yarn.sh
stop-yarn.sh

start-dfs.sh 和 stop-dfs.sh

HDFS_DATANODE_USER=root
HADOOP_SECURE_DN_USER=hdfs
HDFS_NAMENODE_USER=root
HDFS_SECONDARYNAMENODE_USER=root

start-yarn.shstop-yarn.sh

YARN_RESOURCEMANAGER_USER=root
HADOOP_SECURE_DN_USER=yarn
YARN_NODEMANAGER_USER=root

3.2 Worker配置

Worker的配置也很简单,直接把 master 文件目录复制一下,把 改成 worker

4. Docker-Compose 集群配置

首先得安装 docker-compose 命令:

pip3 install docker-compose

我的 Docker Compose 文件如下

version: "3"

services:
  master:
    image: hadoop:3.2
    networks:
      hadoop:
        ipv4_address: 172.16.172.2
    volumes:
     - "/etc/localtime:/etc/localtime:ro"
     - "/home/anderw/docker/container/hadoop/master/hdfs/tmp:/usr/local/hadoop/hdfs/tmp"
     - "/home/anderw/docker/container/hadoop/master/hdfs/name:/usr/local/hadoop/hdfs/name"
     - "/home/anderw/docker/container/hadoop/master/hdfs/data:/usr/local/hadoop/hdfs/data"
     - "/home/andrew/docker/container/hadoop/master/config/etc/core-site.xml:/usr/local/hadoop/etc/hadoop/core-site.xml"
     - "/home/andrew/docker/container/hadoop/master/config/etc/hdfs-site.xml:/usr/local/hadoop/etc/hadoop/hdfs-site.xml"
     - "/home/andrew/docker/container/hadoop/master/config/etc/mapred-site.xml:/usr/local/hadoop/etc/hadoop/mapred-site.xml"
     - "/home/andrew/docker/container/hadoop/master/config/etc/yarn-site.xml:/usr/local/hadoop/etc/hadoop/yarn-site.xml"
     - "/home/andrew/docker/container/hadoop/master/config/etc/hadoop-env.sh:/usr/local/hadoop/etc/hadoop/hadoop-env.sh"
     - "/home/andrew/docker/container/hadoop/master/config/etc/hadoop-env.sh:/usr/local/hadoop/etc/hadoop/yarn-env.sh"
     - "/home/andrew/docker/container/hadoop/master/config/etc/workers:/usr/local/hadoop/etc/hadoop/workers"
     - "/home/andrew/docker/container/hadoop/master/config/sbin/start-dfs.sh:/usr/local/hadoop/sbin/start-dfs.sh"
     - "/home/andrew/docker/container/hadoop/master/ssh/:/root/.ssh/"
    command: 
     - /bin/sh
     - -c
     - |
       rm /root/.ssh/known_hosts
       /etc/init.d/ssh start
       /usr/local/hadoop/bin/hdfs namenode -format
       ./usr/local/hadoop/sbin/start-dfs.sh
       tail -f /dev/null
    expose:
     - "22"
     - "9000"

  worker1:
    image: hadoop:3.2
    networks:
      hadoop:
        ipv4_address: 172.16.172.3
    volumes:
     - "/etc/localtime:/etc/localtime:ro"
     - "/home/anderw/docker/container/hadoop/worker1/hdfs/tmp:/usr/local/hadoop/hdfs/tmp"
     - "/home/anderw/docker/container/hadoop/worker1/hdfs/name:/usr/local/hadoop/hdfs/name"
     - "/home/anderw/docker/container/hadoop/worker1/hdfs/data:/usr/local/hadoop/hdfs/data"
     - "/home/andrew/docker/container/hadoop/worker1/config/etc/core-site.xml:/usr/local/hadoop/etc/hadoop/core-site.xml"
     - "/home/andrew/docker/container/hadoop/worker1/config/etc/hdfs-site.xml:/usr/local/hadoop/etc/hadoop/hdfs-site.xml"
     - "/home/andrew/docker/container/hadoop/worker1/config/etc/mapred-site.xml:/usr/local/hadoop/etc/hadoop/mapred-site.xml"
     - "/home/andrew/docker/container/hadoop/worker1/config/etc/yarn-site.xml:/usr/local/hadoop/etc/hadoop/yarn-site.xml"
     - "/home/andrew/docker/container/hadoop/worker1/config/etc/hadoop-env.sh:/usr/local/hadoop/etc/hadoop/hadoop-env.sh"
     - "/home/andrew/docker/container/hadoop/worker1/config/etc/hadoop-env.sh:/usr/local/hadoop/etc/hadoop/yarn-env.sh"
     - "/home/andrew/docker/container/hadoop/worker1/config/etc/workers:/usr/local/hadoop/etc/hadoop/workers"
     - "/home/andrew/docker/container/hadoop/worker1/config/sbin/start-dfs.sh:/usr/local/hadoop/sbin/start-dfs.sh"
     - "/home/andrew/docker/container/hadoop/worker1/ssh/:/root/.ssh/"
    command: 
     - /bin/sh
     - -c
     - |
       rm /root/.ssh/known_hosts
       /etc/init.d/ssh start
       ./usr/local/hadoop/sbin/start-dfs.sh
       tail -f /dev/null
    expose:
     - "22"
networks:
      hadoop:
        ipam:
          driver: default
          config: 
            - subnet: "172.16.172.0/24"

注意:

  1. 首次启动的时候就会 format namenode, 在第二次启动的时候要把 usr/local/hadoop/bin/hdfs namenode -format 这个命令给删除
  2. 我已经把 .ssh 的文件目录映射到外面了,大家必须按照自己的情况做出更改,映射出来的目的是方便下面步骤,更改 ssh 的设置
  3. server 名字必须是 master 和 worker1,如果要自定义,必须更改上面的配置文件

5. ssh 免登录设置

首先,在上面的docker-compose 中,删除调用 start-dfs.sh

docker-compose -f "docker-compose 文件的绝对路径" up -d

然后找到 master 和 worker 容器的id, 可以通过 docker container ps 查看,接着进入容器,用以下命令

docker exec -it "容器id" /bin/bash

这样就可以在 root 的用户下操作镜像,这个时候就可以生成自己用的公钥秘钥

ssh-keygen -t rsa -C "用户名称"

一路按 enter, 就可以生成默认的免密码的公钥和秘钥了,默认地址就是在 /root/.ssh 下面有个 id_rsa 和 id_rsa.pub,在 master 的容器中把它改成 mastermaster.pub, 在 worker1 的容器中把它改成 worker1worker1.pub。在宿主机上确保 master , worker1 的 .ssh 映射文件里面都有这两个秘钥( master 和 worker1 )。同时 authorized_key 中 也必须含有这两个对应的 pub。如果没有authorized_key 要创建,并且确保文件访问权限为 600。接着在他们 .ssh 文件目录中添加 config 文件,内容如下

Host worker1        
    HostName worker1
    IdentityFile "对应worker1的秘钥路径"
    User root

Host master             
    HostName master 
    IdentityFile "对应master的秘钥路径"
    PreferredAuthentications publickey
    User root

接着,用

docker-compose -f "docker-compose 文件的绝对路径" down

关掉容器,在 docker-compose 的 command 上添加执行的脚本

./usr/local/hadoop/sbin/start-dfs.sh

再重新执行

docker-compose -f "docker-compose 文件的绝对路径" up -d

如无意外, Hadoop 集群就可以跑起来了

6 后记

还好写了这篇笔记,6.2号的时候发现昨天好好的笔记本来不了机了..........可能得重新部署环境。由于最近在复习java,所以没有怎么更新文章,在这里我补充下构建镜像以及使用这几个容器需要避开的坑吧。

6.1 构建镜像注意的地方

  • Dockerfile 的每一个命令,都会在本机上留下中间镜像,只有 docker image ls -a 才能看到,所以为了避免出现过多的冗余,最好指令少一点
  • 除了指令少一点,当然最好就是每一条指令只做一件事情,而且还得记住,构建镜像的时候必须记得用命令删除不必要的内容,以免镜像过大
  • 成功率高,但耗时大的命令尽量往前放,因为后面即使失败,第二次构建也会从成功的命令开始,这样避免重复构建镜像的时候花费过多的时间

6.2 构建容器注意的地方

在我搭建上面的环境时, ssh 密码登录卡了我两天时间,到最后排查到的问题是,当你进入容器新建目录的时候,如果宿主机有跟该目录做映射,在宿主机不存在对应目录的情况下,目录的用户权限是跟容器一致的;如果在宿主创建对应目录,那么容器内的目录权限以宿主机的为准。因为当时没有注意到这个细节,导致映射出来的 .ssh 文件中的秘钥,配置全都不可用,所以大家在搭建环境的时候务必注意这一点。