The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Using Stork with Kubernetes

This guide explains how to use Stork with Kubernetes for service discovery and load balancing.

If you are new to Stork, please read the Stork Getting Started Guide.

这项技术被认为是preview。

preview(预览) 中,不保证向后兼容和在生态系统中的存在。具体的改进可能需要改变配置或API,并且正在计划变得 稳定 。欢迎在我们的 邮件列表 中提供反馈,或在我们的 GitHub问题列表 中提出问题。

For a full list of possible statuses, check our FAQ entry.

先决条件

要完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • 安装JDK 11以上版本并正确配置了 JAVA_HOME

  • Apache Maven 3.8.1+

  • A working container runtime (Docker or Podman)

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

  • Access to a Kubernetes cluster (Minikube is a viable option)

架构

In this guide, we will work with a few components deployed in a Kubernetes cluster:

  • A simple blue service.

  • A simple red service.

  • The color-service is the Kubernetes service which is the entry point to the Blue and Red instances.

  • A client service using a REST client to call the blue or the red service. Service discovery and selection are delegated to Stork.

Architecture of the application

For the sake of simplicity, everything will be deployed in the same namespace of the Kubernetes cluster.

解决方案

We recommend that you follow the instructions in the next sections and create the applications step by step. However, you can go right to the completed example.

克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git ,或下载一个 存档

The solution is located in the stork-kubernetes-quickstart directory.

Discovery and selection

Before going further, we need to discuss discovery vs. selection.

  • Service discovery is the process of locating service instances. It produces a list of service instances that is potentially empty (if no service matches the request) or contains multiple service instances.

  • Service selection, also called load-balancing, chooses the best instance from the list returned by the discovery process. The result is a single service instance or an exception when no suitable instance can be found.

Stork handles both discovery and selection. However, it does not handle the communication with the service but only provides a service instance. The various integrations in Quarkus extract the location of the service from that service instance.

创建项目

Create a Quarkus project importing the quarkus-rest-client-reactive and quarkus-resteasy-reactive extensions using your favorite approach:

CLI
quarkus create app org.acme:stork-kubernetes-quickstart \
    --extension=quarkus-rest-client-reactive,quarkus-resteasy-reactive \
    --no-code
cd stork-kubernetes-quickstart

创建Grade项目,请添加 --gradle 或者 --gradle-kotlin-dsl 参数。

关于如何安装并使用Quarkus CLI的更多信息,请参考Quarkus CLI指南

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.2.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=stork-kubernetes-quickstart \
    -Dextensions="quarkus-rest-client-reactive,quarkus-resteasy-reactive" \
    -DnoCode
cd stork-kubernetes-quickstart

创建Grade项目,请添加 -DbuildTool=gradle 或者 -DbuildTool=gradle-kotlin-dsl 参数。

In the generated project, also add the following dependencies:

pom.xml
<dependency>
    <groupId>io.smallrye.stork</groupId>
    <artifactId>stork-service-discovery-kubernetes</artifactId>
</dependency>
<dependency>
      <groupId>io.smallrye.stork</groupId>
      <artifactId>stork-load-balancer-random</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-kubernetes</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-kubernetes-client</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-container-image-jib</artifactId>
</dependency>
build.gradle
implementation("io.smallrye.stork:stork-service-discovery-kubernetes")
implementation("io.smallrye.stork:stork-load-balancer-random")
implementation("io.quarkus:quarkus-kubernetes")
implementation("io.quarkus:quarkus-kubernetes-client")
implementation("io.quarkus:quarkus-container-image-jib")

stork-service-discovery-kubernetes provides an implementation of service discovery for Kubernetes. stork-load-balancer-random provides an implementation of random load balancer. quarkus-kubernetes enables the generation of Kubernetes manifests each time we perform a build. The quarkuks-kubernetes-client extension enables the use of the Fabric8 Kubernetes Client in native mode. And quarkus-container-image-jib enables the build of a container image using Jib.

The Blue and Red services

Let’s start with the very beginning: the service we will discover, select and call.

The Red and Blue are two simple REST services serving an endpoint responding Hello from Red! and Hello from Blue! respectively. The code of both applications has been developed following the Getting Started Guide.

As the goal of this guide is to show how to use Stork Kubernetes service discovery, we won’t provide the specifics steps for the Red and Blue services. Their container images are already built and available in a public registry:

Deploy the Blue and Red services in Kubernetes

Now that we have our service container images available in a public registry, we need to deploy them into the Kubernetes cluster.

The following file contains all the Kubernetes resources needed to deploy the Blue and Red services in the cluster and make them accessible:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: development
  name: endpoints-reader
rules:
  - apiGroups: [""] # "" indicates the core API group
    resources: ["endpoints", "pods"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: stork-rb
  namespace: development
subjects:
  - kind: ServiceAccount
    # Reference to upper's `metadata.name`
    name: default
    # Reference to upper's `metadata.namespace`
    namespace: development
roleRef:
  kind: Role
  name: endpoints-reader
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    app.quarkus.io/commit-id: f747f359406bedfb1a39c57392a5b5a9eaefec56
    app.quarkus.io/build-timestamp: 2022-03-31 - 10:36:56 +0000
  labels:
    app.kubernetes.io/name: color-service
    app.kubernetes.io/version: "1.0"
  name: color-service (1)
spec:
  ports:
    - name: http
      port: 80
      targetPort: 8080
  selector:
    app.kubernetes.io/version: "1.0"
    type: color-service
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app.quarkus.io/commit-id: f747f359406bedfb1a39c57392a5b5a9eaefec56
    app.quarkus.io/build-timestamp: 2022-03-31 - 10:36:56 +0000
  labels:
    color: blue
    type: color-service
    app.kubernetes.io/name: blue-service
    app.kubernetes.io/version: "1.0"
  name: blue-service (2)
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: blue-service
      app.kubernetes.io/version: "1.0"
  template:
    metadata:
      annotations:
        app.quarkus.io/commit-id: f747f359406bedfb1a39c57392a5b5a9eaefec56
        app.quarkus.io/build-timestamp: 2022-03-31 - 10:36:56 +0000
      labels:
        color: blue
        type: color-service
        app.kubernetes.io/name: blue-service
        app.kubernetes.io/version: "1.0"
    spec:
      containers:
        - env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          image: quay.io/quarkus/blue-service:1.0
          imagePullPolicy: Always
          name: blue-service
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app.quarkus.io/commit-id: 27be03414510f776ca70d70d859b33e134570443
    app.quarkus.io/build-timestamp: 2022-03-31 - 10:38:54 +0000
  labels:
    color: red
    type: color-service
    app.kubernetes.io/version: "1.0"
    app.kubernetes.io/name: red-service
  name: red-service (2)
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/version: "1.0"
      app.kubernetes.io/name: red-service
  template:
    metadata:
      annotations:
        app.quarkus.io/commit-id: 27be03414510f776ca70d70d859b33e134570443
        app.quarkus.io/build-timestamp: 2022-03-31 - 10:38:54 +0000
      labels:
        color: red
        type: color-service
        app.kubernetes.io/version: "1.0"
        app.kubernetes.io/name: red-service
    spec:
      containers:
        - env:
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          image: quay.io/quarkus/red-service:1.0
          imagePullPolicy: Always
          name: red-service
          ports:
            - containerPort: 8080
              name: http
              protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress (3)
metadata:
  annotations:
    app.quarkus.io/commit-id: f747f359406bedfb1a39c57392a5b5a9eaefec56
    app.quarkus.io/build-timestamp: 2022-03-31 - 10:46:19 +0000
  labels:
    app.kubernetes.io/name: color-service
    app.kubernetes.io/version: "1.0"
    color: blue
    type: color-service
  name: color-service
spec:
  rules:
    - host: color-service.127.0.0.1.nip.io
      http:
        paths:
          - backend:
              service:
                name: color-service
                port:
                  name: http
            path: /
            pathType: Prefix

There are a few interesting parts in this listing:

1 The Kubernetes Service resource, color-service, that Stork will discover.
2 The Red and Blue service instances behind the color-service Kubernetes service.
3 A Kubernetes Ingress resource making the color-service accessible from the outside of the cluster at the color-service.127.0.0.1.nip.io url. Not that the Ingress is not needed for Stork however, it helps to check that the architecture is in place.

Create a file named kubernetes-setup.yml with the content above at the root of the project and run the following commands to deploy all the resources in the Kubernetes cluster. Don’t forget to create a dedicated namespace:

kubectl create namespace development
kubectl apply -f kubernetes-setup.yml -n=development

If everything went well the Color service is accessible on http://color-service.127.0.0.1.nip.io. You should have Hello from Red! and Hello from Blue! response randomly.

Stork is not limited to Kubernetes and integrates with other service discovery mechanisms.

The REST Client interface and the front end API

So far, we didn’t use Stork; we just deployed the services we will be discovering, selecting, and calling.

We will call the services using the Reactive REST Client. Create the src/main/java/org/acme/MyService.java file with the following content:

package org.acme;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * The REST Client interface.
 *
 * Notice the `baseUri`. It uses `stork://` as URL scheme indicating that the called service uses Stork to locate and
 * select the service instance. The `my-service` part is the service name. This is used to configure Stork discovery
 * and selection in the `application.properties` file.
 */
@RegisterRestClient(baseUri = "stork://my-service")
public interface MyService {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    String get();
}

It’s a straightforward REST client interface containing a single method. However, note the baseUri attribute: * the stork:// suffix instructs the REST client to delegate the discovery and selection of the service instances to Stork, * the my-service part of the URI is the service name we will be using in the application configuration.

It does not change how the REST client is used. Create the src/main/java/org/acme/FrontendApi.java file with the following content:

package org.acme;

import org.eclipse.microprofile.rest.client.inject.RestClient;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * A frontend API using our REST Client (which uses Stork to locate and select the service instance on each call).
 */
@Path("/api")
public class FrontendApi {

    @RestClient MyService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String invoke() {
        return service.get();
    }

}

It injects and uses the REST client as usual.

Stork configuration

Now we need to configure Stork for using Kubernetes to discover the red and blue instances of the service.

In the src/main/resources/application.properties, add:

quarkus.stork.my-service.service-discovery.type=kubernetes
quarkus.stork.my-service.service-discovery.k8s-namespace=development
quarkus.stork.my-service.service-discovery.application=color-service
quarkus.stork.my-service.load-balancer.type=random

stork.my-service.service-discovery indicates which type of service discovery we will be using to locate the my-service service. In our case, it’s kubernetes. If your access to the Kubernetes cluster is configured via Kube config file, you don’t need to configure the access to it. Otherwise, set the proper Kubernetes url using the quarkus.stork.my-service.service-discovery.k8s-host property. quarkus.stork.my-service.service-discovery.application contains the name of the Kubernetes service Stork is going to ask for. In our case, this is the color-service corresponding to the kubernetes service backed by the Red and Blue instances. Finally, quarkus.stork.my-service.load-balancer.type configures the service selection. In our case, we use a random Load Balancer.

Deploy the REST Client interface and the front end API in the Kubernetes cluster

The system is almost complete. We only need to deploy the REST Client interface and the client service to the cluster. In the src/main/resources/application.properties, add:

quarkus.container-image.registry=<public registry>
quarkus.kubernetes-client.trust-certs=true
quarkus.kubernetes.ingress.expose=true
quarkus.kubernetes.ingress.host=my-service.127.0.0.1.nip.io

The quarkus.container-image.registry contains the container registry to use. The quarkus.kubernetes.ingress.expose indicates that the service will be accessible from the outside of the cluster. The quarkus.kubernetes.ingress.host contains the url to access the service. We are using nip.io wildcard for IP address mappings.

For a more customized configuration you can check the Deploying to Kubernetes guide

Build and push the container image

Thanks to the extensions we are using, we can perform the build of a container image using Jib and also enabling the generation of Kubernetes manifests while building the application. For example, the following command will generate a Kubernetes manifest in the target/kubernetes/ directory and also build and push a container image for the project:

./mvnw package -Dquarkus.container-image.build=true -Dquarkus.container-image.push=true

Deploy client service to the Kubernetes cluster

The generated manifest can be applied to the cluster from the project root using kubectl:

kubectl apply -f target/kubernetes/kubernetes.yml -n=development

Please note that if you use Elliptic Curve keys with Stork and are getting exceptions like java.lang.ClassNotFoundException: org.bouncycastle.jce.provider.BouncyCastleProvider, then adding a BouncyCastle PKIX dependency (org.bouncycastle:bcpkix-jdk15on) is required.

Note that internally an org.bouncycastle.jce.provider.BouncyCastleProvider provider will be registered if it has not already been registered.

You can have this provider registered as described in the BouncyCastle or BouncyCastle FIPS sections.

We’re done! So, let’s see if it works.

Open a browser and navigate to http://my-service.127.0.0.1.nip.io/api.

Or if you prefer, in another terminal, run:

> curl http://my-service.127.0.0.1.nip.io/api
...
> curl http://my-service.127.0.0.1.nip.io/api
...
> curl http://my-service.127.0.0.1.nip.io/api
...

The responses should alternate randomly between Hello from Red! and Hello from Blue!.

You can compile this application into a native executable:

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

Then, you need to build a container image based on the native executable. For this use the corresponding Dockerfile:

> docker build -f src/main/docker/Dockerfile.native -t quarkus/stork-kubernetes-quickstart .

After publishing the new image to the container registry. You can redeploy the Kubernetes manifest to the cluster.

进一步探索

This guide has shown how to use SmallRye Stork to discover and select your services. You can find more about Stork in: