きどたかのブログ

いつか誰かがこのブログからトラブルを解決しますように。

Building AWS Glue Data Catalog Client for Apache Hive Metastore

長い道のりを経て、なんとか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に持っていった方が試行錯誤の時間を短縮できる。

javascalapythonを知っていて、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-awsaws-java-sdk-bundleを各々単品で入手して、$SPARK_HOME/jarsに配置。

aws-java-sdk-bundleには依存ライブラリも名前空間を変えて含まれているので楽。

$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エンドポイントの設定を忘れずに行うこと。

spark_env.shはなくても良い

without hadoopのときは、SPARK_DIST_CLASSPATH環境変数を設定するものだが、with hadoopのときは不要。