Part 2でセットアップしたAmbari on DockerとApache Zeppelinの環境にsbtを使ったビルド環境をDockerで作ってみます。ScalaのコードもZeppelinの画面上で書いて実行したほうが簡単なのですが、Sparkアプリのjarをspark-submit
する環境も作っておきます。
このシリーズ
- Apache Zeppelinでデータ分析を分散処理する - Part 1: データ分析のライフサイクル
- Apache Zeppelinでデータ分析を分散処理する - Part 2: Ambari on DockerにZeppelinをセットアップする
- Apache Zeppelinでデータ分析を分散処理する - Part 3: ZeppelinでHiveを使えるようにする
- Apache Zeppelinでデータ分析を分散処理する - Part 4: Ambari on Dockerのambari-functionsを使ってみる
プロジェクト
プロジェクトのディレクトリを作成します。
$ mkdir -p ~/scala_apps/spike
$ cd !$
DockerとDocker Compose
sbtのビルド環境はDockerとDocker Compose上に構築します。コンテナは使い捨て、プロジェクトのディレクトリはDockerホストからマウントします。成果物のjarもDokcerホスト上に作成されます。ScalaのコーディングはDockerホスト上のエディタか、別のコーディング用コンテナで行う想定です。
Dockerfile
DockerfileはオフィシャルjavaのOpenJDK 7をベースにします。Apache Ivyのartifactsのキャッシュディレクトリとsbtのグローバルセッティングのディレクトリをボリュームにします。この2つは実行時にDockerホストのディレクトリをマウントして永続化します。sbtを始めて実行する時にartifactsをたくさんダウンロードするのですが、これが結構時間がかかります。
FROM java:openjdk-7-jdk
MAINTAINER Masato Shimizu <ma6ato@gmail.com>
ENV SBT_VERSION 0.13.9
ENV PATH ${PATH}:/usr/local/sbt/bin
WORKDIR /app
RUN wget -O- http://dl.bintray.com/sbt/native-packages/sbt/$SBT_VERSION/sbt-$SBT_VERSION.tgz | tar xz -C /usr/local
VOLUME ["/root/.ivy2", "/root/.sbt", "/app"]
docker-compose.yml
docker-compose.ymlファイルにコンテナを起動するときの設定を書いておきます。Ambari on Dockerで構成するHadoopクラスタのConsulを使ったDNSを外部から利用しています。sbtコンテナでビルドしたjarファイルをWebHDFS経由でPUTするときにHadoopノードの名前解決が必要になります。
sbt:
build: .
volumes:
- .:/app
- /temp/.sbt:/root/.sbt
- /temp/.ivy2:/root/.ivy2
dns:
- $AMB_CONSUL
- 172.17.0.1
- 8.8.8.8
dns_search:
- service.consul
Dockerイメージをbuidします。
$ docker-compose build
Scala REPL
Scalaのインタープリターをsbt console
コマンドから起動してみます。AMB_CONSUL
環境変数はREPLの起動では使いませんが、docker-compose.ymlに定義してある環境変数が見つからないと警告が出るため最初にexportしておきます。
$ export AMB_CONSUL=$(docker inspect --format="{{ .NetworkSettings.IPAddress }}" amb-consul)
$ docker-compose run --rm sbt sbt console
[info] Set current project to Sample app (in build file:/app/)
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.10.5 (OpenJDK 64-Bit Server VM, Java 1.7.0_91).
Type in expressions to have them evaluated.
Type :help for more information.
scala> :quit
sbtコンテナ
Scalaのコードを配置するディレクトリを作成します。
$ cd ~/scala_apps/spike
$ mkdir -p src/main/scala/
SampleApp.scala
SparkのLICENSEファイルの行数を数える単純なSparkアプリを書きます。
import org.apache.spark.{SparkContext, SparkConf}
object SampleApp {
def main(args: Array[String]) {
val conf = new SparkConf().setAppName("App")
val sc = new SparkContext(conf)
val src = sc.textFile("LICENSE")
val lines = src.count()
println("Lines %,d".format(lines))
}
}
build.sbt
プロジェクトのビルド定義を書きます。sbtの0.13.9に合わせてScalaは2.10.5を指定します。HDP 2.3と同じSpark 1.4.1をビルド用に使います。今回は最初なのでspark-submit
するjarはsbt assemblyを使ったuberjarではありません。
name := "Sample app"
version := "1.0"
scalaVersion := "2.10.5"
libraryDependencies += "org.apache.spark" %% "spark-core" % "1.4.1"
sbt package
プロジェクトのディレクトリはScalaのソースコードを含めて以下のようになりました。
$ tree
.
├── Dockerfile
├── build.sbt
├── docker-compose.yml
└── src
└── main
└── scala
└── SampleApp.scala
sbtコンテナを使ってSparkアプリをビルドします。target/scala-2.10/sample-app_2.10-1.0.jar
ファイルがDockerホスト上に作成されました。
$ docker-compose run --rm sbt sbt package
[info] Set current project to Sample app (in build file:/app/)
[info] Packaging /app/target/scala-2.10/sample-app_2.10-1.0.jar ...
[info] Done packaging.
[success] Total time: 1 s, completed Dec 26, 2015 4:13:52 AM
HDFS
ここからはAmbari on Dockerで構成されたHadoopクラスタのコンテナを使います。HDFSのNameNodeであるamb1
ノードを使います。
今回はrootユーザーのまま作業をしているので、HDFSの/user
ディレクトリにユーザーのホームディレクトリがあることを確認します。
$ docker exec amb1 hadoop fs -ls /user
Found 5 items
drwxrwx--- - ambari-qa hdfs 0 2015-12-25 17:08 /user/ambari-qa
drwxr-xr-x - hcat hdfs 0 2015-12-25 17:10 /user/hcat
drwx------ - hive hdfs 0 2015-12-25 17:10 /user/hive
drwxr-xr-x - root hdfs 0 2015-12-26 02:40 /user/root
drwxrwxr-x - spark hdfs 0 2015-12-25 17:09 /user/spark
/user/root
ディレクトリがなければ、以下のようにhdfs
ユーザーにスイッチして作成します。
$ su hdfs
$ hadoop fs -mkdir /user/root
$ hadoop fs -chown root /user/root
サンプルのSparkアプリで利用するLICENSEファイルはHDPの以下のディレクトリにあります。
/usr/hdp/2.3.4.0-3485/spark/LICENSE
LICENSEファイルをyarn-cluster
モードで実行するSparkアプリから読み込むためにHDFSにputしておきます。
$ docker exec amb1 hadoop fs -put /usr/hdp/2.3.4.0-3485/spark/LICENSE /user/root
$ docker exec amb1 hadoop fs -ls /user/root/LICENSE
-rw-r--r-- 3 root hdfs 17356 2015-12-25 17:20 LICENSE
SparkアプリのjarファイルもWebHDFS経由でputします。
$ docker-compose run --rm sbt \
curl -L -X PUT -T /app/target/scala-2.10/sample-app_2.10-1.0.jar "http://amb1:50070/webhdfs/v1/user/root/test/sample-app_2.10-1.0.jar?user.name=root&op=CREATE&overwrite=true"
HDFSに/user/test/sample-app_2.10-1.0.jar
ファイルが作成されました。
$ docker exec amb1 hadoop fs -ls ./test/sample-app_2.10-1.0.jar
-rwxr-xr-x 3 root hdfs 1862 2015-12-26 04:16 test/sample-app_2.10-1.0.jar
spark-submit
最後にspark-submit
をしてHDFSにputしたSparkアプリを実行します。--master
フラグにはyarn-cluster
を指定してYARNのアプリケーションマスターのプロセスで実行しています。
$ docker exec amb1 \
spark-submit \
--class SampleApp \
--master yarn-cluster \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 1 \
hdfs://amb1/user/root/test/sample-app_2.10-1.0.jar
...
15/12/26 02:54:18 INFO Client:
client token: N/A
diagnostics: N/A
ApplicationMaster host: 172.17.0.5
ApplicationMaster RPC port: 0
queue: default
start time: 1451098443955
final status: SUCCEEDED
tracking URL: http://amb1.service.consul:8088/proxy/application_1451063385654_0011/
user: root
15/12/26 02:54:18 INFO ShutdownHookManager: Shutdown hook called
15/12/26 02:54:18 INFO ShutdownHookManager: Deleting directory /tmp/spark-c30cdca1-6f2b-4432-a866-c63391e619b2
yarn longコマンドからSparkアプリの実行結果を確認します。SparkのLICENSEファイルは294行ありました。
$ docker exec amb1 \
yarn logs -applicationId application_1451063385654_0011
...
LogType:stdout
Log Upload Time:Sat Dec 26 02:54:19 +0000 2015
LogLength:10
Log Contents:
Lines 294
End of LogType:stdout