Spring BatchをCloud Foundry上で使う際のうまく実行するTipsのメモ
目次
- Hello WorldなSpring Batchアプリケーションの作成
- Cloud Foundry上でTaskとしてSpring Batchのジョブを実行する
- Spring BatchのJob実行履歴の永続化
- Scheduler for PCFを利用してTaskを定期実行する
Hello WorldなSpring Batchアプリケーションの作成
まずは簡単なBatchアプリケーションを作成します。ジョブのメタデータや実行履歴を保存するデータベースは組み込みのH2を使用します。
curl https://start.spring.io/starter.tgz \
-d artifactId=hello-spring-batch \
-d baseDir=hello-spring-batch \
-d packageName=com.example.hello \
-d dependencies=batch,h2 \
-d applicationName=HelloSpringBatchApplication | tar -xzvf -
cd hello-spring-batch
Hello WorldなTasklet
を一つ定義します。
cat <<EOF > src/main/java/com/example/hello/JobConfig.java
package com.example.hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@EnableBatchProcessing
@Configuration
public class JobConfig {
private final Logger log = LoggerFactory.getLogger(JobConfig.class);
private final StepBuilderFactory stepBuilderFactory;
private final JobBuilderFactory jobBuilderFactory;
public JobConfig(StepBuilderFactory stepBuilderFactory, JobBuilderFactory jobBuilderFactory) {
this.stepBuilderFactory = stepBuilderFactory;
this.jobBuilderFactory = jobBuilderFactory;
}
@Bean
public Step step1() {
return this.stepBuilderFactory.get("step1") //
.tasklet((stepContribution, chunkContext) -> {
log.info("Hello World!");
return RepeatStatus.FINISHED;
}) //
.build();
}
@Bean
public Job job1() {
return this.jobBuilderFactory.get("job1") //
.incrementer(new RunIdIncrementer()) //
.start(step1()) //
.build();
}
}
EOF
ここで一つTipを。ローカル環境では普通にジョブを実行できるが、Cloud Foundry上ではコマンドライン引数に--spring.batch.job.enabled=true
明示的につけないとジョブが実行されないようにします。これはこのアプリケーションがうっかりCloud Foundry上のLong Running Processとして起動してしまった時にジョブが実行されないようにするためにです。Cloud Foundry上ではTaskとして明示的に実行した時にのみジョブが実行されるようにします。
Cloud Foundry上ではcloud
プロファイルが有効になるため、application-cloud.properties
に次の設定を行えば、Cloud Foundry上のみspring.batch.job.enabled
がデフォルトでfalse
になります。ローカル環境では--spring.profiles.active=cloud
を指定しない限りは無視されます。
cat <<EOF > src/main/resources/application-cloud.properties
spring.batch.job.enabled=false
EOF
次の設定は必須ではありませんが、以下の説明(ログのキャプチャ)ではこの設定でログ出力を減らしています。
cat <<EOF > src/main/resources/application.properties
logging.level.root=WARN
logging.level.com.example=INFO
logging.level.org.springframework.batch=INFO
EOF
アプリケーションをビルドします。
./mvnw clean package -DskipTests=true
ローカル環境で実行します。
java -jar target/hello-spring-batch-0.0.1-SNAPSHOT.jar
まずは普通のSpring Batchアプリケーションを作りました。
Cloud Foundry上でTaskとしてSpring Batchのジョブを実行する
次にこのアプリケーションをCloud Foundryにデプロイします。ここではPivotal Web Servicesを使います。他のCFでも同じです。
基本的にはcf push
でjarをデプロイすれば良いのですが、このアプリはWebアプリではないので、--health-check-type
にnone
を指定します。
Long Running Processではなく、Taskとして実行したいので-i 0
にしてインスタンス数を0にしつつコンテナイメージだけ作ります。
cf push hello-batch -p target/hello-spring-batch-0.0.1-SNAPSHOT.jar -m 768m --health-check-type none --random-route -i 0
次にこのコンテナをTaskとして実行します。cf run-task <app-name> <command
を実行すれば良いのですが、CF上でSpringアプリを実行するコマンドはjava -jar <jar-file>
ではなく、Buildpackが適切に決めてくれていました。
Taskではこのコマンドを明示的に設定する必要があります。Buildpackで設定された実行コマンドは次のコマンドで確認可能です。
cf curl /v2/apps/$(cf app hello-batch --guid) | jq -r .entity.detected_start_command
実行結果は次のようになります。
JAVA_OPTS="-agentpath:$PWD/.java-buildpack/open_jdk_jre/bin/jvmkill-1.16.0_RELEASE=printHeapHistogram=1 -Djava.io.tmpdir=$TMPDIR -XX:ActiveProcessorCount=$(nproc) -Djava.ext.dirs=$PWD/.java-buildpack/container_security_provider:$PWD/.java-buildpack/open_jdk_jre/lib/ext -Djava.security.properties=$PWD/.java-buildpack/java_security/java.security $JAVA_OPTS" && CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-3.13.0_RELEASE -totMemory=$MEMORY_LIMIT -loadedClasses=13479 -poolType=metaspace -stackThreads=250 -vmOptions="$JAVA_OPTS") && echo JVM Memory Configuration: $CALCULATED_MEMORY && JAVA_OPTS="$JAVA_OPTS $CALCULATED_MEMORY" && MALLOC_ARENA_MAX=2 SERVER_PORT=$PORT eval exec $PWD/.java-buildpack/open_jdk_jre/bin/java $JAVA_OPTS -cp $PWD/. org.springframework.boot.loader.JarLauncher
これをcf run-task
で実行するには次のコマンドを実行します。上記のコマンドの末尾に--spring.batch.job.enabled=true
を加えてジョブを有効にするのがポイントです。
Taskのログを見るためにcf run-task
実行前に別のターミナルでcf logs hello-batch
を実行しておきます。
cf run-task hello-batch "$(cf curl /v2/apps/$(cf app hello-batch --guid) | jq -r .entity.detected_start_command) --spring.batch.job.enabled=true"
cf logs hello-batch
でログを確認すると、Jobが実行されていることが確認できます。
Taskの実行履歴はcf tasks
で確認できます。
cf tasks hello-batch
Spring BatchのJob実行履歴の永続化
Taskの実行履歴はCloud Foundry側で保持されていますが、Spring Batch自体もJobの実行履歴を永続化する機能を初めから持っています。今回保存されていないのはインメモリなH2データベースを使用しているためです。 H2ではなく、MySQLなどを使用すればSpring Batch側でJobのメタデータを永続化することができます。
Pivotal Web Servicesで利用可能なcleardb
サービスのspar
プラン(free)でMySQLのサービスインスタンスを作成し、hello-batch
アプリにバインドします。
cf create-service cleardb spark job-db
cf bind-service hello-batch job-db
cf restage
でコンテナイメージを作り直します。この時JDBCドライバーが自動で組み込まれます。
cf restage hello-batch
起動時のログを見ればMySQLが使われていることがわかります。
cf run-task
を3回実行します。
cf run-task hello-batch "$(cf curl /v2/apps/$(cf app hello-batch --guid) | jq -r .entity.detected_start_command) --spring.batch.job.enabled=true"
cf run-task hello-batch "$(cf curl /v2/apps/$(cf app hello-batch --guid) | jq -r .entity.detected_start_command) --spring.batch.job.enabled=true"
cf run-task hello-batch "$(cf curl /v2/apps/$(cf app hello-batch --guid) | jq -r .entity.detected_start_command) --spring.batch.job.enabled=true"
ログ中のrun.id
がインクリメントされていることがわかります。これは過去の実行履歴が残っているためです。
Scheduler for PCFを利用してTaskを定期実行する
Taskをcf run-task
コマンドではなく、定期実行するためのサービスとしてScheduler for PCFがあります。
このサービスはPivotal Web ServicesまたはPivotal Cloud Foundryでのみ利用可能です。
次のコマンドでscheduler-for-pcf
サービスが利用可能か確認できます。
$ cf marketplace | grep scheduler
scheduler-for-pcf standard
scheduler-for-pcf
サービスのサービスインスタンスを作成してhello-batch
アプリにバインドします。
cf create-service scheduler-for-pcf standard hello-scheduler
cf bind-service hello-batch hello-scheduler
次にSchedulerの登録を行いますが、これにはcf CLIのプラグインが必要です。
プラグインはこちらからダウンロードできます。 次のコマンドでインストールします。
cf install-plugin ~/Downloads/scheduler-for-pcf-cliplugin-macosx64-binary-1.1.0 -f
プラグインの利用方法はこちらを確認してください。
まずはSchedulerに登録するJob(Spring BatchのJobではないです)を作成します。cf create-job <app-name> <job-name> <command>
形式です。
cf create-job hello-batch hello-job "$(cf curl /v2/apps/$(cf app hello-batch --guid) | jq -r .entity.detected_start_command) --spring.batch.job.enabled=true"
作成済みのJob一覧はcf jobs
で確認できます。
cf jobs
まずはJobを即時実行してみます。
cf run-job hello-job
cf logs
でログを確認できます。Taskの実行と同じですが、TaskのIDの部分にSchedulerのIDも含まれています。
今度はこのJobを定期実行します。Cron形式で次のようにスケジューリングできます。
cf schedule-job hello-job "*/5 * ? * *"
スケジュール化されているJob一覧はcf job-schedules
で確認できます。
cf job-schedules
Apps Manager上でも確認可能です。
Jobの実行履歴はcf job-history
でも確認できます。
cf job-history hello-job
Spring BatchをCloud Foundryで実行する際のいくつかのTipsを紹介しました。 Pivotal Web ServicesではTaskの実行時間のみ課金対象なので、安くをBatchを運用できるのではないでしょうか。