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 Pod
s 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’sartifactId
(spring-jkube-external-config
) when deploying.
- Tells the
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.
- 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
jkube.namespace
property- Tells the
kubernetes-maven-plugin
to use the namespace of the same name as our application’sartifactId
(spring-jkube-external-config
) when deploying anything to Kubernetes.
- Tells the
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 version of the JKube
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:
- Compile the application.
- Run all of the tests.
- Generate all the necessary Kubernetes manifests.
- Build a container image for the application.
- Push the container image to the internal minikube daemon registry.
- 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:
- Create the directory
src/main/jkube
. - Inside
src/main/jkube
, create a file namedservice.yml
. - 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
.