Configuring microservices running in Kubernetes

duration 15 minutes

Prerequisites:

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.

MicroProfile Config provides useful annotations that you can use to inject configured values into your code. These values can come from any configuration source, such as environment variables. Using environment variables allows for easier deployment to different environments. 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.

Additional prerequisites

Before you begin, you need a containerization software for building containers. Kubernetes supports various container runtimes. You will use Docker in this guide. For Docker installation instructions, refer to the official Docker documentation.

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

Complete the setup for your operating system:

After you complete the Docker setup instructions for your operating system, ensure that Kubernetes (not Swarm) is selected as the orchestrator in Docker Preferences.

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

Complete the setup for your operating system:

After you complete the Docker setup instructions for your operating system, ensure that Kubernetes (not Swarm) is selected as the orchestrator in Docker Preferences.

You will use Minikube as a single-node Kubernetes cluster that runs locally in a virtual machine. Make sure you have kubectl installed. If you need to install kubectl, see the kubectl installation instructions. For Minikube installation instructions, see the Minikube documentation.

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.

Before you begin, make sure you have all the necessary prerequisites.

Starting and preparing your cluster for deployment

Start your Kubernetes cluster.

Start your Docker Desktop environment.

Ensure that Kubernetes is running on Docker Desktop and that the context is set to docker-desktop.

Run the following command from a command-line session:

minikube start

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

kubectl get nodes

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

You do not need to do any other step.

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 system and inventory. The system microservice returns the JVM system properties of the running container. The inventory microservice adds the properties from the system microservice to the inventory. This 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.

cd start
mvn clean package

Next, run the docker build commands to build container images for your application:

docker build -t system:1.0-SNAPSHOT system/.
docker build -t inventory:1.0-SNAPSHOT inventory/.

The -t flag in the docker build command allows the Docker image to be labeled (tagged) in the name[:tag] format. The tag for an image describes the specific image version. If the optional [:tag] tag is not specified, the latest tag is created by default.

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
system-deployment-6bd97d9bf6-6d2cj     1/1       Running   0          34s
inventory-deployment-645767664f-7gnxf  1/1       Running   0          34s

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

The default host name for Docker Desktop is localhost.

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

Navigate to http://[hostname]:31000/system/properties and use the username bob and the password bobpwd to authenticate. Replace [hostname] with the IP address or host name of your Kubernetes cluster. Open your browser’s developer console and examine the response headers.

You can also run the curl command to make requests to your microservices. Use the -u option to pass in the username bob and the password bobpwd.

curl http://localhost:31000/system/properties -u bob:bobpwd

If the curl command is unavailable, then use Postman. Postman enables you to make requests using a graphical interface. To make a request with Postman, enter http://localhost:31000/system/properties into the URL bar. Next, set the Authorization with Basic Auth type. Set the Username field to bob and the Password field to bobpwd. Read the Authorizing requests document for more details. Finally, click the blue Send button to make the request.

curl http://localhost:31000/system/properties -u bob:bobpwd
curl http://$(minikube ip):31000/system/properties -u bob:bobpwd

Similarly, navigate to http://[hostname]:32000/inventory/systems/system-service, or use the following curl command to add the system to your inventory.

curl http://localhost:32000/inventory/systems/system-service
curl http://$(minikube ip):32000/inventory/systems/system-service

Modifying system microservice

The system service is hardcoded to use a single forward slash as the context root. The context root is set in the webApplication element, where the contextRoot attribute is specified as "/". You’ll make the value of the contextRoot attribute configurable by implementing it as a variable.

Replace the server.xml file.
system/src/main/liberty/config/server.xml

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.1</feature>
 5    <feature>jsonb-3.0</feature>
 6    <feature>cdi-4.0</feature>
 7    <feature>jsonp-2.1</feature>
 8    <feature>mpConfig-3.1</feature>
 9    <feature>mpHealth-4.0</feature>
10    <feature>appSecurity-5.0</feature>
11  </featureManager>
12
13  <variable name="http.port" defaultValue="9090"/>
14  <variable name="https.port" defaultValue="9453"/>
15  <variable name="system.app.username" defaultValue="bob"/>
16  <variable name="system.app.password" defaultValue="bobpwd"/>
17  <!-- tag::context.root[] -->
18  <variable name="context.root" defaultValue="/"/>
19  <!-- end::context.root[] -->
20
21  <httpEndpoint host="*" httpPort="${http.port}" 
22    httpsPort="${https.port}" id="defaultHttpEndpoint" />
23
24  <!-- tag::webApplication[] -->
25  <webApplication location="guide-kubernetes-microprofile-config-system.war" contextRoot="${context.root}"/>
26  <!-- end::webApplication[] -->
27
28  <basicRegistry id="basic" realm="BasicRegistry">
29    <user name="${system.app.username}" password="${system.app.password}" />
30  </basicRegistry>
31
32</server>

The contextRoot attribute in the webApplication element now gets its value from the context.root variable. To find a value for the context.root variable, Open Liberty looks for the following environment variables, in order:

  • context.root

  • context_root

  • CONTEXT_ROOT

Modifying inventory microservice

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

Replace the SystemClient class.
inventory/src/main/java/io/openliberty/guides/inventory/client/SystemClient.java

SystemClient.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2024 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package io.openliberty.guides.inventory.client;
 13
 14import java.net.URI;
 15import java.util.Base64;
 16import java.util.Properties;
 17
 18import jakarta.enterprise.context.RequestScoped;
 19import jakarta.inject.Inject;
 20import jakarta.ws.rs.client.Client;
 21import jakarta.ws.rs.client.ClientBuilder;
 22import jakarta.ws.rs.client.Invocation.Builder;
 23import jakarta.ws.rs.core.HttpHeaders;
 24import jakarta.ws.rs.core.MediaType;
 25import jakarta.ws.rs.core.Response;
 26import jakarta.ws.rs.core.Response.Status;
 27
 28import org.eclipse.microprofile.config.inject.ConfigProperty;
 29
 30@RequestScoped
 31public class SystemClient {
 32
 33  // Constants for building URI to the system service.
 34  private final String SYSTEM_PROPERTIES = "/system/properties";
 35  private final String PROTOCOL = "http";
 36
 37  // tag::context-root[]
 38  @Inject
 39  @ConfigProperty(name = "CONTEXT_ROOT", defaultValue = "")
 40  String CONTEXT_ROOT;
 41  // end::context-root[]
 42
 43  @Inject
 44  @ConfigProperty(name = "http.port")
 45  String HTTP_PORT;
 46
 47  // Basic Auth Credentials
 48  // tag::credentials[]
 49  // tag::system-app-username[]
 50  @Inject
 51  @ConfigProperty(name = "SYSTEM_APP_USERNAME")
 52  private String username;
 53  // end::system-app-username[]
 54
 55  // tag::system-app-password[]
 56  @Inject
 57  @ConfigProperty(name = "SYSTEM_APP_PASSWORD")
 58  private String password;
 59  // end::system-app-password[]
 60  // end::credentials[]
 61
 62  // Wrapper function that gets properties
 63  public Properties getProperties(String hostname) {
 64    Properties properties = null;
 65    Client client = ClientBuilder.newClient();
 66    try {
 67        Builder builder = getBuilder(hostname, client);
 68        properties = getPropertiesHelper(builder);
 69    } catch (Exception e) {
 70        System.err.println(
 71        "Exception thrown while getting properties: " + e.getMessage());
 72    } finally {
 73        client.close();
 74    }
 75    return properties;
 76  }
 77
 78  // Method that creates the client builder
 79  private Builder getBuilder(String hostname, Client client) throws Exception {
 80    // tag::context-root1[]
 81    URI uri = new URI(
 82                  PROTOCOL, null, hostname, Integer.valueOf(HTTP_PORT),
 83                  CONTEXT_ROOT + SYSTEM_PROPERTIES, null, null);
 84    // end::context-root1[]
 85    String urlString = uri.toString();
 86    Builder builder = client.target(urlString).request();
 87    builder.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
 88           .header(HttpHeaders.AUTHORIZATION, getAuthHeader());
 89    return builder;
 90  }
 91
 92  // Helper method that processes the request
 93  private Properties getPropertiesHelper(Builder builder) throws Exception {
 94    Response response = builder.get();
 95    if (response.getStatus() == Status.OK.getStatusCode()) {
 96        return response.readEntity(Properties.class);
 97    } else {
 98        System.err.println("Response Status is not OK.");
 99        return null;
100    }
101  }
102
103  private String getAuthHeader() {
104    String usernamePassword = username + ":" + password;
105    String encoded = Base64.getEncoder().encodeToString(usernamePassword.getBytes());
106    return "Basic " + encoded;
107  }
108}

The changes introduced here use MicroProfile Config and CDI to inject the value of the environment variables CONTEXT_ROOT, SYSTEM_APP_USERNAME and SYSTEM_APP_PASSWORD into the SystemClient class.

Creating a ConfigMap and Secret

Several options exist 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 that you want to set for a specific container. With these options in mind, you’re going to use a ConfigMap and Secret to set these values. These are resources provided by Kubernetes as a way to provide configuration values to your containers. A benefit is that they can be reused 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 app name with the following kubectl command.

kubectl create configmap sys-app-root --from-literal contextRoot=/dev

This command deploys a ConfigMap named sys-app-root to your cluster. It has a key called contextRoot with a value of /dev. 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.

Run the following command to display details of the ConfigMap.

kubectl describe configmaps sys-app-root

Create a Secret to configure the new credentials that inventory uses to authenticate against system with the following kubectl command.

kubectl create secret generic sys-app-credentials --from-literal username=alice --from-literal password=wonderland

This command looks similar to the command to create a ConfigMap, but one difference is the word generic. This word creates a Secret that doesn’t store information in any specialized way. Different types of secrets are available, such as secrets to store Docker credentials and secrets to store public and private key pairs.

Run the following command to display details of the Secret.

kubectl describe secrets/sys-app-credentials

A Secret is similar to a ConfigMap. A key difference is that a Secret is used for confidential information such as credentials. One of the main differences is that you must 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. Encryption is not required for the application to run.

kubernetes.yaml

  1apiVersion: apps/v1
  2kind: Deployment
  3metadata:
  4  name: system-deployment
  5  labels:
  6    app: system
  7spec:
  8  selector:
  9    matchLabels:
 10      app: system
 11  strategy:
 12    type: RollingUpdate
 13    rollingUpdate:
 14      maxUnavailable: 1
 15      maxSurge: 1
 16  template:
 17    metadata:
 18      labels:
 19        app: system
 20    spec:
 21      containers:
 22      # tag::system-container[]
 23      - name: system-container
 24        image: system:1.0-SNAPSHOT
 25        ports:
 26        - containerPort: 9090
 27        # system probes
 28        startupProbe:
 29          httpGet:
 30            path: /health/started
 31            port: 9090
 32        livenessProbe:
 33          httpGet:
 34            path: /health/live
 35            port: 9090
 36          initialDelaySeconds: 60
 37          periodSeconds: 10
 38          timeoutSeconds: 3
 39          failureThreshold: 1
 40        readinessProbe:
 41           httpGet:
 42            path: /health/ready
 43            port: 9090
 44           initialDelaySeconds: 30
 45           periodSeconds: 10
 46           timeoutSeconds: 3
 47           failureThreshold: 1
 48        # Set the environment variables
 49        # tag::env1[]
 50        env:
 51        # end::env1[]
 52        # tag::contextRoot1[]
 53        - name: CONTEXT_ROOT
 54          # tag::valueFrom1[]
 55          valueFrom:
 56          # end::valueFrom1[]
 57            # tag::configRef1[]
 58            configMapKeyRef:
 59              # tag::root1[]
 60              name: sys-app-root
 61              # end::root1[]
 62              # tag::contextRootKey1[]
 63              key: contextRoot
 64              # end::contextRootKey1[]
 65            # end::configRef1[]
 66        # end::contextRoot1[]
 67        # tag::sysUsername1[]
 68        - name: SYSTEM_APP_USERNAME
 69          # tag::valueFrom2[]
 70          valueFrom:
 71          # end::valueFrom2[]
 72            # tag::secretRef1[]
 73            secretKeyRef:
 74              # tag::credentials1[]
 75              name: sys-app-credentials
 76              # end::credentials1[]
 77              # tag::username1[]
 78              key: username
 79              # end::username1[]
 80            # end::secretRef1[]
 81        # end::sysUsername1[]
 82        # tag::sysPassword1[]
 83        - name: SYSTEM_APP_PASSWORD
 84          # tag::valueFrom3[]
 85          valueFrom:
 86          # end::valueFrom3[]
 87            # tag::secretRef2[]
 88            secretKeyRef:
 89              # tag::credentials2[]
 90              name: sys-app-credentials
 91              # end::credentials2[]
 92              # tag::password1[]
 93              key: password
 94              # end::password1[]
 95            # end::secretRef2[]
 96        # end::sysPassword1[]
 97      # end::system-container[]
 98---
 99apiVersion: apps/v1
100kind: Deployment
101metadata:
102  name: inventory-deployment
103  labels:
104    app: inventory
105spec:
106  selector:
107    matchLabels:
108      app: inventory
109  strategy:
110    type: RollingUpdate
111    rollingUpdate:
112      maxUnavailable: 1
113      maxSurge: 1
114  template:
115    metadata:
116      labels:
117        app: inventory
118    spec:
119      containers:
120      # tag::inventory-container[]
121      - name: inventory-container
122        image: inventory:1.0-SNAPSHOT
123        ports:
124        - containerPort: 9090
125        # inventory probes
126        startupProbe:
127          httpGet:
128            path: /health/started
129            port: 9090
130        livenessProbe:
131          httpGet:
132            path: /health/live
133            port: 9090
134          initialDelaySeconds: 60
135          periodSeconds: 10
136          timeoutSeconds: 3
137          failureThreshold: 1
138        readinessProbe:
139          httpGet:
140            path: /health/ready
141            port: 9090
142          initialDelaySeconds: 30
143          periodSeconds: 10
144          timeoutSeconds: 3
145          failureThreshold: 1
146        # Set the environment variables
147        # tag::env2[]
148        env:
149        # end::env2[]
150        - name: SYS_APP_HOSTNAME
151          value: system-service
152        # tag::contextRoot2[]
153        - name: CONTEXT_ROOT
154          # tag::valueFrom4[]
155          valueFrom:
156          # end::valueFrom4[]
157            # tag::configRef2[]
158            configMapKeyRef:
159              # tag::root2[]
160              name: sys-app-root
161              # end::root2[]
162              # tag::contextRootKey2[]
163              key: contextRoot
164              # end::contextRootKey2[]
165            # end::configRef2[]
166        # end::contextRoot2[]
167        # tag::sysUsername2[]
168        - name: SYSTEM_APP_USERNAME
169          # tag::valueFrom5[]
170          valueFrom:
171          # end::valueFrom5[]
172            # tag::secretRef3[]
173            secretKeyRef:
174              # tag::credentials3[]
175              name: sys-app-credentials
176              # end::credentials3[]
177              # tag::username2[]
178              key: username
179              # end::username2[]
180            # end::secretRef3[]
181        # end::sysUsername2[]
182        # tag::sysPassword2[]
183        - name: SYSTEM_APP_PASSWORD
184          # tag::valueFrom6[]
185          valueFrom:
186          # end::valueFrom6[]
187            # tag::secretRef4[]
188            secretKeyRef:
189              # tag::credentials4[]
190              name: sys-app-credentials
191              # end::credentials4[]
192              # tag::password2[]
193              key: password
194              # end::password2[]
195            # end::secretRef4[]
196        # end::sysPassword2[]
197      # end::inventory-container[]
198---
199apiVersion: v1
200kind: Service
201metadata:
202  name: system-service
203spec:
204  type: NodePort
205  selector:
206    app: system
207  ports:
208  - protocol: TCP
209    port: 9090
210    targetPort: 9090
211    nodePort: 31000
212---
213apiVersion: v1
214kind: Service
215metadata:
216  name: inventory-service
217spec:
218  type: NodePort
219  selector:
220    app: inventory
221  ports:
222  - protocol: TCP
223    port: 9090
224    targetPort: 9090
225    nodePort: 32000

Dockerfile

 1FROM icr.io/appcafe/open-liberty:kernel-slim-java11-openj9-ubi
 2
 3ARG VERSION=1.0
 4ARG REVISION=SNAPSHOT
 5LABEL \
 6  org.opencontainers.image.authors="My Name" \
 7  org.opencontainers.image.vendor="Open Liberty" \
 8  org.opencontainers.image.url="local" \
 9  org.opencontainers.image.source="https://github.com/OpenLiberty/guide-kubernetes-microprofile-config" \
10  org.opencontainers.image.version="$VERSION" \
11  org.opencontainers.image.revision="$REVISION" \
12  vendor="Open Liberty" \
13  name="system" \
14  version="$VERSION-$REVISION" \
15  summary="The system microservice from the Configuring microservices running in Kubernetes guide" \
16  description="This image contains the system microservice running with the Open Liberty runtime."
17COPY --chown=1001:0 src/main/liberty/config /config/
18RUN features.sh
19COPY --chown=1001:0 target/guide-kubernetes-microprofile-config-system.war /config/apps
20
21RUN configure.sh

Updating Kubernetes resources

Next, you will update your Kubernetes deployments to set the environment variables in your containers based on the values that are configured in the ConfigMap and Secret that you created previously.

Replace the kubernetes file.
kubernetes.yaml

kubernetes.yaml

  1apiVersion: apps/v1
  2kind: Deployment
  3metadata:
  4  name: system-deployment
  5  labels:
  6    app: system
  7spec:
  8  selector:
  9    matchLabels:
 10      app: system
 11  strategy:
 12    type: RollingUpdate
 13    rollingUpdate:
 14      maxUnavailable: 1
 15      maxSurge: 1
 16  template:
 17    metadata:
 18      labels:
 19        app: system
 20    spec:
 21      containers:
 22      # tag::system-container[]
 23      - name: system-container
 24        image: system:1.0-SNAPSHOT
 25        ports:
 26        - containerPort: 9090
 27        # system probes
 28        startupProbe:
 29          httpGet:
 30            path: /health/started
 31            port: 9090
 32        livenessProbe:
 33          httpGet:
 34            path: /health/live
 35            port: 9090
 36          initialDelaySeconds: 60
 37          periodSeconds: 10
 38          timeoutSeconds: 3
 39          failureThreshold: 1
 40        readinessProbe:
 41           httpGet:
 42            path: /health/ready
 43            port: 9090
 44           initialDelaySeconds: 30
 45           periodSeconds: 10
 46           timeoutSeconds: 3
 47           failureThreshold: 1
 48        # Set the environment variables
 49        # tag::env1[]
 50        env:
 51        # end::env1[]
 52        # tag::contextRoot1[]
 53        - name: CONTEXT_ROOT
 54          # tag::valueFrom1[]
 55          valueFrom:
 56          # end::valueFrom1[]
 57            # tag::configRef1[]
 58            configMapKeyRef:
 59              # tag::root1[]
 60              name: sys-app-root
 61              # end::root1[]
 62              # tag::contextRootKey1[]
 63              key: contextRoot
 64              # end::contextRootKey1[]
 65            # end::configRef1[]
 66        # end::contextRoot1[]
 67        # tag::sysUsername1[]
 68        - name: SYSTEM_APP_USERNAME
 69          # tag::valueFrom2[]
 70          valueFrom:
 71          # end::valueFrom2[]
 72            # tag::secretRef1[]
 73            secretKeyRef:
 74              # tag::credentials1[]
 75              name: sys-app-credentials
 76              # end::credentials1[]
 77              # tag::username1[]
 78              key: username
 79              # end::username1[]
 80            # end::secretRef1[]
 81        # end::sysUsername1[]
 82        # tag::sysPassword1[]
 83        - name: SYSTEM_APP_PASSWORD
 84          # tag::valueFrom3[]
 85          valueFrom:
 86          # end::valueFrom3[]
 87            # tag::secretRef2[]
 88            secretKeyRef:
 89              # tag::credentials2[]
 90              name: sys-app-credentials
 91              # end::credentials2[]
 92              # tag::password1[]
 93              key: password
 94              # end::password1[]
 95            # end::secretRef2[]
 96        # end::sysPassword1[]
 97      # end::system-container[]
 98---
 99apiVersion: apps/v1
100kind: Deployment
101metadata:
102  name: inventory-deployment
103  labels:
104    app: inventory
105spec:
106  selector:
107    matchLabels:
108      app: inventory
109  strategy:
110    type: RollingUpdate
111    rollingUpdate:
112      maxUnavailable: 1
113      maxSurge: 1
114  template:
115    metadata:
116      labels:
117        app: inventory
118    spec:
119      containers:
120      # tag::inventory-container[]
121      - name: inventory-container
122        image: inventory:1.0-SNAPSHOT
123        ports:
124        - containerPort: 9090
125        # inventory probes
126        startupProbe:
127          httpGet:
128            path: /health/started
129            port: 9090
130        livenessProbe:
131          httpGet:
132            path: /health/live
133            port: 9090
134          initialDelaySeconds: 60
135          periodSeconds: 10
136          timeoutSeconds: 3
137          failureThreshold: 1
138        readinessProbe:
139          httpGet:
140            path: /health/ready
141            port: 9090
142          initialDelaySeconds: 30
143          periodSeconds: 10
144          timeoutSeconds: 3
145          failureThreshold: 1
146        # Set the environment variables
147        # tag::env2[]
148        env:
149        # end::env2[]
150        - name: SYS_APP_HOSTNAME
151          value: system-service
152        # tag::contextRoot2[]
153        - name: CONTEXT_ROOT
154          # tag::valueFrom4[]
155          valueFrom:
156          # end::valueFrom4[]
157            # tag::configRef2[]
158            configMapKeyRef:
159              # tag::root2[]
160              name: sys-app-root
161              # end::root2[]
162              # tag::contextRootKey2[]
163              key: contextRoot
164              # end::contextRootKey2[]
165            # end::configRef2[]
166        # end::contextRoot2[]
167        # tag::sysUsername2[]
168        - name: SYSTEM_APP_USERNAME
169          # tag::valueFrom5[]
170          valueFrom:
171          # end::valueFrom5[]
172            # tag::secretRef3[]
173            secretKeyRef:
174              # tag::credentials3[]
175              name: sys-app-credentials
176              # end::credentials3[]
177              # tag::username2[]
178              key: username
179              # end::username2[]
180            # end::secretRef3[]
181        # end::sysUsername2[]
182        # tag::sysPassword2[]
183        - name: SYSTEM_APP_PASSWORD
184          # tag::valueFrom6[]
185          valueFrom:
186          # end::valueFrom6[]
187            # tag::secretRef4[]
188            secretKeyRef:
189              # tag::credentials4[]
190              name: sys-app-credentials
191              # end::credentials4[]
192              # tag::password2[]
193              key: password
194              # end::password2[]
195            # end::secretRef4[]
196        # end::sysPassword2[]
197      # end::inventory-container[]
198---
199apiVersion: v1
200kind: Service
201metadata:
202  name: system-service
203spec:
204  type: NodePort
205  selector:
206    app: system
207  ports:
208  - protocol: TCP
209    port: 9090
210    targetPort: 9090
211    nodePort: 31000
212---
213apiVersion: v1
214kind: Service
215metadata:
216  name: inventory-service
217spec:
218  type: NodePort
219  selector:
220    app: inventory
221  ports:
222  - protocol: TCP
223    port: 9090
224    targetPort: 9090
225    nodePort: 32000

The CONTEXT_ROOT, SYSTEM_APP_USERNAME, and SYSTEM_APP_PASSWORD environment variables are set in the env sections of system-container and inventory-container.

Using the valueFrom field, you can specify the value of an environment variable from various sources. These sources include a ConfigMap, a Secret, and information about the cluster. In this example configMapKeyRef gets the value contextRoot from the sys-app-root ConfigMap. Similarly, secretKeyRef gets the values username and password from the sys-app-credentials Secret.

Deploying your changes

Rebuild the application using mvn clean package.

mvn clean package

Run the docker build commands to rebuild container images for your application:

docker build -t system:1.0-SNAPSHOT system/.
docker build -t inventory:1.0-SNAPSHOT inventory/.

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

kubectl replace --force -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.

Your application will now be available at the http://[hostname]:31000/dev/system/properties URL. You now need to use the new username, alice, and the new password, wonderland, to log in. Alternatively, you can run the following command:

curl http://localhost:31000/dev/system/properties -u alice:wonderland

If the curl command is unavailable, then use Postman. To make a request with Postman, enter http://localhost:31000/dev/system/properties into the URL bar. Next, set the Authorization with Basic Auth type, and set the Username field to alice and Password field to wonderland. Finally, click the blue Send button to make the request.

curl http://localhost:31000/dev/system/properties -u alice:wonderland
curl http://$(minikube ip):31000/dev/system/properties -u alice:wonderland

Notice that the URL you are using to reach the application now has /dev as the context root.

Verify that http://[hostname]:32000/inventory/systems/system-service is working as intended. If it is not working, then check the configuration of the credentials.

Testing the microservices

Run the integration tests:

mvn failsafe:integration-test -Dsystem.context.root=/dev

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

mvn failsafe:integration-test -Dsystem.context.root=/dev -Dsystem.service.root=$(minikube ip):31000 -Dinventory.service.root=$(minikube ip):32000

The tests for inventory verify that the service can communicate with system using the configured credentials. If the credentials are misconfigured, then the inventory test fails, so the inventory test indirectly verifies that 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.system.SystemEndpointIT
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.706 s - in it.io.openliberty.guides.system.SystemEndpointIT

Results:

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.inventory.InventoryEndpointIT
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.696 s - in it.io.openliberty.guides.inventory.InventoryEndpointIT

Results:

Tests run: 3, 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 sys-app-root
kubectl delete secret sys-app-credentials

Nothing more needs to be done for Docker Desktop.

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

Copy file contents
Copied to clipboard

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

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

Like Open Liberty? Star our repo on GitHub.

Star