Java Operator SDK プロジェクトのテンプレートを作成
Java Operator SDKを利用した Kubernetes のカスタムコントローラー開発プロジェクトのテンプレートを作成し、プロジェクトを初期化します。シェルスクリプトによるツールを提供します。
ツール本体
init.sh
#!/bin/bash
#=======================================================================
# Java Operator SDKで実装するカスタムコントローラープロジェクトの雛形を作成します。
# usage: bash init.sh {group.id} {CustomResourceKind}
#=======================================================================
# 入力パラメータ
group_id=$1
kind=$2
# 入力値チェック
if [ -z ${group_id} ]; then
echo "Input group ID"
exit 1
fi
if [ -z ${kind} ]; then
echo "Input custom resource kind"
exit 1
fi
controller_name=`echo ${kind} | tr [A-Z] [a-z]`controller
# javaパッケージ作成向け変数作成
OLD_IFS="$IFS"
IFS='.' read -ra group_id_array <<< "$group_id"
IFS="$OLD_IFS"
java_package_path="" # スラッシュ区切りのパッケージパス
java_package_name="" # ドット区切りのパッケージ名
for ((i=${#group_id_array[@]}-1; i>=0; i--)); do
java_package_path=${java_package_path}${group_id_array[i]}
if [ $i -gt 0 ]; then
java_package_path=${java_package_path}"/"
fi
java_package_name=${java_package_name}${group_id_array[i]}
if [ $i -gt 0 ]; then
java_package_name=${java_package_name}"."
fi
done
# ディレクトリ作成
k8s_resource_dir=${controller_name}/k8s
java_class_dir=${controller_name}/src/main/java/${java_package_path}
java_resources_dir=${controller_name}/src/main/resources
java_test_dir=${controller_name}/src/test/java/${java_package_path}
mkdir -p ${k8s_resource_dir}
mkdir -p ${java_class_dir}
mkdir -p ${java_resources_dir}
mkdir -p ${java_test_dir}
# custom_resource.yaml作成
cat << EOF > ${k8s_resource_dir}/custom_resource.yaml
apiVersion: ${group_id}/v1
kind: ${kind}CustomResource
metadata:
name: `echo ${kind} | tr [A-Z] [a-z]`-sample
spec:
# TODO: add your custom resource spec
EOF
# pom.xml作成
cat << EOF > ${controller_name}/pom.xml
<?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>
<groupId>${java_package_name}</groupId>
<artifactId>${controller_name}</artifactId>
<version>0.1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<josdk.version>5.1.2</josdk.version>
<fabric8-client.version>7.3.1</fabric8-client.version>
<lombok.version>1.18.40</lombok.version>
<logback.version>1.5.18</logback.version>
<junit.version>5.13.4</junit.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-bom</artifactId>
<version>\${josdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.javaoperatorsdk/operator-framework -->
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework</artifactId>
<version>\${josdk.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.javaoperatorsdk/operator-framework-junit-5 -->
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit-5</artifactId>
<version>\${josdk.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>\${lombok.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>\${logback.version}</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>\${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>crd-generator-maven-plugin</artifactId>
<version>\${fabric8-client.version}</version>
<executions>
<execution>
<?m2e execute onConfiguration,onIncremental?>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${java_package_name}.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
EOF
# Main.java作成
cat << EOF > ${java_class_dir}/Main.java
package ${java_package_name};
import io.javaoperatorsdk.operator.Operator;
public class Main {
public static void main(String[] args) {
Operator operator = new Operator();
operator.register(new ${kind}Reconciler());
operator.start();
}
}
EOF
# Spec.java作成
cat << EOF > ${java_class_dir}/${kind}Spec.java
package ${java_package_name};
import lombok.Data;
@Data
public class ${kind}Spec {
private String value;
// TODO: add your custom resource spec
}
EOF
# Status.java作成
cat << EOF > ${java_class_dir}/${kind}Status.java
package ${java_package_name};
import lombok.Data;
@Data
public class ${kind}Status {
// TODO: add your custom resource status
}
EOF
# CustomResource作成
cat << EOF > ${java_class_dir}/${kind}CustomResource.java
package ${java_package_name};
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Version;
@Group("${group_id}")
@Version("v1")
public class ${kind}CustomResource extends CustomResource<${kind}Spec, ${kind}Status> implements Namespaced {
}
EOF
# Reconciler作成
cat << EOF > ${java_class_dir}/${kind}Reconciler.java
package ${java_package_name};
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.Workflow;
@Workflow(dependents = { /* TODO: add your dependent resources */ })
public class ${kind}Reconciler implements Reconciler<${kind}CustomResource> {
@Override
public UpdateControl<${kind}CustomResource> reconcile(${kind}CustomResource resource, Context<${kind}CustomResource> context) throws Exception {
// TODO: add your business logic
return UpdateControl.noUpdate();
}
}
EOF
# ReconcilerTest作成
cat << EOF > ${java_test_dir}/${kind}ReconcilerTest.java
package ${java_package_name};
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
public class ${kind}ReconcilerTest {
@RegisterExtension
LocallyRunOperatorExtension extension = LocallyRunOperatorExtension.builder()
.withReconciler(${kind}Reconciler.class)
.build();
@Test
void test() {
${kind}CustomResource cr = extension.create(testResource());
// TODO: add your test logic
extension.delete(cr);
}
${kind}CustomResource testResource() {
${kind}CustomResource resource = new ${kind}CustomResource();
resource.setMetadata(new ObjectMetaBuilder()
.withName("test-resource")
.build());
resource.setSpec(new ${kind}Spec());
resource.getSpec().setValue("test");
// TODO: add your custom resource spec
return resource;
}
}
EOF
# .gitignore作成
cat << EOF > ${controller_name}/.gitignore
#Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties
.flattened-pom.xml
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env
EOF
# Makefile作成
cat << EOF > ${controller_name}/Makefile
# Test the controller application using junit test class
.PHONY: test
test:
mvn clean test
# Create the jar file of the controller application, and apply the crd manifest file
# The crd manifest file will be found in target/classes/META-INF/fabric8
.PHONY: package
package:
mvn clean package
kubectl apply -f target/classes/META-INF/fabric8/\$(ls -1 target/classes/META-INF/fabric8)
# Run the built jar file
.PHONY: run
run:
java -jar target/${controller_name}-0.1.0-SNAPSHOT.jar
EOF
# logback.xmlのサンプル作成
cat << EOF > ${java_resources_dir}/logback.xml.sample
<configuration>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</configuration>
EOF
作成ファイル一覧
上記ツールによって下記ファイルが作成されます。
custom_resource.yaml
カスタムリソースのマニフェストです。spec については、後述のSpec.javaと併せて実装者が定義して追記する必要があります。
pom.xml
Java プロジェクトの必要な依存関係を記述しています。各種ライブラリのバージョンが古い場合などは適宜更新をしてください。
Main.java
main メソッドを持つクラスです。基本的に編集は不要です。
Spec.java
カスタムリソースの spec を定義するクラスです。必要な値を宣言してください。
Status.java
カスタムリソースの status を定義するクラスです。必要な値を宣言してください。
CustomResource.java
カスタムリソース本体を宣言するクラスです。基本的に編集は不要です。
Reconciler.java
main から呼び出されて reconcile 処理を行うクラスです。コントローラーの業務処理を記述してください。
ReconcilerTest.java
Reconciler.javaのテストクラスです。業務処理に併せてテストを記述してください。
.gitignore
Git 管理対象外とするディレクトリを記述しています。基本的に編集は不要です。
Makefile
makeコマンドで実行する開発支援向けのスクリプトを定義しています:
test単体テストを実行します。packagejar ファイルを作成し、自動生成される CRD マニフェストを apply します。run作成した jar ファイルを実行します。
logback.xmlのサンプル
ログ出力向けの設定を行うlogback.xmlのサンプルを出力します。ファイル名から.sampleを外すとログレベルが INFO に上がります(デフォルトは DEBUG)。