📝 BLOG.IK.AM

@making's memo
(🗃 Categories 🏷 Tags)

Spring BatchをCloud Foundry上で実行する際のTips

🗃 {Programming/Java/org/springframework/batch}

🏷 Java 🏷 Spring 🏷 Spring Batch 🏷 Spring Boot 🏷 Cloud Foundry 🏷 Pivotal Web Services 🏷 Pivotal Cloud Foundry

🗓 Updated at 2019-03-19T01:49:30Z by Toshiaki Maki  🗓 Created at 2019-03-10T18:50:08Z by Toshiaki Maki  {✒️️ Edit  ⏰ History}


Spring BatchをCloud Foundry上で使う際のうまく実行するTipsのメモ

目次

Hello WorldなSpring Batchアプリケーションの作成

まずは簡単なBatchアプリケーションを作成します。ジョブのメタデータや実行履歴を保存するデータベースは組み込みのH2を使用します。

curl 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
spring.jmx.enabled=false
EOF

アプリケーションをビルドします。

./mvnw clean package -DskipTests=true

ローカル環境で実行します。

java -jar target/hello-spring-batch-0.0.1-SNAPSHOT.jar

image

まずは普通のSpring Batchアプリケーションを作りました。

Cloud Foundry上でTaskとしてSpring Batchのジョブを実行する

次にこのアプリケーションをCloud Foundryにデプロイします。ここではPivotal Web Servicesを使います。他のCFでも同じです。
基本的にはcf pushでjarをデプロイすれば良いのですが、このアプリはWebアプリではないので、--health-check-typenoneを指定します。
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 /v3/apps/$(cf app hello-batch --guid)/droplets/current | jq -r .process_types.task

実行結果は次のようになります。

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 /v3/apps/$(cf app hello-batch --guid)/droplets/current | jq -r .process_types.task) --spring.batch.job.enabled=true"

cf logs hello-batchでログを確認すると、Jobが実行されていることが確認できます。

image

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が使われていることがわかります。

image

cf run-taskを3回実行します。

cf run-task hello-batch "$(cf curl /v3/apps/$(cf app hello-batch --guid)/droplets/current | jq -r .process_types.task) --spring.batch.job.enabled=true"
cf run-task hello-batch "$(cf curl /v3/apps/$(cf app hello-batch --guid)/droplets/current | jq -r .process_types.task) --spring.batch.job.enabled=true"
cf run-task hello-batch "$(cf curl /v3/apps/$(cf app hello-batch --guid)/droplets/current | jq -r .process_types.task) --spring.batch.job.enabled=true"

image

ログ中の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 /v3/apps/$(cf app hello-batch --guid)/droplets/current | jq -r .process_types.task) --spring.batch.job.enabled=true"

作成済みのJob一覧はcf jobsで確認できます。

cf jobs

image

まずはJobを即時実行してみます。

cf run-job hello-job

cf logsでログを確認できます。Taskの実行と同じですが、TaskのIDの部分にSchedulerのIDも含まれています。

image

今度はこのJobを定期実行します。Cron形式で次のようにスケジューリングできます。

cf schedule-job hello-job "*/5 * ? * *"

スケジュール化されているJob一覧はcf job-schedulesで確認できます。

cf job-schedules

image

Apps Manager上でも確認可能です。

image

Jobの実行履歴はcf job-historyでも確認できます。

cf job-history hello-job

image


Spring BatchをCloud Foundryで実行する際のいくつかのTipsを紹介しました。
Pivotal Web ServicesではTaskの実行時間のみ課金対象なので、安くをBatchを運用できるのではないでしょうか。