Deploying to Kubernetes

Let’s now deploy the application to Kubernetes. The instructions In this lesson assume minikube, but any Kubernetes distribution would suffice. If you would like to use another Kubernetes distribution, you may need to adjust some of the commands.

We are still in the development phase of our application, so we do not yet have a CI/CD process in place. We need a way to deploy and test our application in a Kubernetes environment before committing our code into version control. We will use Eclipse JKube for this.

What is Eclipse JKube

Eclipse JKube is a set of Maven and Gradle plugins that enhances a Java application’s build process by providing tasks to your build tool for building container images, generating Kubernetes manifests, and deploying an application to Kubernetes without any hassle. Eclipse JKube also contains tools to assist in developing and debugging a Java application on Kubernetes, such as watching Pods and tailing log files.

NOTE: Eclipse JKube also has an OpenShift-specific plugin, able to generate resources specific for OpenShift.

Adding JKube to the application

JKube can be added to our application simply by adding some configuration to the application’s pom.xml file. We will add JKube’s kubernetes-maven-plugin to our project. We will encapsulate all Kubernetes capabilities into a separate Maven profile named k8s. This is helpful because we only want to interact with the plugin when working with Kubernetes. In those scenarios, we can simply activate the k8s profile.

Add the following XML snippet just after the <build> element in the pom.xml file:

<profiles>
  <profile>
    <id>k8s</id>
    <properties>
      <jkube.enricher.jkube-namespace.namespace>${project.artifactId}</jkube.enricher.jkube-namespace.namespace>
      <jkube.generator.from>registry.access.redhat.com/ubi8/openjdk-${maven.compiler.release}:latest</jkube.generator.from>
      <jkube.namespace>${project.artifactId}</jkube.namespace>
      <jkube.recreate>true</jkube.recreate>
      <jkube.version>1.10.1</jkube.version>
    </properties>
    <build>
      <plugins>
        <plugin>
          <groupId>org.eclipse.jkube</groupId>
          <artifactId>kubernetes-maven-plugin</artifactId>
          <version>${jkube.version}</version>
          <executions>
            <execution>
              <id>jkube</id>
              <goals>
                <goal>resource</goal>
                <goal>build</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

NOTE: The completed pom.xml file containing these changes can be found on the solution branch of the repo.

Let’s examine the properties we used:

  • jkube.enricher.jkube-namespace.namespace property
    • Tells the kubernetes-maven-plugin to create a namespace of the same name as our application’s artifactId (spring-jkube-external-config) when deploying.
  • jkube.generator.from property
    • Tells the kubernetes-maven-plugin which base image to use when building the image. In this example, we are using the OpenJDK image for either Java 11 or 17 based on the maven.compiler.release property.
  • jkube.namespace property
    • Tells the kubernetes-maven-plugin to use the namespace of the same name as our application’s artifactId (spring-jkube-external-config) when deploying anything to Kubernetes.
  • jkube.recreate property
    • Destroys existing resources before deploying new versions.
  • jkube.version property
    • The version of the JKube kubernetes-maven-plugin to use. The current version as of the writing of this lesson is 1.10.1. You can look on Maven Central and use the latest version if you'd like.

The kubernetes-maven-plugin has many additional configuration options available. Please read its documentation for more information.

Deploy to minikube

The JKube plugin will create a container image for you and then deploy it and the generated descriptors to Kubernetes. In this lesson, we will not push the image to a remote container registry. Instead, we will instruct the JKube plugin to push the container image it builds to the internal minikube daemon registry. To do that, you need to point your terminal to minikube’s docker daemon.

NOTE: Before proceeding, please ensure your minikube cluster is started.

For Linux/macOS, accomplish this by running one of the following commands in your terminal, depending on your operating system:

  • Linux/macOS:  eval $(minikube -p minikube docker-env)
  • Windows PowerShell: & minikube -p minikube docker-env --shell powershell | Invoke-Expression
  • Windows CMD: @FOR /f "tokens=*" %i IN ('minikube -p minikube docker-env --shell cmd') DO @%i

NOTE: Please read the full documentation if something isn’t working for you.

In the same terminal as the previous step, run ./mvnw k8s:deploy -Pk8s on Linux/macOS or mvnw.cmd k8s:deploy -Pk8s on Windows. This will activate the newly-added k8s Maven profile, performing these steps:

  1. Compile the application.
  2. Run all of the tests.
  3. Generate all the necessary Kubernetes manifests.
  4. Build a container image for the application.
  5. Push the container image to the internal minikube daemon registry.
  6. Deploy the Kubernetes manifests to the cluster.

The application should now be deployed in minikube, but it is not exposed outside the cluster.

Expose the application

An easy way to expose an application on minikube outside the cluster is to expose its Service as a NodePort. The JKube plugins allow you to create resource fragments that will be applied to the generated Kubernetes manifests. A resource fragment contains just the parts of a manifest you want to override from what the plugin generates, almost like inheritance. JKube will generate the manifest and then overlay the fragments on top. JKube supports many kinds of resource fragments.

NOTE: If deploying to OpenShift, the JKube OpenShift plugin will automatically generate a Route for the application, exposing it outside the cluster automatically.

Let’s create a Service resource fragment:

  1. Create the directory src/main/jkube.
  2. Inside src/main/jkube, create a file named service.yml.
  3. Add the contents of src/main/jkube/service.yml:
spec:
  type: NodePort
  ports:
    - port: 8080
      nodePort: 30526
      targetPort: 8080

This resource fragment instructs the Service manifest to use a NodePort type and to expose it on port 30526. Port 30526 was chosen randomly by the author.

Now, redeploy the application by re-running the same Maven command as before (./mvnw k8s:deploy -Pk8s on Linux/macOS or mvnw.cmd k8s:deploy -Pk8s on Windows).

Once complete, run the command minikube service list. You should see something like this:

|------------------------------|------------------------------|--------------|----------------------------|
|          NAMESPACE           |             NAME             | TARGET PORT  |            URL             |
|------------------------------|------------------------------|--------------|----------------------------|
| default                      | kubernetes                   | No node port |
| kube-system                  | kube-dns                     | No node port |
| spring-jkube-external-config | spring-jkube-external-config | http/8080    | http://192.168.205.5:30526 |
|------------------------------|------------------------------|--------------|----------------------------|

NOTE: Your ip address in the URL will most likely be different than what is shown here.

The URL column should contain a URL for your application that you can now interact with.

Try interacting with the endpoints by opening a browser window or using the curl command from another terminal. The following table shows what the values of some of the endpoints should look like:

  • http://<your-ip>:30526/hello
    • Hello World
  • http://<your-ip>:30526/hello/Yoda
    • Hello Yoda
  • http://<your-ip>:30526/actuator/info
    • {}
  • http://<your-ip>:30526/actuator/env/hello.greeting
{
  "property": {
    "source": "Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'",
    "value": "Hello"
  }
}

NOTE: There is most likely more data in the http://<your-ip>:30526/actuator/env/hello.greeting response. The output is trimmed for brevity.

The hello.greeting property is still being read from the internal application.yml file. Let’s now fix that to read the configuration from a ConfigMap.

Eric Deandrea
Eric Deandrea
Sr. Principal Developer Advocate
Eric is a Sr. Principal Developer Advocate at Red Hat, focusing on Application Development technologies such as Quarkus, Spring Boot, as well as the rest of Red Hat's Runtimes portfolio. Prior to joining Red Hat, Eric spent many years in the financial services & insurance industries. Eric also enjoys contributing to various open-source projects, including Quarkus and the Spring ecosystem. Outside of work, Eric enjoys ice hockey and martial arts. He holds a black belt in Kempo Karate.