Building AWS Glue Data Catalog Client for Apache Hive Metastore
長い道のりを経て、なんとかCodeBuildで、Sparkを動かすためのなんちゃってEMR(without EMRFS)を用意したときの記録です。
最終的に、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 1.2.1-spark2のビルド
手順では何も言及がないが、forkされたHiveを使う。
GitHub - JoshRosen/hive at release-1.2.1-spark2
SparkはforkしたHiveに本当に依存している。
これにもパッチを当てる。
gpgは-Dgpg.skip=trueで省略。
hadoop-23.versionも変更。
JDK差異の対応
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あたりを読まないと分からない。
maven-dependency-pluginに悩まされる
古い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環境変数が使えるなら、そのタイミングで作成した方が良い。
hadoop-awsの追加
実際に動作確認をしていたら、
s3のファイルシステムが見つからない問題が発生。
sparkのdistributionには、hadoop-awsは含まれていないのだった。
mvn dependey:getでhadoop-awsとaws-java-sdk-bundleを各々単品で入手して、$SPARK_HOME/jarsに配置。
$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を集めるのはやはり無理が多い。
S3アクセス時に403エラー
クレデンシャルの設定がおかしいのか、
普通にIAMロールに権限が不足している。