Spark アプリケーションは、サードパーティ製の Java または Scala ライブラリに依存していることがよくあります。ここでは、Spark ジョブを Dataproc クラスタに送信する際に、このような依存関係を含めるうえで推奨される方法を紹介します。
gcloud dataproc jobs submit
コマンドを使用してローカルマシンからジョブを送信する場合は、--properties spark.jars.packages=[DEPENDENCIES]
フラグを使用します。
例:gcloud dataproc jobs submit spark \ --cluster=my-cluster \ --region=region \ --properties=spark.jars.packages='com.google.cloud:google-cloud-translate:1.35.0,org.apache.bahir:spark-streaming-pubsub_2.11:2.2.0'
クラスタでジョブを直接送信する場合、
--packages=[DEPENDENCIES]
パラメータを指定したspark-submit
コマンドを使用します。
例:spark-submit --packages='com.google.cloud:google-cloud-translate:1.35.0,org.apache.bahir:spark-streaming-pubsub_2.11:2.2.0'
依存関係による競合の回避
Spark アプリケーションの依存関係が Hadoop の依存関係と競合する場合、前述の手法は失敗する可能性があります。この競合は、Hadoop がその依存関係をアプリケーションの classpath に挿入すると、その依存関係がアプリケーションの依存関係よりも優先されるために発生します。競合が発生すると、NoSuchMethodError
またはその他のエラーが生成される場合があります。
例:
Guava は、Hadoop を含む多数のライブラリやフレームワークによって使用される、Java 用の Google コアライブラリです。ジョブまたはその依存関係により、Hadoop によって使用されているバージョンよりも新しいバージョンの Guava が必要になると、依存関係の競合が発生する可能性があります。
Hadoop v3.0 では、この問題は解決されましたが、以前の Hadoop バージョンに依存しているアプリケーションでは、依存関係の競合が発生する可能性を回避するために、次の 2 つの手順を行う必要があります。
- アプリケーションのパッケージとそのすべての依存関係を含む単一の JAR を作成します。
- 競合する依存関係パッケージを、この uber JAR 内に再配置して、それらのパス名が Hadoop の依存関係パッケージのパス名と競合しないようにします。 コードを変更する代わりに、プラグイン(下記を参照)を使用すると、パッケージ化プロセスの一部としてこの再配置(つまり、シェーディング)が自動的に行われます。
Maven を使用してシェーディングされた uber JAR を作成する
Maven は、Java アプリケーションを構築するためのパッケージ管理ツールです。Maven scala プラグインを使用して、Spark アプリケーションによって使用される言語である Scala で作成されたアプリケーションを構築できます。Maven shade プラグインを使用して、シェーディングされた JAR を作成できます。
com.google.common
パッケージに置かれている Guava ライブラリをシェーディングする pom.xml
構成ファイルの例を次に示します。この構成により、Maven に com.google.common
パッケージの名前を repackaged.com.google.common
に変更する指示が行われ、元のパッケージからクラスへのすべての参照が更新されます。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <groupId><!-- YOUR_GROUP_ID --></groupId> <artifactId><!-- YOUR_ARTIFACT_ID --></artifactId> <version><!-- YOUR_PACKAGE_VERSION --></version> <dependencies> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-sql_2.11</artifactId> <version><!-- YOUR_SPARK_VERSION --></version> <scope>provided</scope> </dependency> <!-- YOUR_DEPENDENCIES --> </dependencies> <build> <plugins> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> <configuration> <scalaVersion><!-- YOUR_SCALA_VERSION --></scalaVersion> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass><!-- YOUR_APPLICATION_MAIN_CLASS --></mainClass> </transformer> <!-- This is needed if you have dependencies that use Service Loader. Most Google Cloud client libraries do. --> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> </transformers> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/maven/**</exclude> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> <relocations> <relocation> <pattern>com</pattern> <shadedPattern>repackaged.com.google.common</shadedPattern> <includes> <include>com.google.common.**</include> </includes> </relocation> </relocations> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
ビルドするには、次のようにします。
mvn package
pom.xml
についての注意事項:
- ManifestResourceTransformer は、uber JAR のマニフェスト ファイル(
MANIFEST.MF
)にある属性を処理します。マニフェストで、アプリケーションのエントリ ポイントが指定されていることもあります。 - Spark は Dataproc にインストールされているため、Spark のスコープは
provided
です。 - Dataproc クラスタにインストールされている Spark のバージョンを指定します(Dataproc バージョン リストを参照)。アプリケーションで、Dataproc クラスタにインストールされているバージョンとは異なるバージョンの Spark が必要な場合、初期化アクションを作成するか、アプリケーションで使用している Spark バージョンをインストールするカスタム イメージを作成します。
<filters>
エントリによって、依存関係のMETA-INF
ディレクトリから署名ファイルが除外されます。このエントリがない場合、java.lang.SecurityException: Invalid signature file digest for Manifest main attributes
ランタイム例外が発生する可能性があります。これは、uber JAR のコンテキストでは署名ファイルが無効であるためです。- 複数のライブラリのシェーディングが必要になることがあります。これを行うには、複数のパスを含めます。
次の例では、Guava と Protobuf ライブラリがシェーディングされます。
<relocation> <pattern>com</pattern> <shadedPattern>repackaged.com</shadedPattern> <includes> <include>com.google.protobuf.**</include> <include>com.google.common.**</include> </includes> </relocation>
SBT を使用してシェーディングされた uber JAR を作成する
SBT は、Scala アプリケーションを構築するためのツールです。SBT を使用してシェーディングされた JAR を作成するには、まず、project/
ディレクトリ内に assembly.sbt
という名前のファイルを作成し、sbt-assembly
プラグインをビルド定義に追加します。
├── src/ └── build.sbt └── project/ └── assembly.sbt
続いて、次の行を assembly.sbt
に追加します。
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
com.google.common package
に置かれている Guava ライブラリをシェーディングする build.sbt
構成ファイルの例を次に示します。
lazy val commonSettings = Seq( organization := "YOUR_GROUP_ID", name := "YOUR_ARTIFACT_ID", version := "YOUR_PACKAGE_VERSION", scalaVersion := "YOUR_SCALA_VERSION", ) lazy val shaded = (project in file(".")) .settings(commonSettings) mainClass in (Compile, packageBin) := Some("YOUR_APPLICATION_MAIN_CLASS") libraryDependencies ++= Seq( "org.apache.spark" % "spark-sql_2.11" % "YOUR_SPARK_VERSION" % "provided", // YOUR_DEPENDENCIES ) assemblyShadeRules in assembly := Seq( ShadeRule.rename("com.google.common.**" -> "repackaged.com.google.common.@1").inAll )
ビルドするには、次のようにします。
sbt assembly
build.sbt
についての注意事項:
- SBT では厳密な競合解決戦略が使用されているため、上記の例のシェーディング ルールで一部の依存関係競合が解決されない場合があります。このため、
MergeStrategy.first
、last
、concat
、filterDistinctLines
、rename
、discard
方式を使用して、競合している特定のタイプのファイルを明示的にマージする、より詳細なルールの提供が必要になることがあります。詳細については、sbt-assembly
のマージ方式をご覧ください。 - 複数のライブラリのシェーディングが必要になることがあります。これを行うには、複数のパスを含めます。
次の例では、Guava と Protobuf ライブラリがシェーディングされます。
assemblyShadeRules in assembly := Seq( ShadeRule.rename("com.google.common.**" -> "repackaged.com.google.common.@1").inAll, ShadeRule.rename("com.google.protobuf.**" -> "repackaged.com.google.protobuf.@1").inAll )
Dataproc に uber JAR を送信する
Spark アプリケーションとその依存関係を含む、シェーディングされた uber JAR を作成したら、Dataproc にジョブを送信できるようになります。
次のステップ
- Maven と SBT の両方の構成ファイルを含むサンプルの Spark アプリケーションである spark-translate を確認します。
- Dataproc クイックスタートで Spark Scala ジョブを作成して実行し、Dataproc クラスタで Spark Scala ジョブを作成して実行する方法を学習します。