EC2 A1インスタンスでApache Hadoopをビルドしてみる

気がつけばARMアーキテクチャのインスタンスがAWSで使えるようになっていたので、Apache Hadoopのビルドを試してみます。

今回はAmazon Linux、a1.xlargeを起動します。

```
$ uname -a
Linux ip-172-31-9-141.us-west-2.compute.internal 4.14.77-81.59.amzn2.aarch64 #1 SMP Mon Nov 12 21:28:57 UTC 2018 aarch64 aarch64 aarch64 GNU/Linux
```

CPU情報を確認してみますが、Featuresが少なくて壮観です

```
$ cat /proc/cpuinfo
processor	: 0
BogoMIPS	: 166.66
Features	: fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
CPU implementer	: 0x41
CPU architecture: 8
CPU variant	: 0x0
CPU part	: 0xd08
CPU revision	: 3

processor	: 1
BogoMIPS	: 166.66
Features	: fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
CPU implementer	: 0x41
CPU architecture: 8
CPU variant	: 0x0
CPU part	: 0xd08
CPU revision	: 3

processor	: 2
BogoMIPS	: 166.66
Features	: fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
CPU implementer	: 0x41
CPU architecture: 8
CPU variant	: 0x0
CPU part	: 0xd08
CPU revision	: 3

processor	: 3
BogoMIPS	: 166.66
Features	: fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
CPU implementer	: 0x41
CPU architecture: 8
CPU variant	: 0x0
CPU part	: 0xd08
CPU revision	: 3
```

Hadoopをビルドする場合、通常では、`-Pnative`のオプションを有効にしてC/C++のライブラリ(native library)もビルドするのですが、今回は簡単のため、native libraryのビルドは無効にします。Native libraryのビルドを無効化してもHadoopが動かなくなることはないのですが、[HDFS Short-Circuit Local Reads](https://hadoop.apache.org/docs/r3.1.1/hadoop-project-dist/hadoop-hdfs/ShortCircuitLocalReads.html)などの、native libraryに依存する高速化の機能が使えなくなります。

また、Hadoopをビルドするときには `yum install` で入ってくれないライブラリをいくつかインストールする必要があります。その代表例がApache Maven(>= 3.3)とProtocol Buffers(= 2.5.0)なのですが、Protocol Buffersについては、現在のAmazon Linuxだと `yum install protobuf*` でいとも簡単に入ってくれます。Mavenは手でインストールしてあげる必要がありますが、現状A1インスタンスは東京リージョンでサポートしていないので、ASF(Apache Software Founcation)のミラーサイトのうち近い所から意識してダウンロードしてあげないとダウンロードに時間がかかります。ダウンロード元のインスタンスから以下のコマンドを実行することで、最も近いミラーサイトを取得できます。

```
$ curl -s 'https://www.apache.org/dyn/closer.cgi?as_json=1' | python -c "import sys, json; print json.load(sys.stdin)['preferred']"
http://us.mirrors.quenda.co/apache/
```

情報元: https://stackoverflow.com/questions/21534797/finding-the-closest-apache-software-foundation-mirror-programatically

ここまで準備が終わったところでHadoopをビルドしてみると、YARN CSI(Container Storage Interface)のビルドで失敗します。

```
[ERROR] Failed to execute goal org.xolstice.maven.plugins:protobuf-maven-plugin:0.5.1:compile-custom (default) on project hadoop-yarn-csi: Missing:
[ERROR] ----------
[ERROR] 1) io.grpc:protoc-gen-grpc-java:exe:linux-aarch_64:1.15.1
[ERROR] 
[ERROR]   Try downloading the file manually from the project website.
[ERROR] 
[ERROR]   Then, install it using the command: 
[ERROR]       mvn install:install-file -DgroupId=io.grpc -DartifactId=protoc-gen-grpc-java -Dversion=1.15.1 -Dclassifier=linux-aarch_64 -Dpackaging=exe -Dfile=/path/to/file
[ERROR] 
[ERROR]   Alternatively, if you host your own repository you can deploy the file there: 
[ERROR]       mvn deploy:deploy-file -DgroupId=io.grpc -DartifactId=protoc-gen-grpc-java -Dversion=1.15.1 -Dclassifier=linux-aarch_64 -Dpackaging=exe -Dfile=/path/to/file -Durl=[url] -DrepositoryId=[id]
[ERROR] 
[ERROR]   Path to dependency: 
[ERROR]   	1) org.apache.hadoop:hadoop-yarn-csi:jar:3.3.0-SNAPSHOT
[ERROR]   	2) io.grpc:protoc-gen-grpc-java:exe:linux-aarch_64:1.15.1
[ERROR] 
[ERROR] ----------
[ERROR] 1 required artifact is missing.
[ERROR] 
[ERROR] for artifact: 
[ERROR]   org.apache.hadoop:hadoop-yarn-csi:jar:3.3.0-SNAPSHOT
[ERROR] 
[ERROR] from the specified remote repositories:
[ERROR]   apache.snapshots.https (https://repository.apache.org/content/repositories/snapshots, releases=true, snapshots=true),
[ERROR]   repository.jboss.org (http://repository.jboss.org/nexus/content/groups/public/, releases=true, snapshots=false),
[ERROR]   central (https://repo.maven.apache.org/maven2, releases=true, snapshots=false)
[ERROR] 
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
[ERROR] 
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <goals> -rf :hadoop-yarn-csi
```

`io.grpc:protoc-gen-grpc-java:exe:linux-aarch_64:1.15.1` が存在しないと言われています。呼び出し元は以下の通り

```xml:hadoop-yarn-project/hadoop-yarn/hadoop-yarn-csi/pom.xml
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${protobuf-maven-plugin.version}</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.6.1:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.15.1:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
```

`${os.detected.classifier}`が利用され、アーキテクチャに対応したartifactを取りに行こうとしますが、`io.grpc:protoc-gen-grpc-java`にaarch_64のリリースが存在しないために失敗しています。この問題を解決するには大きく2通りあります。

* YARN CSIのビルドを諦める
* 自前で `io.grpc:protoc-gen-grpc-java` をビルドする

ビルドを諦める場合は、以下の設定から `hadoop-yarn-csi` を取り除けば完了です。

```xml:hadoop-yarn-project/hadoop-yarn/pom.xml
  <modules>
    <module>hadoop-yarn-api</module>
    <module>hadoop-yarn-common</module>
    <module>hadoop-yarn-server</module>
    <module>hadoop-yarn-applications</module>
    <module>hadoop-yarn-site</module>
    <module>hadoop-yarn-client</module>
    <module>hadoop-yarn-registry</module>
    <module>hadoop-yarn-ui</module>
    <module>hadoop-yarn-csi</module>
  </modules>
```

これでは面白くないので、自前でビルドしてみることにしましょう。

* protobufをビルド

```
$ git clone https://github.com/google/protobuf.git
$ cd protobuf
$ git checkout v3.5.1.1
$ ./autogen.sh && ./configure && make
```

* 環境変数の設定

```
$ export CXXFLAGS="-I/home/aajisaka/git/protobuf/src -L/home/aajisaka/git/protobuf/src/.libs"
$ export LDFLAGS="$CXXFLAGS"
$ export LD_LIBRARY_PATH="/home/aajisaka/git/protobuf/src"
```

* grpc-javaをビルド

```
$ git clone https://github.com/grpc/grpc-java.git
$ cd grpc-java
$ git checkout v1.15.1
$ ./gradlew build --no-daemon
$ ./gradlew install --no-daemon
```

参考にした記事

* TensorFlowをPPC64eでビルドする: https://medium.com/@alapha23/tensorflow-1-2-0-built-from-source-on-ppc64le-4a83ae1b2e27
* 環境変数を変更しても `--no-daemon` を入れるかdaemonを殺さないとコンパイルオプションに反映されない: 
https://github.com/grpc/grpc-java/issues/2888
  * かなりハマった...

Local repositoryを見ると、pomとexeが正しく配置されていそうに見えます。これでようやくHadoopをビルドできそうです!

```
$ ls -alh ~/.m2/repository/io/grpc/protoc-gen-grpc-java/1.15.1/
total 3.0M
drwxrwxr-x 2 aajisaka aajisaka  288 Dec 21 06:44 .
drwxrwxr-x 3 aajisaka aajisaka   52 Dec 21 06:44 ..
-rw-rw-r-- 1 aajisaka aajisaka 3.0M Dec 21 06:43 protoc-gen-grpc-java-1.15.1-linux-aarch_64.exe
-rw-rw-r-- 1 aajisaka aajisaka  559 Dec 21 01:40 protoc-gen-grpc-java-1.15.1-linux-aarch_64.exe.lastUpdated
-rw-rw-r-- 1 aajisaka aajisaka 2.1K Dec 21 06:44 protoc-gen-grpc-java-1.15.1.pom
-rw-rw-r-- 1 aajisaka aajisaka  513 Dec 21 01:40 protoc-gen-grpc-java-1.15.1.pom.lastUpdated
-rw-rw-r-- 1 aajisaka aajisaka   40 Dec 21 01:40 protoc-gen-grpc-java-1.15.1.pom.sha1
-rw-rw-r-- 1 aajisaka aajisaka  181 Dec 21 01:40 _remote.repositories
```

ビルド!

```
$ mvn install -DskipTests
(snip)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  17:19 min
[INFO] Finished at: 2018-12-21T07:07:36Z
[INFO] ------------------------------------------------------------------------
```

めでたしめでたし。

しかしながら、現実にHadoopをARMアーキテクチャで利用するためにはnative libraryのビルドも必要でしょうし、本当にARMに変更してコスパが良くなるのかベンチマークする必要があるでしょう。特に、CPUのハードウェア命令に頼った処理をしている場合は注意が必要です。例えば、HDFSのデータ暗号化にはAES-NIを、HDFS Erasure CodingにはISA-Lをそれぞれ高速化のために利用しているため、ARMでこれらの処理を充分高速に実行できるかは未知数です。

気が向いたらnative libraryのビルドも頑張ってみます。

## 追記

* 自前でprotobufやgrpc-javaをビルドしなくてもいいように、必要なartifactを用意しました: http://home.apache.org/~aajisaka/repository/io/grpc/protoc-gen-grpc-java/1.15.1/
    * もちろん利用は自己責任で
* HDFSのデータ暗号化で利用されるAES-CTRを動かしてみたけど、悪くなさそう。

```
$ openssl speed -evp aes-256-ctr
Doing aes-256-ctr for 3s on 16 size blocks: 57872810 aes-256-ctr's in 3.00s
Doing aes-256-ctr for 3s on 64 size blocks: 38476148 aes-256-ctr's in 3.00s
Doing aes-256-ctr for 3s on 256 size blocks: 16757050 aes-256-ctr's in 3.00s
Doing aes-256-ctr for 3s on 1024 size blocks: 5071541 aes-256-ctr's in 3.00s
Doing aes-256-ctr for 3s on 8192 size blocks: 688484 aes-256-ctr's in 3.00s
OpenSSL 1.0.2k-fips  26 Jan 2017
built on: reproducible build, date unspecified
options:bn(64,64) md2(int) rc4(ptr,char) des(idx,cisc,16,int) aes(partial) idea(int) blowfish(ptr) 
compiler: gcc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DKRB5_MIT -DL_ENDIAN -Wall -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches   -Wa,--noexecstack -DPURIFY -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-256-ctr     308654.99k   820824.49k  1429934.93k  1731085.99k  1880020.31k
```

参考: https://medium.com/@voluntas/cpu-%E3%81%AE-aes-%E9%AB%98%E9%80%9F%E5%8C%96-21459540ed8