---
title: Spring BatchをCloud Foundry上で実行する際のTips
tags: ["Java", "Spring", "Spring Batch", "Spring Boot", "Cloud Foundry", "Pivotal Web Services", "Pivotal Cloud Foundry"]
categories: ["Programming", "Java", "org", "springframework", "batch"]
date: 2019-03-10T18:50:08Z
updated: 2019-12-02T08:21:37Z
---

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

**目次**

<!-- toc -->

### 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`を一つ定義します。

```java
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`を指定しない限りは無視されます。

```properties
cat <<EOF > src/main/resources/application-cloud.properties
spring.batch.job.enabled=false
EOF
```

次の設定は必須ではありませんが、以下の説明(ログのキャプチャ)ではこの設定でログ出力を減らしています。

```properties
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
```

![image](https://user-images.githubusercontent.com/106908/54089779-03e2a180-43b0-11e9-942d-937266807a0c.png)

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

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

次にこのアプリケーションをCloud Foundryにデプロイします。ここでは[Pivotal Web Services](https://run.pivotal.io)を使います。他のCFでも同じです。
基本的には`cf push`でjarをデプロイすれば良いのですが、このアプリはWebアプリではないので、`--health-check-type`に`none`を指定します。
Long Running Processではなく、[Task](https://docs.cloudfoundry.org/devguide/using-tasks.html)として実行したいので`-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が実行されていることが確認できます。

![image](https://user-images.githubusercontent.com/106908/54089325-363dd000-43ab-11e9-9c73-e9e27d9a2b0e.png)


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](https://run.pivotal.io)で利用可能な`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](https://user-images.githubusercontent.com/106908/54089634-591db380-43ae-11e9-9168-a46ab393ce88.png)


`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"
```

![image](https://user-images.githubusercontent.com/106908/54089657-9aae5e80-43ae-11e9-8d59-d75710045ff8.png)

ログ中の`run.id`がインクリメントされていることがわかります。これは過去の実行履歴が残っているためです。

### Scheduler for PCFを利用してTaskを定期実行する

Taskを`cf run-task`コマンドではなく、定期実行するためのサービスとして[Scheduler for PCF](https://docs.pivotal.io/pcf-scheduler)があります。
このサービスは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のプラグインが必要です。

プラグインは[こちら](https://network.pivotal.io/products/p-scheduler)からダウンロードできます。
次のコマンドでインストールします。

```
cf install-plugin ~/Downloads/scheduler-for-pcf-cliplugin-macosx64-binary-1.1.0 -f
```

プラグインの利用方法は[こちら](https://docs.pivotal.io/pcf-scheduler/1-2/using-jobs.html)を確認してください。

まずは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
```

![image](https://user-images.githubusercontent.com/106908/54089285-dcd5a100-43aa-11e9-8020-bc72a2ab9ca0.png)

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

```
cf run-job hello-job
```

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

![image](https://user-images.githubusercontent.com/106908/54089389-d0057d00-43ab-11e9-8e31-9d1c9a501c6c.png)

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

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

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

```
cf job-schedules
```

![image](https://user-images.githubusercontent.com/106908/54089473-8a957f80-43ac-11e9-9d35-32796385afe5.png)

[Apps Manager](https://console.run.pivotal.io)上でも確認可能です。

![image](https://user-images.githubusercontent.com/106908/54089501-cc262a80-43ac-11e9-9104-2279f708e287.png)

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

```
cf job-history hello-job
```

![image](https://user-images.githubusercontent.com/106908/54089606-06dc9280-43ae-11e9-9223-972c16366f63.png)



---

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