Configuring microservices running in Kubernetes

duration 15 minutes
Git clone to get going right away:
git clone https://github.com/OpenLiberty/guide-kubernetes-microprofile-config.git
Copy Github clone command

Explore how to externalize configuration using MicroProfile Config and configure your microservices using Kubernetes ConfigMaps and Secrets.

What you’ll learn

You will learn how and why to externalize your microservice’s configuration. Externalized configuration is useful because configuration usually changes depending on your environment. You will also learn how to configure the environment by providing required values to your application using Kubernetes; this allows for easier deployment to different environments.

MicroProfile Config provides useful annotations that you can use to inject configured values into your code. These values can come from any config sources, such as environment variables. To learn more about MicroProfile Config, read the Configuring microservices guide.

Furthermore, you’ll learn how to set these environment variables with ConfigMaps and Secrets. These resources are provided by Kubernetes and act as a data source for your environment variables. You can use a ConfigMap or Secret to set environment variables for any number of containers.

Prerequisites

Before you begin, have the following tools installed:

First, you will need a containerization software for building containers. Kubernetes supports a variety of container types. You will use Docker in this guide. For installation instructions, refer to the official Docker documentation.

Windows | Mac

Use Docker Desktop, where a local Kubernetes environment is pre-installed and enabled. If you do not see the Kubernetes tab then you have an older version of Docker Desktop; upgrade to the latest version.

Complete the setup for your operating system:

  • Set up Docker for Windows.

  • Set up Docker for Mac.

  • After following one of the sets of instructions, ensure that Kubernetes (not Swarm) is selected as the orchestrator in Docker Preferences.

Linux

You will use Minikube as a single-node Kubernetes cluster that runs locally in a virtual machine. For Minikube installation instructions see the minikube installation instructions. Make sure to read the Requirements section as different operating systems require different prerequisites to get Minikube running.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-kubernetes-microprofile-config.git
cd guide-kubernetes-microprofile-config

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Starting and preparing your cluster for deployment

Start your Kubernetes cluster.

Windows | Mac

Start your Docker Desktop environment.

Linux

Run the following command from a command line:

minikube start

Next, validate that you have a healthy Kubernetes environment by running the following command from the command line.

kubectl get nodes

This command should return a Ready status for the master node.

Windows | Mac

You do not need to do any other step.

Linux

Run the following command to configure the Docker CLI to use Minikube’s Docker daemon. After you run this command, you will be able to interact with Minikube’s Docker daemon and build new images directly to it from your host machine:

eval $(minikube docker-env)

Deploying the microservices

The two microservices you will deploy are called name and ping. The name microservice displays a brief greeting and the name of the container that it runs in. The ping microservice pings the Kubernetes Service that encapsulates the pod running the name microservice. The ping microservice demonstrates how communication can be established between pods inside a cluster. To build these applications, navigate to the start directory and run the following command.

mvn package

When the build succeeds, run the following command to deploy the necessary Kubernetes resources to serve the applications.

kubectl apply -f kubernetes.yaml

When this command finishes, wait for the pods to be in the Ready state. Run the following command to view the status of the pods.

kubectl get pods

When the pods are ready, the output shows 1/1 for READY and Running for STATUS.

NAME                               READY     STATUS    RESTARTS   AGE
name-deployment-6bd97d9bf6-6d2cj   1/1       Running   0          34s
ping-deployment-645767664f-7gnxf   1/1       Running   0          34s

After the pods are ready, you will make requests to your services.

Windows | Mac

The default hostname for Docker Desktop is localhost.

Linux

The default hostname for minikube is 192.168.99.100. Otherwise it can be found using the minikube ip command.

Navigate to http://[hostname]:31000/api/name and use the username bob and the password bobpwd to authenticate. Replace [hostname] with the IP address or hostname of your Kubernetes cluster. You will see something similar to Hello! I’m container name-deployment-6bd97d9bf6-qbhbc.

Similarly, navigate to http://[hostname]:32000/api/ping/name-service and you will see pong.

Modifying name microservice

The name service is hardcoded to have Hello! as the greeting message. You’ll make this message configurable by adding the greeting member and return value.

Replace the NameResource class.
name/src/main/java/io/openliberty/guides/name/NameResource.java

NameResource.java

 1package io.openliberty.guides.name;
 2
 3import javax.enterprise.context.RequestScoped;
 4import javax.inject.Inject;
 5import javax.ws.rs.GET;
 6import javax.ws.rs.Path;
 7import javax.ws.rs.Produces;
 8import javax.ws.rs.core.MediaType;
 9
10import org.eclipse.microprofile.config.inject.ConfigProperty;
11
12@RequestScoped
13@Path("/")
14public class NameResource {
15
16    @Inject
17    @ConfigProperty(name = "GREETING")
18    private String greeting;
19
20    @Inject
21    @ConfigProperty(name = "HOSTNAME")
22    private String hostname;
23
24    @GET
25    @Produces(MediaType.TEXT_PLAIN)
26    public String getContainerName() {
27        return greeting + " I'm container " + hostname + "\n";
28    }
29
30}

The greeting member was added to the return value of getContainerName().

These changes use MicroProfile Config and CDI to inject the value of an environment variable called GREETING into the greeting member of the NameResource class.

Another change uses the new greeting member to create a response with a custom greeting message in getContainerName().

Modifying ping microservice

The ping service is hardcoded to use bob and bobpwd as the credentials to authenticate against the name service. You’ll make these credentials configurable.

Replace the PingResource class.
ping/src/main/java/io/openliberty/guides/ping/PingResource.java

PingResource.java

 1package io.openliberty.guides.ping;
 2
 3import java.net.MalformedURLException;
 4import java.net.URL;
 5import java.net.UnknownHostException;
 6import java.util.Base64;
 7
 8import javax.enterprise.context.RequestScoped;
 9import javax.inject.Inject;
10import javax.ws.rs.GET;
11import javax.ws.rs.Path;
12import javax.ws.rs.PathParam;
13import javax.ws.rs.ProcessingException;
14import javax.ws.rs.Produces;
15import javax.ws.rs.core.MediaType;
16
17import org.apache.commons.lang3.exception.ExceptionUtils;
18import org.eclipse.microprofile.config.inject.ConfigProperty;
19import org.eclipse.microprofile.rest.client.RestClientBuilder;
20
21import io.openliberty.guides.ping.client.NameClient;
22import io.openliberty.guides.ping.client.UnknownUrlException;
23
24@RequestScoped
25@Path("")
26public class PingResource {
27
28    @Inject
29    @ConfigProperty(name = "USERNAME")
30    private String username;
31
32    @Inject
33    @ConfigProperty(name = "PASSWORD")
34    private String password;
35
36    @GET
37    @Path("/{hostname}")
38    @Produces(MediaType.TEXT_PLAIN)
39    public String getContainerName(@PathParam("hostname") String host) {
40        try {
41            NameClient nameClient = RestClientBuilder.newBuilder()
42                            .baseUrl(new URL("http://" + host + ":9080/api"))
43                            .register(UnknownUrlException.class)
44                            .build(NameClient.class);
45            nameClient.getContainerName(getAuthHeader());
46            return "pong\n";
47        } catch (ProcessingException ex) {
48            // Checking if UnknownHostException is nested inside and rethrowing if not.
49            if (this.isUnknownHostException(ex)) {
50                System.err.println("The specified host is unknown");
51                ex.printStackTrace();
52            } else {
53                throw ex;
54            }
55        } catch (UnknownUrlException ex) {
56            System.err.println("The given URL is unreachable");
57            ex.printStackTrace();
58        } catch (MalformedURLException ex) {
59            System.err.println("The given URL is not formatted correctly.");
60            ex.printStackTrace();
61        }
62        return "Bad response from " + host + "\nCheck the console log for more info.";
63    }
64
65    private boolean isUnknownHostException(ProcessingException ex) {
66        Throwable rootEx = ExceptionUtils.getRootCause(ex);
67        return rootEx != null && rootEx instanceof UnknownHostException;
68    }
69
70    private String getAuthHeader() {
71        String usernamePassword = username + ":" + password;
72        String encoded = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
73        return "Basic " + encoded;
74    }
75
76}

The changes introduced here use MicroProfile Config and CDI to inject the value of the environment variables USERNAME and PASSWORD into the PingResource class.

Creating a ConfigMap and Secret

There are several ways to configure an environment variable in a Docker container. You can set it directly in the Dockerfile with the ENV command. You can also set it in your kubernetes.yaml file by specifying a name and a value for the environment variable you want to set for a specific container. With these options in mind, you are going to use a ConfigMap and Secret to set these values. These are resources provided by Kubernetes that are used as a way to provide configuration values to your containers. A benefit is that they can be re-used across many different containers, even if they all require different environment variables to be set with the same value.

Create a ConfigMap to configure the greeting with the following kubectl command.

kubectl create configmap greeting-config --from-literal message=Greetings...

This command deploys a ConfigMap named greeting-config to your cluster. It has a key called message with a value of Greetings…​. The --from-literal flag allows you to specify individual key-value pairs to store in this ConfigMap. Other available options, such as --from-file and --from-env-file, provide more versatility as to what you want to configure. Details about these options can be found in the Kubernetes CLI documentation.

Create a Secret to configure the credentials that ping will use to authenticate against name with the following kubectl command.

kubectl create secret generic name-credentials --from-literal username=bob --from-literal password=bobpwd

This command looks very similar to the command to create a ConfigMap, one difference is the word generic. It means that you’re creating a Secret that is generic, in other words it stores information that is not specialized in any way. There are different types of secrets, such as secrets to store Docker credentials and secrets to store public/private key pairs.

A Secret is similar to a ConfigMap, except a Secret is used for confidential information such as credentials. One of the main differences is that you have to explicitly tell kubectl to show you the contents of a Secret. Additionally, when it does show you the information, it only shows you a Base64 encoded version so that a casual onlooker doesn’t accidentally see any sensitive data. Secrets don’t provide any encryption by default, that is something you’ll either need to do yourself or find an alternate option to configure.

kubernetes.yaml

 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: name-deployment
 5  labels:
 6    app: name
 7spec:
 8  selector:
 9    matchLabels:
10      app: name
11  template:
12    metadata:
13      labels:
14        app: name
15    spec:
16      containers:
17      - name: name-container
18        image: name:1.0-SNAPSHOT
19        ports:
20        - containerPort: 9080
21        # Set the GREETING environment variable
22        env:
23        - name: GREETING
24          valueFrom:
25            configMapKeyRef:
26              name: greeting-config
27              key: message
28---
29apiVersion: apps/v1
30kind: Deployment
31metadata:
32  name: ping-deployment
33  labels:
34    app: ping
35spec:
36  selector:
37    matchLabels:
38      app: ping
39  template:
40    metadata:
41      labels:
42        app: ping
43    spec:
44      containers:
45      - name: ping-container
46        image: ping:1.0-SNAPSHOT
47        ports:
48        - containerPort: 9080
49        # Set the USERNAME and PASSWORD environment variables
50        env:
51        - name: USERNAME
52          valueFrom:
53            secretKeyRef:
54              name: name-credentials
55              key: username
56        - name: PASSWORD
57          valueFrom:
58            secretKeyRef:
59              name: name-credentials
60              key: password
61---
62apiVersion: v1
63kind: Service
64metadata:
65  name: name-service
66spec:
67  type: NodePort
68  selector:
69    app: name
70  ports:
71  - protocol: TCP
72    port: 9080
73    targetPort: 9080
74    nodePort: 31000
75---
76apiVersion: v1
77kind: Service
78metadata:
79  name: ping-service
80spec:
81  type: NodePort
82  selector:
83    app: ping
84  ports:
85  - protocol: TCP
86    port: 9080
87    targetPort: 9080
88    nodePort: 32000

Dockerfile

1FROM open-liberty
2
3COPY target/liberty/wlp/usr/servers/defaultServer/bootstrap.properties /config
4COPY src/main/liberty/config/server.xml /config
5COPY target/*.war /config/apps/name.war

Updating Kubernetes resources

Next, you will update your Kubernetes deployments to set the environment variables in your containers based on the values configured in the ConfigMap and Secret created previously. The env section under the name-container is where the GREETING environment variable will be set. The env section under the ping-container is where the USERNAME and PASSWORD environment variables will be set.

Replace the kubernetes file.
kubernetes.yaml

kubernetes.yaml

 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: name-deployment
 5  labels:
 6    app: name
 7spec:
 8  selector:
 9    matchLabels:
10      app: name
11  template:
12    metadata:
13      labels:
14        app: name
15    spec:
16      containers:
17      - name: name-container
18        image: name:1.0-SNAPSHOT
19        ports:
20        - containerPort: 9080
21        # Set the GREETING environment variable
22        env:
23        - name: GREETING
24          valueFrom:
25            configMapKeyRef:
26              name: greeting-config
27              key: message
28---
29apiVersion: apps/v1
30kind: Deployment
31metadata:
32  name: ping-deployment
33  labels:
34    app: ping
35spec:
36  selector:
37    matchLabels:
38      app: ping
39  template:
40    metadata:
41      labels:
42        app: ping
43    spec:
44      containers:
45      - name: ping-container
46        image: ping:1.0-SNAPSHOT
47        ports:
48        - containerPort: 9080
49        # Set the USERNAME and PASSWORD environment variables
50        env:
51        - name: USERNAME
52          valueFrom:
53            secretKeyRef:
54              name: name-credentials
55              key: username
56        - name: PASSWORD
57          valueFrom:
58            secretKeyRef:
59              name: name-credentials
60              key: password
61---
62apiVersion: v1
63kind: Service
64metadata:
65  name: name-service
66spec:
67  type: NodePort
68  selector:
69    app: name
70  ports:
71  - protocol: TCP
72    port: 9080
73    targetPort: 9080
74    nodePort: 31000
75---
76apiVersion: v1
77kind: Service
78metadata:
79  name: ping-service
80spec:
81  type: NodePort
82  selector:
83    app: ping
84  ports:
85  - protocol: TCP
86    port: 9080
87    targetPort: 9080
88    nodePort: 32000

In the kubernetes.yaml file where the containers are defined, you can see the valueFrom field which allows you to specify the value of an environment variable from a variety of sources. These sources include a ConfigMap, a Secret, and information about the cluster. In this example configMapKeyRef gets the value message from the ConfigMap greeting-config. Similarly, secretKeyRef gets the values username and password from the Secret name-credentials.

Deploying your changes

Rebuild the application using mvn package.

mvn package

Run the following commands to deploy your changes to the Kubernetes cluster.

kubectl delete -f kubernetes.yaml
kubectl apply -f kubernetes.yaml

Navigate to http://[hostname]:31000/api/name and you will see that the greeting message has changed from Hello! to Greetings…​. Verify that http://[hostname]:32000/api/ping/name-service is working as intended. If it is not, then check the configuration of the credentials.

Testing the microservices

Windows | Mac

Run the integration tests against a cluster running with a hostname of localhost:

mvn verify -Ddockerfile.skip=true -Dcluster.ip=localhost -Dname.message=Greetings...

Linux

Run the integration tests against a cluster running at Minikube’s IP address:

mvn verify -Ddockerfile.skip=true -Dcluster.ip=`minikube ip` -Dname.message=Greetings...

The tests check that the name service responds with a container name and that the message in the response matches the configured message. The tests for ping verify that the service returns with a pong response on success and that it gracefully handles a bad response. If the credentials are misconfigured, then the ping test will fail, so the ping test indirectly verifies the credentials are correctly configured.

After the tests succeed, you should see output similar to the following in your console.

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.name.NameEndpointTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.542 sec - in it.io.openliberty.guides.name.NameEndpointTest

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.ping.PingEndpointTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.761 sec - in it.io.openliberty.guides.ping.PingEndpointTest

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Tearing down the environment

Run the following commands to delete all the resources that you created.

kubectl delete -f kubernetes.yaml
kubectl delete configmap greeting-config
kubectl delete secret name-credentials

Windows | Mac

Nothing more needs to be done for Docker Desktop.

Linux

Perform the following steps to return your environment to a clean state.

  1. Point the Docker daemon back to your local machine:

    eval $(minikube docker-env -u)
  2. Stop your Minikube cluster:

    minikube stop
  3. Delete your cluster:

    minikube delete

Great work! You’re done!

You have used MicroProfile Config to externalize the configuration of two microservices, and then you configured them by creating a ConfigMap and Secret in your Kubernetes cluster.

Guide Attribution

Configuring microservices running in Kubernetes by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy file contents
Git clone this repo to get going right away:
git clone https://github.com/OpenLiberty/guide-kubernetes-microprofile-config.git
Copy github clone command
Copied to clipboard

Nice work! Where to next?

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like