Quarkus – Supersonic Subatomic Java, trying out Quarkus guide “Quarkus – Kubernetes extension” (part 1)

Marc Lameriks

In this article, you can read more about the Quarkus code guide I tried out, related to the following topic:

  • The ability to automatically generate Kubernetes resources by Quarkus

The guide covers generating and deploying Kubernetes resources based on sane defaults and user supplied configuration. In this article, I will focus on using custom labels and annotations.

Quarkus guide “Quarkus – Kubernetes extension”

From the Quarkus guide “Building a Native Executable”, part “What’s next?”, I clicked on the link “deployment to Kubernetes and OpenShift”.
[https://quarkus.io/guides/building-native-image#whats-next]

Quarkus offers the ability to automatically generate Kubernetes resources based on sane defaults and user-supplied configuration using dekorate. It currently supports generating resources for vanilla Kubernetes, OpenShift and Knative.
[https://quarkus.io/guides/deploying-to-kubernetes]

One of the prerequisites is access to a Kubernetes cluster (Minikube is a viable option). In my demo environment I had K3s (lightweight certified Kubernetes distribution) installed, so this is fine by me.
[https://technology.amis.nl/2020/08/17/quarkus-supersonic-subatomic-java-setting-up-a-demo-environment-using-vagrant-and-oracle-virtualbox/]

Although this guide uses a new Quarkus project (including the Kubernetes and Jib extensions), I continued this guide with the code in my existing “getting-started” project and I used (as described in my previous article) the Docker container-image extension (quarkus-container-image-docker) instead of the Jib extension (quarkus-container-image-jib).
[https://technology.amis.nl/2020/09/07/quarkus-supersonic-subatomic-java-trying-out-some-quarkus-code-guides-part2/]

The extension quarkus-container-image-docker is using the Docker binary and the generated Dockerfiles under src/main/docker in order to perform Docker builds.
[https://quarkus.io/guides/container-image]

I did have to add the Kubernetes extension to my project. I used vagrant ssh to connect into the running VM. Next, in order to add the extension, I used the following command on the Linux Command Prompt:

cd /vagrant/applications/getting-started
./mvnw quarkus:add-extension -Dextensions="kubernetes"

With the following output:

[INFO] Scanning for projects…
[INFO]
[INFO] ———————-< org.acme:getting-started >———————-
[INFO] Building getting-started 1.0-SNAPSHOT
[INFO] ——————————–[ jar ]———————————
[INFO]
[INFO] — quarkus-maven-plugin:1.7.0.Final:add-extension (default-cli) @ getting-started —
✅ Extension io.quarkus:quarkus-kubernetes has been installed
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 4.190 s
[INFO] Finished at: 2020-09-05T12:54:58Z
[INFO] ————————————————————————
vagrant@ubuntu-bionic:/vagrant/applications/getting-started$

In the pom.xml this added the following dependency:

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-kubernetes</artifactId>
    </dependency>

By adding this dependency, Quarkus enables the generation of Kubernetes manifests each time we perform a build while also enabling the build of a container image using Docker.

Next, I followed the instructions in the guide.

Generation of Kubernetes manifests

In order to package the Quarkus project, I used the following command on the Linux Command Prompt:

./mvnw package

With the following output:


[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< org.acme:getting-started >----------------------
[INFO] Building getting-started 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: https://repo.maven.apache.org/maven2/io/quarkus/quarkus-kubernetes/1.7.0.Final/quarkus-kubernetes-1.7.0.Final.pom
…
Downloaded from central: https://repo.maven.apache.org/maven2/io/dekorate/dekorate-dependencies/0.12.7/dekorate-dependencies-0.12.7.jar (20 MB at 1.0 MB/s)
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ getting-started ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 4 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ getting-started ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- quarkus-maven-plugin:1.7.0.Final:prepare-tests (default) @ getting-started ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ getting-started ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /vagrant/applications/getting-started/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ getting-started ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ getting-started ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.acme.getting.started.GreetingResourceTest
2020-09-05 13:02:52,719 INFO  [io.quarkus] (main) Quarkus 1.7.0.Final on JVM started in 4.424s. Listening on: http://0.0.0.0:8081
2020-09-05 13:02:52,723 INFO  [io.quarkus] (main) Profile test activated.
2020-09-05 13:02:52,723 INFO  [io.quarkus] (main) Installed features: [cdi, kubernetes, resteasy]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 13.024 s - in org.acme.getting.started.GreetingResourceTest
2020-09-05 13:02:56,449 INFO  [io.quarkus] (main) Quarkus stopped in 0.034s
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ getting-started ---
[INFO] Building jar: /vagrant/applications/getting-started/target/getting-started-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- quarkus-maven-plugin:1.7.0.Final:build (default) @ getting-started ---
[INFO] [org.jboss.threads] JBoss Threads version 3.1.1.Final
[WARNING] [io.quarkus.kubernetes.deployment.KubernetesDeployer] A Kubernetes deployment was requested, but the container image to be built will not be pushed to any registry because "quarkus.container-image.registry" has not been set. The Kubernetes deployment will only work properly if the cluster is using the local Docker daemon. For that reason 'ImagePullPolicy' is being force-set to 'IfNotPresent'.
[WARNING] Error reading service account token from: [/var/run/secrets/kubernetes.io/serviceaccount/token]. Ignoring.
[WARNING] Error reading service account token from: [/var/run/secrets/kubernetes.io/serviceaccount/token]. Ignoring.
[INFO] [io.quarkus.deployment.pkg.steps.JarResultBuildStep] Building thin jar: /vagrant/applications/getting-started/target/getting-started-1.0-SNAPSHOT-runner.jar
[WARNING] [io.quarkus.kubernetes.deployment.KubernetesProcessor] No registry was set for the container image, so 'ImagePullPolicy' is being force-set to 'IfNotPresent'.
[INFO] Checking for existing resources in: /vagrant/applications/getting-started/src/main/kubernetes.
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 7361ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  57.983 s
[INFO] Finished at: 2020-09-05T13:03:05Z
[INFO] ------------------------------------------------------------------------
vagrant@ubuntu-bionic:/vagrant/applications/getting-started$

You will notice amongst the other files that are created, two files named kubernetes.json and kubernetes.yml in the target/kubernetes/ directory.
If you look at either file you will see that it contains a Kubernetes ServiceAccount, Service and a Deployment.

Below, you can see the content of the target/kubernetes/kubernetes.json Kubernetes manifest, provided by the Quarkus project packaging:

{
  "apiVersion" : "v1",
  "kind" : "ServiceAccount",
  "metadata" : {
    "annotations" : {
      "app.quarkus.io/build-timestamp" : "2020-09-05 - 13:03:02 +0000"
    },
    "labels" : {
      "app.kubernetes.io/name" : "getting-started",
      "app.kubernetes.io/version" : "1.0-SNAPSHOT"
    },
    "name" : "getting-started"
  }
}{
  "apiVersion" : "v1",
  "kind" : "Service",
  "metadata" : {
    "annotations" : {
      "app.quarkus.io/build-timestamp" : "2020-09-05 - 13:03:02 +0000"
    },
    "labels" : {
      "app.kubernetes.io/name" : "getting-started",
      "app.kubernetes.io/version" : "1.0-SNAPSHOT"
    },
    "name" : "getting-started"
  },
  "spec" : {
    "ports" : [ {
      "name" : "http",
      "port" : 8090,
      "targetPort" : 8090
    } ],
    "selector" : {
      "app.kubernetes.io/name" : "getting-started",
      "app.kubernetes.io/version" : "1.0-SNAPSHOT"
    },
    "type" : "ClusterIP"
  }
}{
  "apiVersion" : "apps/v1",
  "kind" : "Deployment",
  "metadata" : {
    "annotations" : {
      "app.quarkus.io/build-timestamp" : "2020-09-05 - 13:03:02 +0000"
    },
    "labels" : {
      "app.kubernetes.io/name" : "getting-started",
      "app.kubernetes.io/version" : "1.0-SNAPSHOT"
    },
    "name" : "getting-started"
  },
  "spec" : {
    "replicas" : 1,
    "selector" : {
      "matchLabels" : {
        "app.kubernetes.io/name" : "getting-started",
        "app.kubernetes.io/version" : "1.0-SNAPSHOT"
      }
    },
    "template" : {
      "metadata" : {
        "annotations" : {
          "app.quarkus.io/build-timestamp" : "2020-09-05 - 13:03:02 +0000"
        },
        "labels" : {
          "app.kubernetes.io/name" : "getting-started",
          "app.kubernetes.io/version" : "1.0-SNAPSHOT"
        }
      },
      "spec" : {
        "containers" : [ {
          "env" : [ {
            "name" : "KUBERNETES_NAMESPACE",
            "valueFrom" : {
              "fieldRef" : {
                "fieldPath" : "metadata.namespace"
              }
            }
          } ],
          "image" : "vagrant/getting-started:1.0-SNAPSHOT",
          "imagePullPolicy" : "IfNotPresent",
          "name" : "getting-started",
          "ports" : [ {
            "containerPort" : 8090,
            "name" : "http",
            "protocol" : "TCP"
          } ]
        } ],
        "serviceAccount" : "getting-started"
      }
    }
  }
}

Below, you can see the content of the target/kubernetes/kubernetes.yml Kubernetes manifest, provided by the Quarkus project packaging:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-05 - 13:03:02 +0000
  labels:
    app.kubernetes.io/name: getting-started
    app.kubernetes.io/version: 1.0-SNAPSHOT
  name: getting-started
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-05 - 13:03:02 +0000
  labels:
    app.kubernetes.io/name: getting-started
    app.kubernetes.io/version: 1.0-SNAPSHOT
  name: getting-started
spec:
  ports:
  - name: http
    port: 8090
    targetPort: 8090
  selector:
    app.kubernetes.io/name: getting-started
    app.kubernetes.io/version: 1.0-SNAPSHOT
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-05 - 13:03:02 +0000
  labels:
    app.kubernetes.io/name: getting-started
    app.kubernetes.io/version: 1.0-SNAPSHOT
  name: getting-started
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: getting-started
      app.kubernetes.io/version: 1.0-SNAPSHOT
  template:
    metadata:
      annotations:
        app.quarkus.io/build-timestamp: 2020-09-05 - 13:03:02 +0000
      labels:
        app.kubernetes.io/name: getting-started
        app.kubernetes.io/version: 1.0-SNAPSHOT
    spec:
      containers:
      - env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: vagrant/getting-started:1.0-SNAPSHOT
        imagePullPolicy: IfNotPresent
        name: getting-started
        ports:
        - containerPort: 8090
          name: http
          protocol: TCP
      serviceAccount: getting-started

Remark about the Deloyment, container image part:
An important thing to note about the Deployment is that vagrant/getting-started:1.0-SNAPSHOT is used as the container image of the Pod. As I explained in my previous article, the name and tag of the image are controlled by the the Docker container-image extension and can be customized using the usual application.properties.
[https://technology.amis.nl/2020/09/07/quarkus-supersonic-subatomic-java-trying-out-some-quarkus-code-guides-part2/]

Remark about the Deloyment, container port part:
You can also see that port 8090 is used instead of 8080. That’s because I changed the HTTP port in src/main/resources/application.properties as I described in a previous article:
[https://technology.amis.nl/2020/09/01/quarkus-supersonic-subatomic-java-trying-out-some-quarkus-code-guides-part1/]

#
# The HTTP port
#
#quarkus.http.port=8080
quarkus.http.port=8090

Remark about the Deloyment, service account part:
You can also see that serviceAccount is used. However, serviceAccount is a depreciated alias for serviceAccountName. Deprecated: Use serviceAccountName instead. ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
[https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#podspec-v1-core]

To use a non-default service account, simply set the spec.serviceAccountName field of a pod to the name of the service account you wish to use.

The service account has to exist at the time the pod is created, or it will be rejected.
[https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/]

Kubernetes Labels

Labels are key/value pairs that are attached to objects, such as pods. Labels are intended to be used to specify identifying attributes of objects that are meaningful and relevant to users, but do not directly imply semantics to the core system. Labels can be used to organize and to select subsets of objects.

Labels allow for efficient queries and watches and are ideal for use in UIs and CLIs. Non-identifying information should be recorded using annotations.

Keep in mind that label Key must be unique for a given object.
[https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/]

Kubernetes Annotations

You can use Kubernetes annotations to attach arbitrary non-identifying metadata to objects. Clients such as tools and libraries can retrieve this metadata.

The metadata in an annotation can be small or large, structured or unstructured, and can include characters not permitted by labels.
[https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/]

Customizing the recommended labels

The generated manifests use the Kubernetes recommended labels. These labels can be customized.

You can visualize and manage Kubernetes objects with more tools than kubectl and the dashboard. A common set of labels allows tools to work interoperably, describing objects in a common manner that all tools can understand. In addition to supporting tooling, the recommended labels describe applications in a way that can be queried.
[https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/]

In order to take full advantage of using these labels, they should be applied on every resource object.

Label key Description Example Type
app.kubernetes.io/name The name of the application mysql string
app.kubernetes.io/instance A unique name identifying the instance of an application mysql-abcxzy string
app.kubernetes.io/version The current version of the application (e.g., a semantic version, revision hash, etc.) 5.7.21 string
app.kubernetes.io/component The component within the architecture database string
app.kubernetes.io/part-of The name of a higher level application this one is part of wordpress string
app.kubernetes.io/managed-by The tool being used to manage the operation of an application helm string

[https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/]

If you look at the Kubernetes manifest file above, you can see the following labels (and annotations) are used:

  annotations:
    app.quarkus.io/build-timestamp: 2020-09-05 - 13:03:02 +0000
  labels:
    app.kubernetes.io/name: getting-started
    app.kubernetes.io/version: 1.0-SNAPSHOT

So now it’s time to try to customize the values of these recommended labels.

The labels can be customized using quarkus.kubernetes.name, quarkus.kubernetes.version and quarkus.kubernetes.part-of.
[https://quarkus.io/guides/deploying-to-kubernetes#labels]

Label key Quarkus key Quarkus key description
app.kubernetes.io/name quarkus.kubernetes.name The name of the application. This value will be used for naming Kubernetes resources like: - Deployment - Service and so on …​
app.kubernetes.io/instance
app.kubernetes.io/version quarkus.kubernetes.version The version of the application.
app.kubernetes.io/component
app.kubernetes.io/part-of quarkus.kubernetes.part-of The name of the group this component belongs too
app.kubernetes.io/managed-by

[https://quarkus.io/guides/all-config]

Following, the guide, I changed the code of src/main/resources/application.properties to the following:
[in bold, I highlighted the changes (except app.quarkus.io/build-timestamp)]

# Configuration file
# key = value

# Your configuration properties
greeting.message = hello
greeting.name = quarkus

#
# The HTTP port
#
#quarkus.http.port=8080
quarkus.http.port=8090

# The path of the banner (path relative to root of classpath)
# which could be provided by user
#
#quarkus.banner.path=default_banner.txt
quarkus.banner.path=my_banner.txt

# Kubernetes manifest recommended labels
quarkus.kubernetes.name=my-quarkus-kubernetes-name
quarkus.kubernetes.instance=my_quarkus_kubernetes_instance
quarkus.kubernetes.version=my_quarkus_kubernetes_version
quarkus.kubernetes.component=my_quarkus_kubernetes_component
quarkus.kubernetes.part-of=my_quarkus_kubernetes_part-of
quarkus.kubernetes.managed-by=my_quarkus_kubernetes_managed-by

Remark:
As you can see, I also tried to change recommended labels there are not supported. I just wanted to try this out.

In order to recreate the Kubernetes manifests, I used the following command on the Linux Command Prompt:

./mvnw package

Remark:
The output showed the following:


2020-09-09 19:04:57,082 WARN  [io.qua.config] (main) Unrecognized configuration key "quarkus.kubernetes.instance" was provided; it will be ignored; verify that the dependency extension for this configuration is set or you did not make a typo
2020-09-09 19:04:57,083 WARN  [io.qua.config] (main) Unrecognized configuration key "quarkus.kubernetes.component" was provided; it will be ignored; verify that the dependency extension for this configuration is set or you did not make a typo
2020-09-09 19:04:57,083 WARN  [io.qua.config] (main) Unrecognized configuration key "quarkus.kubernetes.managed-by" was provided; it will be ignored; verify that the dependency extension for this configuration is set or you did not make a typo

Below, you can see the content of the target/kubernetes/kubernetes.yml Kubernetes manifest, provided by the Quarkus project packaging:
[in bold, I highlighted the changes (except app.quarkus.io/build-timestamp)]

---
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-09 - 19:05:26 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
  name: my-quarkus-kubernetes-name
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-09 - 19:05:26 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
  name: my-quarkus-kubernetes-name
spec:
  ports:
  - name: http
    port: 8090
    targetPort: 8090
  selector:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-09 - 19:05:26 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
  name: my-quarkus-kubernetes-name
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: my-quarkus-kubernetes-name
      app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
      app.kubernetes.io/version: my_quarkus_kubernetes_version
  template:
    metadata:
      annotations:
        app.quarkus.io/build-timestamp: 2020-09-09 - 19:05:26 +0000
      labels:
        app.kubernetes.io/name: my-quarkus-kubernetes-name
        app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
        app.kubernetes.io/version: my_quarkus_kubernetes_version
    spec:
      containers:
      - env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: vagrant/getting-started:1.0-SNAPSHOT
        imagePullPolicy: IfNotPresent
        name: my-quarkus-kubernetes-name
        ports:
        - containerPort: 8090
          name: http
          protocol: TCP
      serviceAccount: my-quarkus-kubernetes-name

Remark about the labels:
As could be expected, from the labels, only the 3 supported recommended labels were changed.
So, for the next steps, I removed them again.

Remark about the metadata.name:
As you can see, also the metadata.name of the Kubernetes ServiceAccount, Service and Deployment were changed.

Each object in your cluster has a Name that is unique for that type of resource.

Most resource types require a name that can be used as a DNS subdomain name as defined in RFC 1123. This means the name must:

  • contain no more than 253 characters
  • contain only lowercase alphanumeric characters, ‘-‘ or ‘.’
  • start with an alphanumeric character
  • end with an alphanumeric character

Some resource types require their names to follow the DNS label standard as defined in RFC 1123. This means the name must:

  • contain at most 63 characters
  • contain only lowercase alphanumeric characters or ‘-‘
  • start with an alphanumeric character
  • end with an alphanumeric character

[ https://kubernetes.io/docs/concepts/overview/working-with-objects/names/]

So, you can expect a build error like the example below if you don’t conform to the name constraints (with regard to for example a ServiceAccount):


[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:1.7.0.Final:build (default) on project getting-started: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR]         [error]: Build step io.quarkus.kubernetes.deployment.KubernetesDeployer#deploy threw an exception: io.dekorate.deps.kubernetes.client.KubernetesClientException: Failure executing: POST at: https://127.0.0.1:6443/api/v1/namespaces/default/serviceaccounts. Message: ServiceAccount "my_quarkus_kubernetes_name" is invalid: metadata.name: Invalid value: "my_quarkus_kubernetes_name": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*'). 

[https://tools.ietf.org/html/rfc1123]

So, you can expect a build error like the example below if you don’t conform to the name constraints (with regard to for example a Service):


ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:1.7.0.Final:build (default) on project getting-started: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR]         [error]: Build step io.quarkus.kubernetes.deployment.KubernetesDeployer#deploy threw an exception: io.dekorate.deps.kubernetes.client.KubernetesClientException: Failure executing: POST at: https://127.0.0.1:6443/api/v1/namespaces/default/services. Message: Service "my.quarkus.kubernetes.name" is invalid: metadata.name: Invalid value: "my.quarkus.kubernetes.name": a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?').

[https://tools.ietf.org/html/rfc1035]

Using custom Labels

In a previous article I describe how I deployed applications (based on the booksservice) to Minikube. I created a development (DEV) and a testing (TST) environment. In the DEV environment the applications (version 1.0 and 2.0) were using an H2 in-memory database. In the TST environment the application (version 1.0) were using an external MySQL database.

I described how, in the Kubernetes manifest files, I used custom labels, so a ReplicaSet could manage pods with labels that matched the selector. In my case these labels were for example:

Label keyLabel value
appbooksservice
version1.0
environmentdevelopment

I also used Environment variables, for example to set up the active Spring profile.
[https://technology.amis.nl/2019/03/05/using-a-restful-web-service-spring-boot-application-in-minikube-together-with-an-external-dockerized-mysql-database/]

Quarkus offers the ability to add additional custom labels. Just apply the following configuration:

quarkus.kubernetes.labels.<label key>=<label value>

Then, following the guide, I changed the code of src/main/resources/application.properties by adding the following:

# Kubernetes manifest custom labels
quarkus.kubernetes.labels.app=getting-started
quarkus.kubernetes.labels.version=1.0
quarkus.kubernetes.labels.environment=development

Remark about label keys:
For creating valid label keys, please see:
https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set

Remark about label values:
The label values must be strings. In yaml, that means all numeric values must be quoted.
[https://github.com/kubernetes/kubernetes/issues/57509]

Later on, following this code guide, I also tried using the following command and the Kubernetes manifest (with content shown further below) to create the resources:

kubectl apply -f /vagrant/applications/getting-started/target/kubernetes/kubernetes.yml

But this resulted in the following error about the label with key version and value 1.0:


unable to decode "/vagrant/applications/getting-started/target/kubernetes/kubernetes.yml": resource.metadataOnlyObject.ObjectMeta: v1.ObjectMeta.Namespace: Name: Labels: ReadString: expects " or n, but found 1, error found in #10 byte of ...|version":1},"name":"|..., bigger context ...|s_version","environment":"development","version":1},"name":"my-quarkus-kubernetes-name","namespace":|...

Unfortunately, getting quotes around a numeric label value, wasn’t easy.
I tried quarkus.kubernetes.labels.version=”1.0″ in application.properties, but this resulted in the target/kubernetes/kubernetes.yml Kubernetes manifest, in:

version: ‘”1.0″‘

For now, I left this label value without quotes and didn’t proceed with using the kubectl command mentioned above.

In order to recreate the Kubernetes manifests, I used the following command on the Linux Command Prompt:

./mvnw package

Below, you can see the content of the target/kubernetes/kubernetes.yml Kubernetes manifest, provided by the Quarkus project packaging:
[in bold, I highlighted the changes (except app.quarkus.io/build-timestamp)]

---
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-09 - 19:30:15 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
    app: getting-started
    environment: development
    version: 1.0
  name: my-quarkus-kubernetes-name
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-09 - 19:30:15 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
    app: getting-started
    environment: development
    version: 1.0
  name: my-quarkus-kubernetes-name
spec:
  ports:
  - name: http
    port: 8090
    targetPort: 8090
  selector:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    app.quarkus.io/build-timestamp: 2020-09-09 - 19:30:15 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
    app: getting-started
    environment: development
    version: 1.0
  name: my-quarkus-kubernetes-name
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: my-quarkus-kubernetes-name
      app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
      app.kubernetes.io/version: my_quarkus_kubernetes_version
  template:
    metadata:
      annotations:
        app.quarkus.io/build-timestamp: 2020-09-09 - 19:30:15 +0000
      labels:
        app.kubernetes.io/name: my-quarkus-kubernetes-name
        app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
        app.kubernetes.io/version: my_quarkus_kubernetes_version
        app: getting-started
        environment: development
        version: 1.0
    spec:
      containers:
      - env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: vagrant/getting-started:1.0-SNAPSHOT
        imagePullPolicy: IfNotPresent
        name: my-quarkus-kubernetes-name
        ports:
        - containerPort: 8090
          name: http
          protocol: TCP
      serviceAccount: my-quarkus-kubernetes-name

Remark:
As you can see, only the objects (for example Service) metadata labels were changed, the selector labels aren’t changed.

Service, selector: Route service traffic to pods with label keys and values matching this selector.
[https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#service-v1-core]

Deployment, selector: Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template’s labels.
[https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#deploymentspec-v1-apps]

Customizing the annotations

Out of the box, the generated resources will be annotated with version control related information that can be used either by tooling, or by the user for troubleshooting purposes.

Although the code guide talks about the following:

“annotations”: {
“app.quarkus.io/vcs-url” : “<some url>”,
“app.quarkus.io/commit-id” : “<some git SHA>”,
}

As mentioned before the generated manifest actually has the following annotation:

app.quarkus.io/build-timestamp

Remark:
With regard to the two annotations mentioned in the code guide, please see:
https://github.com/quarkusio/quarkus/issues/9280

Using custom annotations

Custom annotations can be added in a way similar to labels.

Just apply the following configuration:

quarkus.kubernetes.annotations.<label key>=<label value>

Then, following the guide, I changed the code of src/main/resources/application.properties by adding the following:

# Kubernetes manifest custom annotations
quarkus.kubernetes.annotations.my_key1=key1_value
quarkus.kubernetes.annotations.my_key2=key2_value

In order to recreate the Kubernetes manifests, I used the following command on the Linux Command Prompt:

./mvnw package

Below, you can see the content of the target/kubernetes/kubernetes.yml Kubernetes manifest, provided by the Quarkus project packaging:
[in bold, I highlighted the changes (except app.quarkus.io/build-timestamp)]

---
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    my_key1: key1_value
    my_key2: key2_value
    app.quarkus.io/build-timestamp: 2020-09-10 - 19:12:44 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
    app: getting-started
    environment: development
    version: 1.0
  name: my-quarkus-kubernetes-name
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    my_key1: key1_value
    my_key2: key2_value
    app.quarkus.io/build-timestamp: 2020-09-10 - 19:12:44 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
    app: getting-started
    environment: development
    version: 1.0
  name: my-quarkus-kubernetes-name
spec:
  ports:
  - name: http
    port: 8090
    targetPort: 8090
  selector:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    my_key1: key1_value
    my_key2: key2_value
    app.quarkus.io/build-timestamp: 2020-09-10 - 19:12:44 +0000
  labels:
    app.kubernetes.io/name: my-quarkus-kubernetes-name
    app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
    app.kubernetes.io/version: my_quarkus_kubernetes_version
    app: getting-started
    environment: development
    version: 1.0
  name: my-quarkus-kubernetes-name
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: my-quarkus-kubernetes-name
      app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
      app.kubernetes.io/version: my_quarkus_kubernetes_version
  template:
    metadata:
      annotations:
        my_key1: key1_value
        my_key2: key2_value
        app.quarkus.io/build-timestamp: 2020-09-10 - 19:12:44 +0000
      labels:
        app.kubernetes.io/name: my-quarkus-kubernetes-name
        app.kubernetes.io/part-of: my_quarkus_kubernetes_part-of
        app.kubernetes.io/version: my_quarkus_kubernetes_version
        app: getting-started
        environment: development
        version: 1.0
    spec:
      containers:
      - env:
        - name: KUBERNETES_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: vagrant/getting-started:1.0-SNAPSHOT
        imagePullPolicy: IfNotPresent
        name: my-quarkus-kubernetes-name
        ports:
        - containerPort: 8090
          name: http
          protocol: TCP
      serviceAccount: my-quarkus-kubernetes-name

So, I conclude this article. I shared with you the steps I took trying out the Quarkus code guide “Quarkus – Kubernetes extension”, and more specific the steps related to using custom labels and annotations.

In a next article, you can read more about other steps I took (continuing with the code guide), for example using namespaces and the automatic deployment of the generated resources to a target platform, in my case K3s (lightweight certified Kubernetes distribution).

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Next Post

Quarkus - Supersonic Subatomic Java, trying out Quarkus guide “Quarkus - Kubernetes extension” (part 2)

Facebook0TwitterLinkedinIn this article, you can read more about the Quarkus code guide I tried out, related to the following topic: The ability to automatically generate Kubernetes resources by Quarkus The guide covers generating and deploying Kubernetes resources based on sane defaults and user supplied configuration. In this article, I will […]