長い道のりを経て、なんとかCodeBuildで、Sparkを動かすためのなんちゃってEMR(without EMRFS)を用意したときの記録です。
GitHub - awslabs/aws-glue-data-catalog-client-for-apache-hive-metastore: The AWS Glue Data Catalog is a fully managed, Apache Hive Metastore compatible, metadata repository. Customers can use the Data Catalog as a central repository to store structural and operational metadata for their data. AWS Glue provides out-of-box integration with Amazon EMR that enables customers to use the AWS Glue Data Catalog as an external Hive Metastore. This is an open-source implementation of the Apache Hive Metastore client on Amazon EMR clusters that uses the AWS Glue Data Catalog as an external Hive Metastore. It serves as a reference implementation for building a Hive Metastore-compatible client that connects to the AWS Glue Data Catalog. It may be ported to other Hive Metastore-compatible platforms such as other Hadoop and Apache Spark distributions
最終的に、CodeBuildからpytestを動かす。
テストケースでpysparkが動いて、spark.sqlからGlueデータカタログに繋がって、ロケーション情報からS3のデータを拾ってデータフレームを得られるようになる。
初めの頃に目指した構成は断念
初めはSparkとHadoopを別々に用意するつもりだった。
without Hadoopには、Hadoopだけではなく、Hiveも入ってない。これが最後の方でクラスパス地獄になったので諦めた。
最終的にはSparkのディストリビューション用のシェルに頼った部分がある。
- Spark with Hadoop
- Glue Metastore client
こう見ると、簡単に思うかもしれないが、そんなことはない。
Hiveを2回ビルドして、Glue Clientをビルドして、Sparkもビルドする。
1回通すのに40分は覚悟しないといけない。
CodeBuildはレイヤーのキャッシュが効かないので、まじで辛い。
もしも同じことをやるなら、EC2にdocker入れて、ある程度動くDockerfileを作ってからCodeBuildに持っていった方が試行錯誤の時間を短縮できる。
javaとscalaとpythonを知っていて、mavenのpomやapache ivyを知っていて、gitも軽く使えて、dockerも知っていて、AWSのCodeBuildとECRとIAMの知識があれば同じことが出来る。
今回のビルドに使う主なバージョン
Spark 2.4.5
Hadoop 2.8.5
Scala(binary version) 2.11
Hive 2.3.5
Hive 1.2.1-spark2
使うEMRのバージョンに合わせて、Spark/Hadoopのバージョンを決めた形。
Hive 2.3系のビルド
Hiveのbranch-2.3でビルドする手順になっているが、Hiveの2.3.6で入った変更で、Glue Client側でコンパイルエラーが発生する。
そのため、2.3.5でビルドした。
Hiveにはパッチを当てる。
はっきり言って、欲しいのはSpark用のGlue Clientなので、Hive 2.3.5はビルドしたくない。しかし、Glue Clientの手順を通すのために、渋々とビルドしている。
hadoop.versionも変更。
junitが見つからないエラーがでたりするが、pomの変更で対応可能、HiveのJIRAにパッチがある。
Hiveのビルドで、java8でのコンパイルが失敗する場合、コンパイルオプションに-Dignore.symbol.fileを付けるようにsedを書いた。
sedは最長一致になるので書き方に気を付ける。
Glue Clientのビルド
手順ではHiveのバージョンを書き換えることになってるが、普通に-Dで渡せば良い。
そのとき、xmllintを使ったりする。
xmllintでpom.xmlのようにスキーマ情報のあるエレメントの抽出はググればでてくる。
後で収集するので、installまでしておく。
spark.hive.metastore.jars用のjarの収集
いまだにこれが正解とは言い切れない。
spark-project:hive-execのruntimeを収集し、Glue Client側からはSpark Clientのjar(shaded済)を収集して、1つのディレクトリに集める。
除外しないといけないJarもあって、SparkのHiveUtilsとIsolatedClientLoaderあたりを読まないと分からない。
古いPOMが多く、当然プラグインもバージョンが古い。何がどうなってるか分からないので、githubでプラグインのコード(Mojo)も読みながら対応していった。
バージョン2.1でrepositoryUrlの指定についてメッセージが出るようなら、新しいバージョンを使って、remoteRepositoriesを使うのが良い。
Sparkのビルド
dev/change-scala-version.sh
dev/make-distribution.sh
を使った。
自分が使わないRなどは除外。
最終的にPython3で色々準備したいので、
make-distribution.shもpython3を使うようにsedで置換。
setuptoolsは先に入れておかないといけない。
zincの問題
alpineでSparkのビルドをやっていたとき、zinc周りの問題が解消出来なかった。installされたはずのzincがいつの間にか消えてしまう、no such file or directoryな問題。
このせいでalpineを諦めた。
javac 137の対応
Sparkのビルドでjavacが137を返していた。
特にspark-sqlの箇所でよく発生。
Hiveではjava8の問題があったが、同じようなログはなく、これは関係していなくて、137に気付かない限り解決に至らない。
メモリが足りてないのでCodeBuildの設定で、7GBメモリに変更。
sparkのインストール
tgzを作ったので、好きなところに展開して、シンボリックリンクを貼ったりした。
$SPARK_HOME/spark-defaults.confの作成
spark.master
spark.submit.deployMode
spark.pyspark.python
spark.pyspark.driver.python
spark.sql.hive.metastore.version
spark.sql.hive.metastore.jars
spark.sql.catalogImplementation
spark.sql.hive.metastore.jarsには、一箇所に集めておいたjarに対して、lsコマンドとtrコマンド(改行コードをコロンに置換)でクラスパスを作成したものを与える。
$SPARK_HOME/hive-site.xmlの作成
hive.metastore.client.factory.class
aws.region
aws.glue.endpoint
regionとendpointは、EC2だったら設定しなくても動くはずだが、CodeBuildのようなコンテナ環境では設定しないと動かない。
今回は、docker build中に埋め込んだが、CodeBuildでAWS_REGION環境変数が使えるなら、そのタイミングで作成した方が良い。
$SPARK_HOME/core-site.xmlの作成
fs.s3.impl
fs.s3a.aws.credential.provider
Glueのデータカタログにはs3で登録してあるため、s3スキーマが来た時に、S3Aの実装が使われるように設定。
CodeBuildで動かす場合、
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI環境変数が渡ってくるので、今回はEC2ContainerCredentialsProviderWrapperを使ってみた。このクラスの話は、hadoop awsのページには載ってない。最新版のhadoopにはこのクラスを使用したコードがあるが、今回使う2.8.5にはそれは含まれていない。
このクラスを使うと、アクセスキーなどは設定ファイルに書かなくても動く。
pysparkのインストール
Sparkの資材の中にあるものを利用して、python3 setup.py installした。
これはjarsにあるものをコピーしたりするので、もろもろ整ったあとに実施する。
aws-cliはインストールしないこと
Glue Clientだったり、hadoop-awsだったりの認証プロバイダの探索順の関係で干渉したりするので、入れないこと。hadoop-awsの方は認証プロバイダを変更できる。Glue Clientの方も変更できないことはないが、aws sdkのものを直接指定できないので、DefaultAWSCredentialsProviderChainが使われると考えると、~/.aws/credentialsが読み込まれないようにインストールしない方が良い。
HiveSessionStateBuilderが見つからない
without hadoopでやってたときに発生。
Sparkのspark-hiveが足りてない。
HiveConfが見つからない
Hiveのhive-commonが足りてない。
これは、パッチが当たってないといけないため、簡単に入手できるSparkのディストリビューションはそのまま使えない。
Py4jの問題
without hadoopでやってたときに発生。
バージョンの異なるPy4jが混ざると発生。
GatewayServerBuilderが、GatewayServerの staticメソッドを呼ぶ箇所だったはず。
異なるpomでjarを集めるのはやはり無理が多い。
whichコマンドが必要
without hadoopでやってたときに発生。
かつalpineでやってた時。
hadoopのどこかのシェルでwhichコマンドがないと正しく動かないものがいた。
S3アクセス時に403エラー
クレデンシャルの設定がおかしいのか、
普通にIAMロールに権限が不足している。
VPC内で動かすとき
GlueとS3のVPCエンドポイントの設定を忘れずに行うこと。