1. 背景
ローカルマシンで Docker を使って Hadoop の分散処理環境の構築を試みたときのログです。
Docker を使い始めた頃に挑戦した内容のため、最適化されていない部分が多々あります。
とりあえず、動くものを作りたいというのが動機でした。
Windows10 上の VirtualBox で Ubuntu18.04を動作させ、その上で Docker を用いて Hadoop 環境をインストールしようとしています。
その VirtualBox には、8192MBのメモリと、2CPUを割り当てています。
ホスト | ゲスト(VirtualBox) | 割当メモリ | 割当CPU |
---|---|---|---|
Windows10 | Ubuntu18.04 | 8192MB | 2CPU |
今では、Hadoop クラスタの Docker イメージは企業からも公開されています。HDPからも sandbox でありますが、私のローカル環境ではスペック不足でフリーズしてしまいました。
sandbox
Docker バージョンの確認
この記事を書くために、動かした Docker のバージョンです。
実際に挑戦したのは、2年ほど前だったと思いますので、当時動かしたときはもっと古いバージョンだったはずです。
$ docker version
Client:
Version: 18.09.7
API version: 1.39
Go version: go1.10.1
Git commit: 2d0083d
Built: Wed Jul 3 12:13:59 2019
OS/Arch: linux/amd64
Experimental: false
Server:
Engine:
Version: 18.09.7
API version: 1.39 (minimum version 1.12)
Go version: go1.10.1
Git commit: 2d0083d
Built: Mon Jul 1 19:31:12 2019
OS/Arch: linux/amd64
Experimental: false
2. Dockerfile
ファイル名は "Dockerfile.hadoop" です。
とにかく RUN コマンドで順番にインストールするようにしていました。
RUN コマンドを実行する度にイメージレイヤーが生成され、イメージのサイズが大きくなるなど知らなかったため、ほとんどのコマンド一つにつき、RUN コマンドを書いています。
ベースを centos6 にした理由は単に馴染みがあったためです。
FROM centos:centos6
LABEL maintainer "blueskyarea"
USER root
# 外部コマンドのインストール
RUN yum update -y libselinux; \
yum install -y curl which tar sudo openssh-server openssh-clients rsync; \
yum clean all
# パスワードなし ssh
# 複数のコンテナ間で ssh 接続することを想定
RUN ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key
RUN ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -q -N "" -t rsa -f /root/.ssh/id_rsa
RUN cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
# Javaのインストール
RUN yum install -y java-1.8.0-openjdk
ENV JAVA_HOME /usr/lib/jvm/jre
ENV PATH $PATH:$JAVA_HOME/bin
RUN rm /usr/bin/java && ln -s $JAVA_HOME/bin/java /usr/bin/java
# HDP レポジトリを登録
ENV HADOOP_GROUP hadoop
ADD http://public-repo-1.hortonworks.com/HDP/centos6/2.x/updates/2.6.2.0/hdp.repo /etc/yum.repos.d/hdp.repo
# zookeeperのインストール
# ディレクトリ構成に一貫性がなくて分かりにくい
ENV ZOOKEEPER_USER zookeeper
ENV ZOOKEEPER_LOG_DIR /var/log/zookeeper
ENV ZOOKEEPER_PID_DIR /var/run/zookeeper
ENV ZOOKEEPER_DATA_DIR /grid/hadoop/zookeeper/data
ENV ZOOKEEPER_CONF_DIR /etc/zookeeper/conf
RUN yum install -y zookeeper-server hadoop
RUN mkdir -p $ZOOKEEPER_LOG_DIR
RUN chown -R $ZOOKEEPER_USER:$HADOOP_GROUP $ZOOKEEPER_LOG_DIR
RUN chmod -R 755 $ZOOKEEPER_LOG_DIR
RUN mkdir -p $ZOOKEEPER_PID_DIR
RUN chown -R $ZOOKEEPER_USER:$HADOOP_GROUP $ZOOKEEPER_PID_DIR
RUN chmod -R 755 $ZOOKEEPER_PID_DIR
RUN mkdir -p $ZOOKEEPER_DATA_DIR
RUN chmod -R 755 $ZOOKEEPER_DATA_DIR
RUN chown -R $ZOOKEEPER_USER:$HADOOP_GROUP $ZOOKEEPER_DATA_DIR
# なぜ、一度削除しているのか
RUN rm -r $ZOOKEEPER_CONF_DIR
RUN mkdir -p $ZOOKEEPER_CONF_DIR
RUN chmod a+x $ZOOKEEPER_CONF_DIR
RUN chown -R $ZOOKEEPER_USER:$HADOOP_GROUP $ZOOKEEPER_CONF_DIR/../
RUN chmod 755 $ZOOKEEPER_CONF_DIR/../
# Hadoopのインストール
RUN umask 0022
RUN yum install -y hadoop-hdfs hadoop-libhdfs hadoop-yarn hadoop-mapreduce hadoop-client openssl
# Snappyのインストール
# インストールする必要はあったのか?
RUN yum install -y snappy snappy-devel
# LZOのインストール
# インストールする必要はあったのか?
RUN yum install -y lzo lzo-devel hadooplzo hadooplzo-native
# NameNodeの環境設定
ENV DFS_NAME_DIR /grid/hadoop/hdfs/nn
ENV HDFS_USER hdfs
RUN mkdir -p $DFS_NAME_DIR
RUN chown -R $HDFS_USER:$HADOOP_GROUP $DFS_NAME_DIR
RUN chmod -R 755 $DFS_NAME_DIR
# SecondaryNameNodeの環境設定
ENV FS_CHECKPOINT_DIR /grid/hadoop/hdfs/snn
RUN mkdir -p $FS_CHECKPOINT_DIR
RUN chown -R $HDFS_USER:$HADOOP_GROUP $FS_CHECKPOINT_DIR
RUN chmod -R 755 $FS_CHECKPOINT_DIR
# DataNodeの環境設定
ENV DFS_DATA_DIR /grid/hadoop/hdfs/dn
RUN mkdir -p $DFS_DATA_DIR
RUN chown -R $HDFS_USER:$HADOOP_GROUP $DFS_DATA_DIR
RUN chmod -R 750 $DFS_DATA_DIR
# NodeManagerの環境設定
ENV YARN_LOCAL_DIR /grid/hadoop/yarn/local
ENV YARN_USER yarn
RUN mkdir -p $YARN_LOCAL_DIR
RUN chown -R $YARN_USER:$HADOOP_GROUP $YARN_LOCAL_DIR
RUN chmod -R 755 $YARN_LOCAL_DIR
ENV YARN_LOCAL_LOG_DIR /grid/hadoop/yarn/logs
RUN mkdir -p $YARN_LOCAL_LOG_DIR
RUN chown -R $YARN_USER:$HADOOP_GROUP $YARN_LOCAL_LOG_DIR
RUN chmod -R 755 $YARN_LOCAL_LOG_DIR
# HDFS Logsの環境設定
ENV HDFS_LOG_DIR /var/log/hadoop/hdfs
RUN mkdir -p $HDFS_LOG_DIR
RUN chown -R $HDFS_USER:$HADOOP_GROUP $HDFS_LOG_DIR
RUN chmod -R 755 $HDFS_LOG_DIR
# Yarn Logsの環境設定
ENV YARN_LOG_DIR /var/log/hadoop/yarn
RUN mkdir -p $YARN_LOG_DIR
RUN chown -R $YARN_USER:$HADOOP_GROUP $YARN_LOG_DIR
RUN chmod -R 755 $YARN_LOG_DIR
# HDFS Processの環境設定
ENV HDFS_PID_DIR /var/run/hadoop/hdfs
RUN mkdir -p $HDFS_PID_DIR
RUN chown -R $HDFS_USER:$HADOOP_GROUP $HDFS_PID_DIR
RUN chmod -R 755 $HDFS_PID_DIR
# Yarn Process IDの環境設定
ENV YARN_PID_DIR /var/run/hadoop/yarn
RUN mkdir -p $YARN_PID_DIR
RUN chown -R $YARN_USER:$HADOOP_GROUP $YARN_PID_DIR
RUN chmod -R 755 $YARN_PID_DIR
# JobHistory Server Logsの環境設定
ENV MAPRED_LOG_DIR /var/log/hadoop/mapred
ENV MAPRED_USER mapred
RUN mkdir -p $MAPRED_LOG_DIR
RUN chown -R $MAPRED_USER:$HADOOP_GROUP $MAPRED_LOG_DIR
RUN chmod -R 755 $MAPRED_LOG_DIR
# JobHistory Server Process IDの環境設定
ENV MAPRED_PID_DIR /var/run/hadoop/mapred
RUN mkdir -p $MAPRED_PID_DIR
RUN chown -R $MAPRED_USER:$HADOOP_GROUP $MAPRED_PID_DIR
RUN chmod -R 755 $MAPRED_PID_DIR
# Hadoop の環境設定
ENV HADOOP_CONF_DIR /etc/hadoop/conf
RUN chown -R $HDFS_USER:$HADOOP_GROUP $HADOOP_CONF_DIR/../
RUN chmod -R 755 $HADOOP_CONF_DIR/../
# 設定ファイルおよび、スクリプトをコンテナにコピー
COPY files/zookeeper/* $ZOOKEEPER_CONF_DIR/
COPY files/hadoop/* /etc/hadoop/conf/
COPY scripts/hdfs-setup.sh /
COPY scripts/start-zookeeper.sh /
COPY scripts/cmd.sh /
# ポート開放
EXPOSE 8020 8040 8088 9000 19888 50070
# コンテナ起動時に呼ばれるスクリプト
CMD ["/cmd.sh"]
3. docker-compose
上記の Dockerfile.hadoop を元に、hadoop273 という名前で Docker イメージを作成します。
その Docker イメージを元に、2つのコンテナを生成します。
- Resource Manager 用のコンテナ(container-hdp-rm1001)
- Node Manager 用のコンテナ(container-hdp-nm2001)
また、専用の docker network(hadoop_nw)を構築します。
これはコンテナ間でのネットワーク通信用です。
version: '2'
services:
# Resource Manager
rm1001:
build:
context: .
dockerfile: ./Dockerfiles/Dockerfile.hadoop
image: hadoop273
hostname: hdp-rm1001.docker
container_name: container-hdp-rm1001
ports:
- "8040:8040"
- "8088:8088"
- "9000:9000"
- "19888:19888"
- "50070:50070"
mem_limit: 1g
networks:
hadoop_nw:
ipv4_address: 172.20.0.2
extra_hosts:
- "hdp-nm2001.docker:172.20.0.3"
environment:
TAG: RM
# Node Manager
nm2001:
build:
context: .
dockerfile: ./Dockerfiles/Dockerfile.hadoop
image: hadoop273
hostname: hdp-nm2001.docker
container_name: container-hdp-nm2001
ports:
- "45454:45454"
- "50010:50010"
depends_on:
- "rm1001"
mem_limit: 1g
networks:
hadoop_nw:
ipv4_address: 172.20.0.3
extra_hosts:
- "hdp-rm1001.docker:172.20.0.2"
environment:
TAG: NM
networks:
hadoop_nw:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/16
gateway: 172.20.0.254
4. スクリプトファイルたち
cmd.sh (コンテナ起動時に呼ばれるスクリプト)
上記の docker-compose.yml で定義したTAG情報を元に、そのコンテナを ResourceManager(とNameNode) として起動するか、NodeManager(とDataNode)として起動するか判断させています。
また、このスクリプトが終了してしまうと、コンテナも終了してしまうため、スクリプトの最後で無限ループをつくり、スクリプト(コンテナ)が終了してしまわないようにしています。
#!/bin/bash
if [ "$TAG" = 'RM' ]; then
# ResourceManager の起動
/usr/hdp/2.6.2.0-205/hadoop-yarn/sbin/yarn-daemon.sh start resourcemanager &&
rm -rf /tmp/hadoop-root/dfs/data/current &&
# NameNode の起動
echo "Y" | /usr/hdp/2.6.2.0-205/hadoop/bin/hdfs --config /etc/hadoop/conf namenode -format &&
/usr/hdp/2.6.2.0-205/hadoop/sbin/hadoop-daemon.sh start namenode &&
sleep 60 &&
/bin/bash /hdfs-setup.sh &&
# JobHistoryServer の起動
/usr/hdp/2.6.2.0-205/hadoop-mapreduce/sbin/mr-jobhistory-daemon.sh start historyserver &&
# 無限ループをつくり、このスクリプトが終了しないようにしている
while true; do sleep 1000; done
elif [ "$TAG" = 'NM' ]; then
rm -rf /grid/hadoop/hdfs/dn/* &&
sleep 30 &&
# NodeManager の起動
/usr/hdp/2.6.2.0-205/hadoop-yarn/sbin/yarn-daemon.sh start nodemanager &&
# DataNode の起動
/usr/hdp/2.6.2.0-205/hadoop/sbin/hadoop-daemon.sh start datanode &&
/bin/bash /start-zookeeper.sh &&
# 無限ループをつくり、このスクリプトが終了しないようにしている
while true; do sleep 1000; done
fi
hdfs-setup.sh (cmd.sh 内で呼ばれるスクリプト)
HDFSに /user/my_data ディレクトリを作成しようとしています。必須ではなく、単にディレクトリを作成しようとしているだけです。
#!/bin/bash
set -e
hdfs dfs -mkdir -p /user/my_data
exit 0
start-zookeeper.sh (cmd.sh 内で呼ばれるスクリプト)
zookeeper を起動しようとしています。そのコンテナが NodeManager の場合、このスクリプトが呼び出されます。
#!/bin/bash
set -e
su - zookeeper -c "export ZOOCFGDIR=/usr/hdp/2.6.2.0-205/zookeeper/conf ; export ZOOCFG=zoo.cfg; source /usr/hdp/2.6.2.0-205/zookeeper/conf/zookeeper-env.sh ; export ZOO_LOG_DIR=/var/log/zookeeper ; /usr/hdp/current/zookeeper-server/bin/zkServer.sh start"
exit 0
5. 実行
docker-compose.yml ファイルが存在するディレクトリ上にて、docker-compose up -d を実行すると、ビルド・プルを自動で行ってから、依存関係に沿った順番でコンテナを起動してくれるはずです。
コマンドをまとめずに、RUNコマンドを一つずつのコマンドに対して書いたため、95ステップもあります。
$ docker-compose up -d
Creating network "273_hadoop_nw" with driver "bridge"
Building rm1001
Step 1/95 : FROM centos:centos6
centos6: Pulling from library/centos
...
---> 89b88369b754
Successfully built 89b88369b754
Successfully tagged hadoop273:latest
約20分ほどで起動できました。
docker image のサイズが 約1.7GB と大きいです。
元のイメージ centos6 から 約1.5GB 増えています。
RUNコマンドを呼ぶ回数を減らして、イメージレイヤーを最適化できれば、数百MB程度は削れるかもしれません。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hadoop273 latest 89b88369b754 18 minutes ago 1.71GB
centos centos6 d0957ffdf8a2 9 months ago 194MB
コンテナも ResourceManager(NameNode)用 と NodeManager(DataNode)用が起動されています。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9028c6b408bd hadoop273 "/cmd.sh" 11 minutes ago Up 11 minutes 8020/tcp, 8040/tcp, 8088/tcp, 9000/tcp, 0.0.0.0:45454->45454/tcp, 19888/tcp, 50070/tcp, 0.0.0.0:50010->50010/tcp container-hdp-nm2001
f6d1d724aca4 hadoop273 "/cmd.sh" 11 minutes ago Up 11 minutes 0.0.0.0:8040->8040/tcp, 0.0.0.0:8088->8088/tcp, 0.0.0.0:9000->9000/tcp, 0.0.0.0:19888->19888/tcp, 8020/tcp, 0.0.0.0:50070->50070/tcp container-hdp-rm1001
試しにNodeManager側のコンテナで bash を起動し、hdfs コマンドを実行してみます。
hdfs-setup.sh 内で作成する /user/my_data が生成されていることが確認できましたので HDFS が使える状態になっていることが分かります。
$ docker exec -it container-hdp-nm2001 bash
# hdfs dfs -ls /user
Found 1 items
drwxr-xr-x - root supergroup 0 2019-12-13 13:50 /user/my_data
6. さいごに
当初の目標であった"ローカルマシンで Docker を使って Hadoop の分散処理環境の構築"は確認することができました。
ここに HBase や Spark などを使って、実際のデータで分散処理や格納などを行うとすると、やはりスペック的に動作ができないなど、厳しい面が出てくると思われますが、小さいデータでも扱えるテスト環境が作れると楽しいだろうと思います。
イメージサイズが大きいことや、コンテナを起動し続けるために、無理やり while ループを使っているなど、ほかにも改善ポイントはたくさんありますが、初めての docker を見よう見まねでも動いたことには嬉しかったです。