概要
KubernetesのAPIを呼び出すJavaのラッパーライブラリが実は公式で用意されており、Javaアプリケーションから比較的簡単にKubernetesの操作を行うことができる。
今回はJobを起動するREST APIをSpring Bootで作成してみる。
環境
- Java 8
- Spring Boot 1.5.10.RELEASE
- Kubernetes 1.8
今回のアプリの仕様
- 公式ドキュメントにあるpiを算出するJobサンプルを実行する
- Jobの定義はYAMLファイルから読み込む
- client-javaはJSON(GSON)を想定しているので、YAML->JSON変換してバインドする
- Job名は一意になるように日付を付加する
- 認証はサービスアカウントのkubernetes configを使う
実装
プロジェクトの作成
curl -s https://start.spring.io/starter.tgz \
-d baseDir=k8s-job-sample \
-d type=gradle-project \
-d javaVersion=1.8 \
-d dependencies=web,lombok | tar -xzvf -
build.gradleに依存を追加
compile('io.kubernetes:client-java:1.0.0-beta2')
Job manifestを設置
src/main/resources/job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(100)"]
restartPolicy: Never
backoffLimit: 4
Bean定義
DemoApplication.java
package com.example.demo;
import com.google.gson.Gson;
import io.kubernetes.client.ApiClient;
import io.kubernetes.client.apis.BatchV1Api;
import io.kubernetes.client.util.Config;
import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public Gson gson() {
return new Gson();
}
@Bean
public Yaml yaml() {
return new Yaml(new SafeConstructor());
}
@Bean
public ApiClient apiClient() throws IOException {
return Config.fromConfig("/path/to/k8s-config.yaml");
}
@Bean
public BatchV1Api batchV1Api(ApiClient apiClient) {
return new BatchV1Api(apiClient);
}
}
※簡易化のため、Applicationクラスに記述
Controller
JobController.java
package com.example.demo;
import com.google.gson.Gson;
import io.kubernetes.client.ApiException;
import io.kubernetes.client.apis.BatchV1Api;
import io.kubernetes.client.models.V1Job;
import io.kubernetes.client.models.V1ObjectMeta;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.yaml.snakeyaml.Yaml;
@RestController
@RequestMapping("jobs")
@RequiredArgsConstructor
public class JobController {
private final BatchV1Api batchApi;
private final ResourceLoader resourceLoader;
private final Gson gson;
private final Yaml yaml;
private DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
@PostMapping
public V1Job executeJob(@RequestParam(required = false, defaultValue = "default") String nameSpace)
throws IOException, ApiException {
V1Job job = loadJobFromYaml("job.yaml");
V1ObjectMeta metaData = job.getMetadata();
metaData.setName(String.format("%s-%s", metaData.getName(), dateTimeFormatter.format(LocalDateTime.now())));
return batchApi.createNamespacedJob(nameSpace, job, null);
}
private V1Job loadJobFromYaml(String fileName) throws IOException {
Map jobManifest = (Map) yaml.load(
resourceLoader.getResource(ResourceLoader.CLASSPATH_URL_PREFIX + fileName).getInputStream());
return gson.fromJson(gson.toJson(jobManifest), V1Job.class);
}
}
動作確認
$ curl -X POST http://localhost:8080/jobs?nameSpace=develop