[小ネタ] Glueのローカル実行でgluepytestをする時に気を付けたいこと

2020.03.10

こんにちは!DA(データアナリティクス)事業本部 インテグレーション部の大高です。

Glueのライブラリはローカル実行用に公開されており、ローカル環境でも実行することができます。また、pytestの実行用に「gluepytest」コマンドも用意されているので、Pythonコードのテストにはこちらが利用可能です。

今回、実際にPythonコードのローカルでのテスト実行環境の検証を行ってみたのですが、いくつかハマったことがあったので注意点を書いてみたいと思います。

前提

今回試したのはGlue version 1.0(Python 3)で記述したGlueのコードをgluepytestでテストしようとしたものになります。

また、環境構築については、下記の記事を参考に作成してみました。

この上で、以下が私がハマったことになります。

注意点1:Glue version 1.0(Python 3)の場合は「glue-1.0」のブランチを利用する

下記のGitHubリポジトリからaws-glue-libsをCloneする際ですが、ブランチが2つあります。1つはmasterで、これはGlue version 0.9になります。もう1つがglue-1.0で、こちらはGlue version 1.0になります。

今回はPython3であるGlue version 1.0を利用したかったのでglue-1.0ブランチからCloneすべきだったのですが、意識せずにCloneしていたのでmasterブランチからCloneしてしまっていました。

下記ドキュメントの「ローカル Python 開発の前提条件」の「2.」にもちゃんと書いてありました。

次のいずれかを行ってください。

  • Glue バージョン 0.9 の場合は、 master ブランチにとどまります。
  • Glue バージョン 1.0 の場合は、ブランチ glue-1.0 を確認します。このバージョンは Python 3 をサポートしています。

ちなみに、Clone先のパスはどこでも問題ありませんがPATHを通しておくと利用時に便利です。

注意点2:「SPARK_HOME」環境変数の設定

下記ドキュメント「ローカル Python 開発の前提条件」の「4.」と「5.」にある通り、Glue用のApache SparkをダウンロードしてSPARK_HOME環境変数を適切に設定する必要があります。

Apache Spark自体はどこに置いてもよいのですが、SPARK_HOME環境変数をきちんと置いた場所に合わせて設定しないとgluepytestの実行時に、以下のようにpysparkが無いというエラーが起きますので気を付けましょう。

E   ModuleNotFoundError: No module named 'pyspark'

注意点3:Pythonのバージョン

Python3の場合、Pythonのサポートバージョンは3.6になりますので、ここもキッチリ合わせます。うっかり3.8で実行したりすると以下のようにエラーが起きます。

/opt/spark-2.4.3-bin-spark-2.4.3-bin-hadoop2.8/python/pyspark/cloudpickle.py:126: in _make_cell_set_template_code
    return types.CodeType(
E   TypeError: an integer is required (got type bytes)

Python シェルジョブを使用して、AWS Glue でシェルとして Python スクリプトを実行できます。Python シェルジョブを使用すると、Python 2.7 または Python 3.6 と互換性のあるスクリプトを実行することができます。

注意点4:Issueへの対応

基本的にはここまでの準備で良いのですが、gluepytestを実行すると以下のエラーが起きました。

E   py4j.protocol.Py4JJavaError: An error occurred while calling None.org.apache.spark.api.java.JavaSparkContext.
E   : java.lang.NoSuchMethodError: io.netty.buffer.PooledByteBufAllocator.defaultNumHeapArena()I

2020/03/10現在においては、下記のIssueがあるようで、私が試した限りでは対応が必要でした。

私の対応としてはIssueへのコメントを参考に2つを実施しました。

1つは、下記2つのjarファイルを削除しました。

  • aws-glue-libs/jarsv1/netty-3.6.2.Final.jar
  • aws-glue-libs/jarsv1/netty-all-4.0.23.Final.jar

もう1つは、Issueへのコメントの通りにglue-setup.shを修正して、jarを削除する行を追加しておきました。

# Run mvn copy-dependencies target to get the Glue dependencies locally
mvn -f $ROOT_DIR/pom.xml -DoutputDirectory=$ROOT_DIR/jarsv1 dependency:copy-dependencies

# ADD THESE TWO LINES
rm ${GLUE_JARS_DIR}/netty-3.6.2.Final.jar
rm ${GLUE_JARS_DIR}/netty-all-4.0.23.Final.jar

注意点5:テスト対象コードの記述

上記までで正常にテスト実行はできるようになったのですが、今度は別の問題が発生しました。

テストコードからは、テスト対象コードをimportしてテストを記述しますが、Glueのコードが以下のようになっているとちょっと問題が起きます。

import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job

def test_target_func(bar):
    foo = bar
    return foo

## @params: [JOB_NAME]
args = getResolvedOptions(sys.argv, ['JOB_NAME'])

このようなジョブのtest_target_funcをテストしたいと思ってテストコードからimportすると、コード全体が評価がされてしまうので、以下のようにJOB_NAME引数が無いよというエラーが起きてしまうのです。

E   awsglue.utils.GlueArgumentError: the following arguments are required:

回避策としては、Glue側のコードをmain関数に入れてあげて、かつ、自身のコードがエントリポイントとして呼ばれている時だけ実行されるように以下のコードを記述してあげます。

if __name__ == '__main__':
    main(sys.argv)

これで、テストコードでimportしてtest_target_funcのみをテストすることができました。

まとめ

以上、「Glueのローカル実行でgluepytestをする時に気を付けたいこと」でした。Glueのローカル実行はとても便利だと思うので、うまく活用していければと思います。

どなたかのお役に立てば幸いです。それでは!