Apache Camelによるマイクロサービスの開発方法

# 概要

- [Camelマイクロサービスプロジェクトの作成](#マイクロサービスプロジェクトの作成)
- [Dockerイメージのビルド](#dockerイメージのビルド)
- [Kubernetesへのデプロイと実行](#kubernetesへのデプロイと実行)
- [マイクロサービスのレジリエンス](#マイクロサービスのレジリエンス)
- [Kubernetes上でのテスト](#kubernetes上でのテスト)
- [その他のツールセット（Fabric8、Helm、Camel K）](#その他のツールセット)

----

# マイクロサービスプロジェクトの作成

----

## Camelアプリケーション例

- 5秒おきに`Hello from <hostname>`を出力するだけのルート

```java:HelloRoute.java
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.util.InetAddressUtil;

public class HelloRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        from("timer:hello?period={{timer.period}}")
            .setBody().constant("Hello from " + InetAddressUtil.getLocalHostName())
            .log("${body}");
    }
}
```

```java:application.properties
timer.period=5000
```

----

## Spring Boot

https://start.spring.io/

- Apache Camelのdependencyを追加してプロジェクト生成

<img width="80%" alt="start-spring-io.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30502/2d778d2e-bdbc-2849-deb0-e3651456b47e.png" />

----

### サンプルプロジェクト

https://github.com/tadayosi/camel-springboot-hello

```
camel-springboot-hello/
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   └── io/github/tadayosi/sample/camel/springboot/hello/
    │   │       ├── HelloApplication.java
    │   │       └── HelloRoute.java
    │   └── resources/
    │       └── application.properties
    └── test
        └── java
            └── io/github/tadayosi/sample/camel/springboot/hello/
                └── HelloApplicationTests.java
```

----

### Spring Bootを使う場合の注意点

- HTTPエンドポイント（`spring-boot-starter-web`など）がない場合、デフォルトで起動後すぐにJVMがシャットダウンしてしまう
- CamelContextを実行し続けるには以下を`true`に設定する

```java:application.properties
camel.springboot.main-run-controller=true
```

----

### ローカルで実行

```console:コマンド
$ mvn spring-boot:run
```

```console:実行結果
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.2)

2021-02-15 16:28:04.233  INFO 19962 --- [           main] i.g.t.s.c.s.hello.HelloApplication       : Starting HelloApplication using Java 11.0.10 on racine with PID 19962 (/home/tasato/projects/samples/camel-springboot-hello/target/classes started by tasato in /home/tasato/projects/samples/camel-springboot-hello)
2021-02-15 16:28:04.235  INFO 19962 --- [           main] i.g.t.s.c.s.hello.HelloApplication       : No active profile set, falling back to default profiles: default
2021-02-15 16:28:04.988  INFO 19962 --- [           main] o.apache.camel.support.LRUCacheFactory   : Detected and using LRUCacheFactory: camel-caffeine-lrucache
2021-02-15 16:28:05.309  INFO 19962 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML routes from: classpath:camel/*.xml
2021-02-15 16:28:05.310  INFO 19962 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML route templates from: classpath:camel-template/*.xml
2021-02-15 16:28:05.310  INFO 19962 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML rests from: classpath:camel-rest/*.xml
2021-02-15 16:28:05.418  INFO 19962 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.7.2 (camel-1) is starting
2021-02-15 16:28:05.419  INFO 19962 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : StreamCaching is not in use. If using streams then it's recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2021-02-15 16:28:05.420  INFO 19962 --- [           main] c.s.b.CamelSpringBootApplicationListener : Starting CamelMainRunController to ensure the main thread keeps running
2021-02-15 16:28:05.422  INFO 19962 --- [           main] o.a.c.i.e.InternalRouteStartupManager    : Route: route1 started and consuming from: timer://hello
2021-02-15 16:28:05.426  INFO 19962 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Total 1 routes, of which 1 are started
2021-02-15 16:28:05.426  INFO 19962 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.7.2 (camel-1) started in 8ms
2021-02-15 16:28:05.435  INFO 19962 --- [           main] i.g.t.s.c.s.hello.HelloApplication       : Started HelloApplication in 1.761 seconds (JVM running for 2.168)
2021-02-15 16:28:06.431  INFO 19962 --- [- timer://hello] route1                                   : Hello from racine
2021-02-15 16:28:11.423  INFO 19962 --- [- timer://hello] route1                                   : Hello from racine
2021-02-15 16:28:16.424  INFO 19962 --- [- timer://hello] route1                                   : Hello from racine
```

----

## Quarkus

https://code.quarkus.io/

- 必要な`Camel *`エクステンションを追加してプロジェクト生成

<img width="80%" alt="code-quarkus-io.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30502/d26921e4-4156-96f8-0b2a-db0df1e8d35f.png" />

----

### サンプルプロジェクト

https://github.com/tadayosi/camel-quarkus-hello

```
camel-quarkus-hello/
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.md
└── src/
    ├── main/
    │   ├── docker/
    │   │   ├── Dockerfile.fast-jar
    │   │   ├── Dockerfile.jvm
    │   │   └── Dockerfile.native
    │   ├── java/
    │   │   └── io/github/tadayosi/sample/
    │   │       ├── camel/quarkus/hello/
    │   │       │   └── HelloRoute.java
    │   │       └── GreetingResource.java
    │   └── resources/
    │       ├── application.properties
    │       └── META-INF/resources/
    │           └── index.html
    └── test
        └── java
            └── io/github/tadayosi/sample/
                ├── GreetingResourceTest.java
                └── NativeGreetingResourceIT.java
```

----

### ローカルで実行

```console:コマンド
$ mvn compile quarkus:dev
```

```console:実行結果
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
            Powered by Quarkus 1.11.2.Final
2021-02-15 16:31:49,076 INFO  [org.apa.cam.qua.cor.CamelBootstrapRecorder] (Quarkus Main Thread) bootstrap runtime: org.apache.camel.quarkus.core.CamelContextRuntime
2021-02-15 16:31:49,149 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (Quarkus Main Thread) Apache Camel 3.7.0 (camel-1) is starting
2021-02-15 16:31:49,151 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (Quarkus Main Thread) StreamCaching is not in use. If using streams then it's recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2021-02-15 16:31:49,154 INFO  [org.apa.cam.imp.eng.InternalRouteStartupManager] (Quarkus Main Thread) Route: route1 started and consuming from: timer://hello
2021-02-15 16:31:49,156 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (Quarkus Main Thread) Total 1 routes, of which 1 are started
2021-02-15 16:31:49,156 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (Quarkus Main Thread) Apache Camel 3.7.0 (camel-1) started in 6ms
2021-02-15 16:31:49,251 INFO  [io.quarkus] (Quarkus Main Thread) camel-quarkus-hello 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.11.2.Final) started in 3.927s. Listening on: http://localhost:8080
2021-02-15 16:31:49,251 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-02-15 16:31:49,252 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [camel-core, camel-support-common, camel-timer, cdi, kubernetes, resteasy]
2021-02-15 16:31:50,162 INFO  [route1] (Camel (camel-1) thread #0 - timer://hello) Hello from racine
2021-02-15 16:31:55,157 INFO  [route1] (Camel (camel-1) thread #0 - timer://hello) Hello from racine
2021-02-15 16:32:00,156 INFO  [route1] (Camel (camel-1) thread #0 - timer://hello) Hello from racine
```

----

# Dockerイメージのビルド

----

## JavaプロジェクトのDockerイメージ作成方法

- Mavenプラグインを使う
    - [docker-maven-plugin](https://dmp.fabric8.io/)
        - 使えるけれど少し古い
    - [Eclipse JKube](https://www.eclipse.org/jkube/)
        - よりモダン、k8sと統合
- フレームワーク固有
    - Spring Boot: [Spring Bootプラグイン](https://spring.io/guides/topicals/spring-boot-docker/)
    - Quarkus: [Container Imageエクステンション](https://quarkus.io/guides/container-image)

----

## docker-maven-plugin

- Fabric8プロジェクトの長らくデファクトだったDocker Mavenプラグイン
- Docker単体で使うなら今でも現役だが、今ならJKubeかフレームワーク固有の機能を使った方がよい

### 使い方

- `pom.xml`にMavenプラグイン設定を追加する

```xml:pom.xml
      <plugin>
        <groupId>io.fabric8</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.34.1</version>
        <configuration>
          <images>
            <image>
              <name>tadayosi/${project.artifactId}:${project.version}</name>
              <build>
                <from>adoptopenjdk/openjdk11:ubi-minimal</from>
                <assembly>
                  <descriptorRef>artifact</descriptorRef>
                </assembly>
                <cmd>java -jar maven/${project.artifactId}-${project.version}.jar</cmd>
              </build>
            </image>
          </images>
        </configuration>
      </plugin>
```

### ビルド方法

```console:コマンド
$ mvn package docker:build
```

```console:確認
$ docker images
REPOSITORY                                                                TAG                 IMAGE ID       CREATED         SIZE
tadayosi/camel-springboot-hello                                           0.0.1-SNAPSHOT      457115bcda7e   3 minutes ago   471MB
...
```

----

## Eclipse JKube

- Fabric8 Mavenプラグインの後継
- Dockerイメージ作成からKubernetes、OpenShiftへのデプロイまでカバーされる

### 使い方

- `pom.xml`にMavenプラグイン設定を追加する

```xml:pom.xml
      <plugin>
        <groupId>org.eclipse.jkube</groupId>
        <artifactId>kubernetes-maven-plugin</artifactId>
        <version>1.1.0</version>
      </plugin>
      <!-- OpenShiftを使いたい場合は以下も入れる -->
      <plugin>
        <groupId>org.eclipse.jkube</groupId>
        <artifactId>openshift-maven-plugin</artifactId>
        <version>1.1.0</version>
      </plugin>
```

- Dockerイメージ名を設定したい場合は`jkube.generator.name`プロパティを使う

```xml:pom.xml
  <properties>
    <jkube.generator.name>tadayosi/%a:%v</jkube.generator.name>
  </properties>
```

### ビルド方法

```console
$ mvn k8s:build
```

- ビルドしたイメージをDocker Hubなどのコンテナレジストリーにプッシュする場合

```console
$ mvn k8s:push
```

#### OpenShiftの場合

- `oc:build`を使うとローカルでなくOpenShift上に直接ImageStreamがビルドされる

```console
$ mvn oc:build
```

----

## Spring Bootプラグイン

- Spring Boot 2.3.0から追加のMavenプラグインなしでDockerイメージをビルドできる  
  https://spring.io/guides/topicals/spring-boot-docker/

```console
$ mvn spring-boot:build-image
```

----

## Quarkus Container Imageエクステンション

- `container-image-docker`エクステンションを使う  
  https://quarkus.io/guides/container-image
- プロジェクト生成時に選ぶか、以下のコマンドで後から追加する

```console
$ mvn quarkus:add-extension -Dextensions="container-image-docker"
```

- Dockerイメージ名やコンテナレジストリーをカスタマイズしたい場合は`application.properties`に書く

```java:application.properties
quarkus.container-image.group=...
quarkus.container-image.name=...
quarkus.container-image.tag=...
quarkus.container-image.registry=docker.io
```

### ビルド方法

```console
$ mvn package -Dquarkus.container-image.build=true
```

- コンテナレジストリーにプッシュする場合

```console
$ mvn package -Dquarkus.container-image.push=true
```

----

## Dockerコンテナの実行

```console:camel-springboot-hello
$ docker run -it tadayosi/camel-springboot-hello:0.0.1-SNAPSHOT
```

```console:camel-quarkus-hello
$ docker run -it tadayosi/camel-quarkus-hello:1.0.0-SNAPSHOT
```

----

# Kubernetesへのデプロイと実行

----

## JavaプロジェクトのKubernetesへのデプロイ方法

- JKube - [kubernetes-maven-plugin](https://www.eclipse.org/jkube/docs/kubernetes-maven-plugin/)
    - 主にSpring Boot
    - [fabric8-maven-plugin](https://maven.fabric8.io/)はdeprecated
- Quarkus
    - [Kubernetesエクステンション](https://quarkus.io/guides/deploying-to-kubernetes)

----

## コンテナイメージを直接実行する

- あくまでお試し用の方法
    - `Pod`しか作られない
- 事前にイメージを`docker.io`にプッシュしておく

```console
$ docker push tadayosi/camel-springboot-hello:0.0.1-SNAPSHOT
```

- イメージの実行

```console
$ kubectl run camel-springboot-hello --labels="app=camel" \
    --image=tadayosi/camel-springboot-hello:0.0.1-SNAPSHOT
```

- 削除

```console
$ kubectl delete pods -l app=camel
```

----

## JKube kubernetes-maven-plugin

- Dockerビルドのところでも使ったMavenプラグイン
    - `target/classes/META-INF/jkube/`以下にマニフェストファイル（`*.yml`）を生成
    - `Deployment`や`Service`も作ってくれる
- インストール方法はそちらを参照

### 使い方

```console:デプロイ
$ mvn k8s:resource k8s:deploy
```

```console:アンデプロイ
$ mvn k8s:undeploy
```

- [stern](https://github.com/stern/stern)を使って実行結果を確認

```console
$ stern camel-springboot-hello -o raw
+ camel-springboot-hello-6fdc989599-wzlff › spring-boot
Starting the Java application using /opt/jboss/container/java/run/run-java.sh ...
INFO exec  java -javaagent:/usr/share/java/jolokia-jvm-agent/jolokia-jvm.jar=config=/opt/jboss/container/jolokia/etc/jolokia.properties -javaagent:/usr/share/java/prometheus-jmx-exporter/jmx_prometheus_javaagent.jar=9779:/opt/jboss/container/prometheus/etc/jmx-exporter-config.yaml -XX:+UseParallelOldGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:MaxMetaspaceSize=100m -XX:+ExitOnOutOfMemoryError -cp "." -jar /deployments/camel-springboot-hello-0.0.1-SNAPSHOT.jar  
[...]
I> No access restrictor found, access to any MBean is allowed
Jolokia: Agent started with URL https://10.131.0.28:8778/jolokia/

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.2)

2021-02-15 12:01:50.337  INFO 1 --- [           main] i.g.t.s.c.s.hello.HelloApplication       : Starting HelloApplication v0.0.1-SNAPSHOT using Java 11.0.9.1 on camel-springboot-hello-6fdc989599-wzlff with PID 1 (/deployments/camel-springboot-hello-0.0.1-SNAPSHOT.jar started by jboss in /deployments)
2021-02-15 12:01:50.340  INFO 1 --- [           main] i.g.t.s.c.s.hello.HelloApplication       : No active profile set, falling back to default profiles: default
2021-02-15 12:01:51.396  INFO 1 --- [           main] o.apache.camel.support.LRUCacheFactory   : Detected and using LRUCacheFactory: camel-caffeine-lrucache
2021-02-15 12:01:51.715  INFO 1 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML routes from: classpath:camel/*.xml
2021-02-15 12:01:51.715  INFO 1 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML route templates from: classpath:camel-template/*.xml
2021-02-15 12:01:51.716  INFO 1 --- [           main] o.a.c.s.boot.SpringBootRoutesCollector   : Loading additional Camel XML rests from: classpath:camel-rest/*.xml
2021-02-15 12:01:51.850  INFO 1 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.7.2 (camel-1) is starting
2021-02-15 12:01:51.852  INFO 1 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : StreamCaching is not in use. If using streams then it's recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2021-02-15 12:01:51.855  INFO 1 --- [           main] c.s.b.CamelSpringBootApplicationListener : Starting CamelMainRunController to ensure the main thread keeps running
2021-02-15 12:01:51.856  INFO 1 --- [           main] o.a.c.i.e.InternalRouteStartupManager    : Route: route1 started and consuming from: timer://hello
2021-02-15 12:01:51.861  INFO 1 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Total 1 routes, of which 1 are started
2021-02-15 12:01:51.861  INFO 1 --- [           main] o.a.c.impl.engine.AbstractCamelContext   : Apache Camel 3.7.2 (camel-1) started in 10ms
2021-02-15 12:01:51.869  INFO 1 --- [           main] i.g.t.s.c.s.hello.HelloApplication       : Started HelloApplication in 2.014 seconds (JVM running for 2.836)
2021-02-15 12:01:52.867  INFO 1 --- [- timer://hello] route1                                   : Hello from camel-springboot-hello-6fdc989599-wzlff
2021-02-15 12:01:57.857  INFO 1 --- [- timer://hello] route1                                   : Hello from camel-springboot-hello-6fdc989599-wzlff
2021-02-15 12:02:02.858  INFO 1 --- [- timer://hello] route1                                   : Hello from camel-springboot-hello-6fdc989599-wzlff
...
```

----

## Quarkus Kubernetesエクステンション

https://quarkus.io/guides/deploying-to-kubernetes

- 最初にインストールしていなければ追加
    - `quarkus-container-image-*`エクステンションが既に1つ追加されている必要あり

```console
$ mvn quarkus:add-extension -Dextensions="kubernetes"
```

### 使い方

```console
$ mvn package -Dquarkus.kubernetes.deploy=true \
              -Dquarkus.kubernetes-client.trust-certs=true
```

- `quarkus.kubernetes-client.trust-certs=true`は本当は良くないがこれがないと動かなかったので…
    - 後で正しいやり方を調査

### ラベルを付ける

- `application.properties`に`quarkus.kubernetes.labels.*`を定義する

```java:application.properties
quarkus.kubernetes.labels.app=camel
```

----

## マイクロサービス間の呼び出し

- KubernetesのDNS
    - 推奨の方法
- 以下はDNS以前の古い方法（オススメしない）
    - Propertiesコンポーネントの`{{service:xxxxx}}`表記
    - Service Call EIP

----

### KubernetesのDNS

- `another-camel-service`サービスが定義されていたらそのままDNSでルックアップできる
    - Kubernetes 1.9以前はサポートされていなかった

```java
from("timer:hello?period=5000")
    .to("http://another-camel-service:8080")
    .log("${body}");
```

----

### {{service:xxxxx}}

- [Propertiesコンポーネント](https://camel.apache.org/components/3.7.x/properties-component.html)のビルトイン関数
- 環境変数に定義された以下の`xxx-yyy-zzz`サービスを自動的に参照できる
    - `XXX_YYY_ZZZ_SERVICE_HOST`
    - `XXX_YYY_ZZZ_SERVICE_PORT`

```java
from("timer:hello?period=5000")
    .to("http://{{service:another-camel-service}}")
    .log("${body}");
```

#### 参考

- k8sにデプロイしたPodには以下のような環境変数が提供されている

```console
$ kubectl exec camel-quarkus-hello-867767dbf8-62h48 -- env | grep _SERVICE_ | sort -n
CAMEL_QUARKUS_HELLO_SERVICE_HOST=172.30.110.100
CAMEL_QUARKUS_HELLO_SERVICE_PORT=8080
CAMEL_QUARKUS_HELLO_SERVICE_PORT_HTTP=8080
CAMEL_SPRINGBOOT_HELLO_SERVICE_HOST=172.30.200.231
CAMEL_SPRINGBOOT_HELLO_SERVICE_PORT=8080
CAMEL_SPRINGBOOT_HELLO_SERVICE_PORT_HTTP=8080
KUBERNETES_SERVICE_HOST=172.30.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
```

----

### Service Call EIP

https://camel.apache.org/components/3.7.x/eips/serviceCall-eip.html

- Camelのサービスレジストリーに登録された外部サービスを呼び出すためのEIP
    - Kubernetes, Consul, Etcd, Zookeeper, DNS

```java
from("timer:hello?period=5000")
    .serviceCall("another-camel-service")
    .log("${body}");
```

- Kubernetesのサービスを呼び出すには以下の依存が必要

Spring Boot

```xml:pom.xml
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-kubernetes-starter</artifactId>
    </dependency>
```

Quarkus

```console
$ mvn quarkus:add-extension \
      -Dextensions="org.apache.camel.quarkus:camel-quarkus-kubernetes"
```

----

## マイクロサービスのリモートデバッグ

### JKube

- デフォルト`5005`ポートでリモートデバッグできる

```console
$ mvn k8s:debug
```

- ポートを変えたい場合

```console
$ mvn k8s:debug -Djkube.debug.port=12345
```

### Quarkus

https://quarkus.io/guides/maven-tooling#remote-development-mode

- `quarkus:remote-dev`を使えばできそう
    - 要調査

```console
$ mvn quarkus:remote-dev ...
```

----

# マイクロサービスのレジリエンス

----

## ReplicaSet

- K8Sに元々備わっている冗長化の機能
- Podが落ちても自動で復旧してくれる

```console
$ kubectl scale deployment camel-springboot-hello --replicas=2
```

```yaml:Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: camel
    group: io.github.tadayosi.sample
    provider: jkube
  name: camel-springboot-hello
  ...
spec:
  replicas: 2
  selector:
    matchLabels:
      app: camel
      group: io.github.tadayosi.sample
      provider: jkube
  ...
```

----

### JKube

- リソースフラグメントを使ってカスタマイズできる  
  https://www.eclipse.org/jkube/docs/kubernetes-maven-plugin#_resource_fragments
    - `src/main/jkube`以下にリソース毎の断片を設定できる
        - `deployment.yml`
        - `service.yml`
        - `configmap.yml`
        - `sa.yml`
        - ... etc.

```yaml:src/main/jkube/deployment.yml
spec:
  replicas: 2
```

----

### Quarkusエクステンション

- なんでも`application.properties`で設定する  
  https://quarkus.io/guides/deploying-to-kubernetes#tuning-the-generated-resources-using-application-properties

```java:src/main/resources/application.properties
quarkus.kubernetes.replicas=2
```

----

## Readiness/Liveness Probe

- Podのヘルスチェック
    - サービス可能（Ready）になったか
    - 生存しているか
    - ReplicaSetだけではPodの中身の死活監視まではできない
- Readiness/Liveness Probeの方法
    1. コマンド実行
    2. HTTPリクエスト
    3. TCPソケット
- SB、QuarkusアプリではHTTPを使うのが一般的

```yaml:Deployment
spec:
  template:
    spec:
      containers:
        - livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /actuator/health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 180
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /actuator/health
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
```

----

### JKube

- POMに`spring-boot-starter-actuator`があれば自動でReadiness/Liveness Probeを設定してくれる

```xml:pom.xml
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
```

```java:application.properties
management.endpoints.web.exposure.include=health
```

#### ヘルスチェック

```console
$ kubectl port-forward camel-springboot-hello-ff7d6fc84-djw4l 8080:8080 9779:9779 &
$ curl -s localhost:8080/actuator/health | jq 
{
  "status": "UP",
  "groups": [
    "liveness",
    "readiness"
  ]
}
```

#### Prometheus

- Prometheusもデフォルトでアタッチしてくれている（ポート：`9779`）

```console
$ curl localhost:9779/metrics
# HELP org_xnio_xnio_coreworkerpoolsize CoreWorkerPoolSize (org.xnio<type=Xnio, provider="nio", worker="XNIO-1"><>CoreWorkerPoolSize)
# TYPE org_xnio_xnio_coreworkerpoolsize untyped
org_xnio_xnio_coreworkerpoolsize{provider="\"nio\"",worker="\"XNIO-1\"",} 16.0
# HELP org_xnio_xnio_maxworkerpoolsize MaxWorkerPoolSize (org.xnio<type=Xnio, provider="nio", worker="XNIO-1"><>MaxWorkerPoolSize)
# TYPE org_xnio_xnio_maxworkerpoolsize untyped
org_xnio_xnio_maxworkerpoolsize{provider="\"nio\"",worker="\"XNIO-1\"",} 16.0
...
```

----

### Quarkusエクステンション

- [MicroProfile Health](https://download.eclipse.org/microprofile/microprofile-health-2.1/microprofile-health-spec.html)を使う
- `smallrye-health`エクステンションを追加するだけで自動的にReadiness/Liveness Probeを設定してくれる  
  https://quarkus.io/guides/microprofile-health

```console
$ mvn quarkus:add-extension -Dextensions="smallrye-health"
```

```yaml:Deployment
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /q/health/live
            port: 8080
            scheme: HTTP
          periodSeconds: 30
          successThreshold: 1
          timeoutSeconds: 10
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /q/health/ready
            port: 8080
            scheme: HTTP
          periodSeconds: 30
          successThreshold: 1
          timeoutSeconds: 10
```

#### ヘルスチェック

```console
$ kubectl port-forward camel-quarkus-hello-77f8464467-d9t8t 8080:8080 &
$ curl -s localhost:8080/q/health/live | jq
{
  "status": "UP",
  "checks": []
}
```

----

## Circuit Breaker EIP

- Circuit BreakerパターンをCamelルートの中で使える  
  https://camel.apache.org/components/3.7.x/eips/circuitBreaker-eip.html
    - Camel 2.xでは`.hystrix()`だったが`.circuitBreaker()`に名前変更された

```java
from("timer:hello?period={{timer.period}}")
    .circuitBreaker()
        .to("http://another-camel-service:8080")
    .onFallback()
        .transform().constant("Remote service is down!")
    .end()
    .log("${body}");
```

----

### 使い方

#### Spring Boot

- POMに`camel-resilience4j-starter`を追加
    - Hystrixはdeprecatedなので使わない

```xml:pom.xml
    <dependency>
      <groupId>org.apache.camel.springboot</groupId>
      <artifactId>camel-resilience4j-starter</artifactId>
    </dependency>
```

#### Quarkus

- `camel-quarkus-microprofile-fault-tolerance`エクステンションを追加

```console
$ mvn quarkus:add-extension -Dextensions="camel-quarkus-microprofile-fault-tolerance"
```

----

# Kubernetes上でのテスト

----

## Fabric8 Kubernetes Client

https://github.com/fabric8io/kubernetes-client

- [Arquillian Cube](https://github.com/arquillian/arquillian-cube)は2018年以降メンテされていない
    - JUnit 5（Jupiter）非対応
- JavaでKubernetes上の統合テストをするなら[Fabric8 Kubernetes Client](https://github.com/fabric8io/kubernetes-client)一択

----

### 使い方

#### Spring Boot

- POMに`kubernetes-client`を追加

```xml:pom.xml
    <dependency>
      <groupId>io.fabric8</groupId>
      <artifactId>kubernetes-client</artifactId>
      <version>5.1.1</version>
      <scope>test</scope>
    </dependency>
```

#### Quarkus

- [kubernetes-client](https://quarkus.io/guides/kubernetes-client)エクステンションを追加
    - `quarkus-kubernetes-client`のスコープは`test`にしておく

```console
$ mvn quarkus:add-extension -Dextensions="kubernetes-client"
```

#### OpenShift

- それぞれ対になる`openshift-client`があるので、そちらに置き換える
    - https://quarkus.io/guides/kubernetes-client#openshift-client

----

### 統合テストの書き方

```java
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class HelloApplicationIT {
    private KubernetesClient client;

    @BeforeEach
    void setUp() {
        client = new DefaultKubernetesClient();
    }

    @Test
    void testPodsReady() {
        PodList podList = client.pods()
                .withLabel("app", "camel")
                .withLabel("group", "io.github.tadayosi.sample")
                .withLabel("provider", "jkube")
                .list();
        assertEquals(2, podList.getItems().size());
        podList.getItems().forEach(pod -> {
            pod.getStatus().getConditions().stream()
                    .filter(c -> "Ready".equals(c.getType()))
                    .forEach(c -> {
                        assertEquals("True", c.getStatus());
                    });
        });
    }
}
```

----

### 統合テストの実行

- `maven-failsafe-plugin`を使う

```xml:pom.xml
  <profiles>
    <profile>
      <id>itests</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>3.0.0-M5</version>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>
```

```console:実行
$ mvn verify -P itests
```

----

## その他の選択肢

- Testcontainers
    - https://www.testcontainers.org/
    - ローカルのDockerコンテナ上で統合テストを行いたい場合

----

# その他のツールセット

----

## Fabric8

- Fabric8 v1 ― まったく別物だった
    - Before K8Sの時代にZooKeeperベースでK8S的なプラットフォームを実現しようとしていた
- Fabric8 v2 ― Java開発者向けK8S/OpenShiftツールセット・ライブラリ
- 現役なのは以下の2つのみ
    - **Kubernetes Client** ― https://github.com/fabric8io/kubernetes-client
    - **Docker Maven Plugin** ― https://github.com/fabric8io/docker-maven-plugin
    - Fabric8 Maven Pluginはdeprecated

----

## Helm

- K8S上のデフォルトのパッケージマネージャ
- JKubeにはHelmサポートあり
    - `mvn k8s:helm`
    - https://www.eclipse.org/jkube/docs/kubernetes-maven-plugin#jkube:helm

----

### JKubeのHelmサポート

```console
$ mvn k8s:helm
```

- `target/jkube/helm/camel-springboot-hello/kubernetes`にHelm chartが生成される

```
target/jkube/helm/camel-springboot-hello/kubernetes/
├── Chart.yaml
├── templates
│   ├── camel-springboot-hello-deployment.yaml
│   └── camel-springboot-hello-service.yaml
└── values.yaml
```

- Helmを使ったアプリのインストール

```console
$ helm install camel-springboot-hello target/jkube/helm/camel-springboot-hello/kubernetes
$ helm list
NAME                  	NAMESPACE	REVISION	UPDATED                                	STATUS  	CHART                                	APP VERSION
camel-springboot-hello	test     	1       	2021-03-01 15:05:24.784538587 +0900 JST	deployed	camel-springboot-hello-0.0.1-SNAPSHOT	           
```

----

## Camel K

https://camel.apache.org/camel-k/

- Operatorベースでさらにクラウドネイティブに突き詰めたサブプロジェクト
    - Camelルートを`Integration`カスタムリソースとしてデプロイできる
    - Java、XML、YAML、Groovy、Kotlin、JSをサポート

**Camel-Kアーキテクチャ** [^camel-k-arch]
![post-camel-k-architecture-detail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30502/d48af32a-8483-3058-0c81-d1c71b1ccfb5.png)

[^camel-k-arch]: <https://www.nicolaferraro.me/2018/10/15/introducing-camel-k/>

----

## 参考文献

- [Camel in Action, 2nd Edition](https://www.manning.com/books/camel-in-action-second-edition)

----

# END