Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

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

A guide on how to externalize configuration using MicroProfile Config and configure your microservices using Kubernetes ConfigMaps and Secrets: https://openliberty.io/guides/kubernetes-microprofile-config.html
https://github.com/openliberty/guide-kubernetes-microprofile-config

Last synced: 3 months ago
JSON representation

A guide on how to externalize configuration using MicroProfile Config and configure your microservices using Kubernetes ConfigMaps and Secrets: https://openliberty.io/guides/kubernetes-microprofile-config.html

Awesome Lists containing this project

README

        

// Copyright (c) 2018, 2024 IBM Corporation and others.
// Licensed under Creative Commons Attribution-NoDerivatives
// 4.0 International (CC BY-ND 4.0)
// https://creativecommons.org/licenses/by-nd/4.0/
//
// Contributors:
// IBM Corporation
//
:projectid: kubernetes-microprofile-config
:page-layout: guide-multipane
:page-duration: 15 minutes
:page-releasedate: 2018-10-12
:page-description: Externalize configuration and use Kubernetes ConfigMaps and Secrets to configure your microservices.
:page-tags: ['kubernetes', 'docker', 'microprofile']
:page-permalink: /guides/{projectid}
:page-related-guides: ['kubernetes-intro', 'microprofile-config', 'cdi-intro', 'docker']
:common-includes: https://raw.githubusercontent.com/OpenLiberty/guides-common/prod
:source-highlighter: prettify
:page-seo-title: Configuring Java microservices on Kubernetes with Eclipse MicroProfile Config
:page-seo-description: A getting started tutorial with examples of how to externalize configuration with Eclipse MicroProfile Config and use Kubernetes ConfigMaps and Secrets to configure your Java microservices.
:guide-author: Open Liberty
= Configuring microservices running in Kubernetes

[.hidden]
NOTE: This repository contains the guide documentation source. To view the guide in published form, view it on the https://openliberty.io/guides/{projectid}.html[Open Liberty website].

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

:minikube-ip: 192.168.99.100
:kube: Kubernetes
:system-api: http://[hostname]:31000/system/properties
:inventory-api: http://[hostname]:32000/inventory/systems
:win: WINDOWS
:mac: MAC
:linux: LINUX

// =================================================================================================
// What you'll learn
// =================================================================================================

== 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 {kube}.

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 https://openliberty.io/guides/microprofile-config.html[Configuring microservices^] guide.

Furthermore, you'll learn how to set these environment variables with ConfigMaps and Secrets. These resources are provided by {kube} 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
// =================================================================================================
[role='command']
include::{common-includes}/kube-prereq.adoc[]

// =================================================================================================
// Getting Started
// =================================================================================================
[role='command']
include::{common-includes}/gitclone.adoc[]

// =================================================================================================
// Starting and preparing your cluster for deployment
// =================================================================================================
// Static guide instruction
ifndef::cloud-hosted[]
[role='command']
include::{common-includes}/kube-start.adoc[]
endif::[]

== 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.

[role='command']
```
cd start
mvn clean package
```

Next, run the `docker build` commands to build container images for your application:
[role='command']
```
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.

// Cloud hosted guide instruction
ifdef::cloud-hosted[]
Push your images to the container registry on IBM Cloud with the following commands:

```bash
docker tag inventory:1.0-SNAPSHOT us.icr.io/$SN_ICR_NAMESPACE/inventory:1.0-SNAPSHOT
docker tag system:1.0-SNAPSHOT us.icr.io/$SN_ICR_NAMESPACE/system:1.0-SNAPSHOT
docker push us.icr.io/$SN_ICR_NAMESPACE/inventory:1.0-SNAPSHOT
docker push us.icr.io/$SN_ICR_NAMESPACE/system:1.0-SNAPSHOT
```

Update the image names and set the image pull policy to **Always** so that the images in your IBM Cloud container registry are used, and remove the **nodePort** fields so that the ports can be automatically generated:

```bash
sed -i 's=system:1.0-SNAPSHOT=us.icr.io/'"$SN_ICR_NAMESPACE"'/system:1.0-SNAPSHOT\n imagePullPolicy: Always=g' kubernetes.yaml
sed -i 's=inventory:1.0-SNAPSHOT=us.icr.io/'"$SN_ICR_NAMESPACE"'/inventory:1.0-SNAPSHOT\n imagePullPolicy: Always=g' kubernetes.yaml
sed -i 's=nodePort: 31000==g' kubernetes.yaml
sed -i 's=nodePort: 32000==g' kubernetes.yaml
```
endif::[]

Run the following command to deploy the necessary {kube} resources to serve the applications.
[role='command']
```
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.
[role='command']
```
kubectl get pods
```

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

[source, role="no_copy"]
----
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.

// Static guide instruction
ifndef::cloud-hosted[]

include::{common-includes}/os-tabs.adoc[]

[.tab_content.windows_section.mac_section]
--
The default host name for Docker Desktop is `localhost`.
--

[.tab_content.linux_section]
--
The default host name for minikube is {minikube-ip}. Otherwise it can be found using the `minikube ip` command.
--

Navigate to `{system-api}` and use the username `bob` and the password `bobpwd` to authenticate. Replace `[hostname]` with the IP address or host name of your {kube} 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`.

include::{common-includes}/os-tabs.adoc[]

[.tab_content.windows_section]
--
[role=command]
```
curl http://localhost:31000/system/properties -u bob:bobpwd
```
If the `curl` command is unavailable, then use https://www.getpostman.com/[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 https://learning.postman.com/docs/sending-requests/authorization/#basic-auth[Authorizing requests^] document for more details. Finally, click the blue `Send` button to make the request.
--

[.tab_content.mac_section]
--
[role=command]
```
curl http://localhost:31000/system/properties -u bob:bobpwd
```
--

[.tab_content.linux_section]
--
[role=command]
```
curl http://$(minikube ip):31000/system/properties -u bob:bobpwd
```
--

Similarly, navigate to `{inventory-api}/system-service`, or use the following `curl` command to add the system to your inventory.

include::{common-includes}/os-tabs.adoc[]

[.tab_content.windows_section.mac_section]
--
[role=command]
```
curl http://localhost:32000/inventory/systems/system-service
```
--

[.tab_content.linux_section]
--
[role=command]
```
curl http://$(minikube ip):32000/inventory/systems/system-service
```
--

endif::[]

// Cloud hosted guide instruction
ifdef::cloud-hosted[]
In this IBM cloud environment, you need to set up port forwarding to access the services. Open another command-line session by selecting **Terminal** > **New Terminal** from the menu of the IDE. Run the following commands to set up port forwarding to access the **system** service.
```bash
SYSTEM_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services system-service`
kubectl port-forward svc/system-service $SYSTEM_NODEPORT:9090
```

Then, open another command-line session by selecting **Terminal** > **New Terminal** from the menu of the IDE. Run the following commands to set up port forwarding to access the **inventory** service.
```bash
INVENTORY_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services inventory-service`
kubectl port-forward svc/inventory-service $INVENTORY_NODEPORT:9090
```

Then use the following commands to access your **system** microservice. The ***-u*** option is used to pass in the username ***bob*** and the password ***bobpwd***.
```bash
SYSTEM_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services system-service`
curl -s http://localhost:$SYSTEM_NODEPORT/system/properties -u bob:bobpwd | jq
```

Use the following commands to access your ***inventory*** microservice.
```bash
INVENTORY_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services inventory-service`
curl -s http://localhost:$INVENTORY_NODEPORT/inventory/systems/system-service | jq
```

When you're done trying out the microservices, press **CTRL+C** in the command line sessions where you ran the ***kubectl port-forward*** commands to stop the port forwarding.
endif::[]

== 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 [hotspot=webApplication file=0]`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.

[role="code_command hotspot file=0", subs="quotes"]
----
#Replace the `server.xml` file.#
`system/src/main/liberty/config/server.xml`
----

// File 0
server.xml
[source, xml, linenums, role='code_column hide_tags=copyright']
----
include::finish/system/src/main/liberty/config/server.xml[]
----

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

// Static guide instruction
ifndef::cloud-hosted[]
* `context.root`
* `context_root`
* `CONTEXT_ROOT`
endif::[]

// Cloud hosted guide instruction
ifdef::cloud-hosted[]
* `context.root`
* `context_root`
* `CONTEXT_ROOT`
endif::[]

== 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.

[role="code_command hotspot file=0", subs="quotes"]
----
#Replace the `SystemClient` class.#
`inventory/src/main/java/io/openliberty/guides/inventory/client/SystemClient.java`
----

SystemClient.java
[source, java, linenums, role='code_column hide_tags=copyright']
----
include::finish/inventory/src/main/java/io/openliberty/guides/inventory/client/SystemClient.java[]
----

The changes introduced here use MicroProfile Config and CDI to inject the value of the environment variables [hotspot=context-root hotspot=context-root1]`CONTEXT_ROOT`, [hotspot=system-app-username]`SYSTEM_APP_USERNAME` and [hotspot=system-app-password]`SYSTEM_APP_PASSWORD` into the [hotspot file=0]`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 [hotspot file=1]`Dockerfile` with the `ENV` command. You can also set it in your [hotspot file=0]`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.
[role='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 https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-configmap-em-[{kube} CLI documentation^].

Run the following command to display details of the ConfigMap.
[role='command']
```
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.
[role='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.
[role='command']
```
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
[source, yaml, linenums, role='code_column']
----
include::finish/kubernetes.yaml[]
----

Dockerfile
[source, text, linenums, role='code_column']
----
include::finish/system/Dockerfile[]
----

== Updating {kube} resources

Next, you will update your {kube} 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.

[role="code_command hotspot", subs="quotes"]
----
#Replace the kubernetes file.#
`kubernetes.yaml`
----

kubernetes.yaml
[source, yaml, linenums, role='code_column']
----
include::finish/kubernetes.yaml[]
----

The [hotspot=contextRoot1 hotspot=contextRoot2 file=0]`CONTEXT_ROOT`, [hotspot=sysUsername1 hotspot=sysUsername2 file=0]`SYSTEM_APP_USERNAME`, and [hotspot=sysPassword1 hotspot=sysPassword2 file=0]`SYSTEM_APP_PASSWORD` environment variables are set in the [hotspot=env1 hotspot=env2 file=0]`env` sections of [hotspot=system-container file=0]`system-container` and [hotspot=inventory-container file=0]`inventory-container`.

Using the [hotspot=valueFrom1 hotspot=valueFrom2 hotspot=valueFrom3 hotspot=valueFrom4 hotspot=valueFrom5 hotspot=valueFrom6 file=0]`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 [hotspot=configRef1 hotspot=configRef2 file=0]`configMapKeyRef` gets the value [hotspot=contextRootKey1 hotspot=contextRootKey2 file=0]`contextRoot` from the [hotspot=root1 hotspot=root2 file=0]`sys-app-root` ConfigMap. Similarly, [hotspot=secretRef1 hotspot=secretRef2 hotspot=secretRef3 hotspot=secretRef4 file=0]`secretKeyRef` gets the values [hotspot=username1 hotspot=username2 file=0]`username` and [hotspot=password1 hotspot=password2 file=0]`password` from the [hotspot=credentials1 hotspot=credentials2 hotspot=credentials3 hotspot=credentials4 file=0]`sys-app-credentials` Secret.

== Deploying your changes

// Static guide instruction
ifndef::cloud-hosted[]
Rebuild the application using `mvn clean package`.
[role='command']
```
mvn clean package
```
endif::[]

// Cloud hosted guide instruction
ifdef::cloud-hosted[]
Rebuild the application using ***mvn clean package***.
```bash
cd /home/project/guide-kubernetes-microprofile-config/start
mvn clean package
```
endif::[]

Run the `docker build` commands to rebuild container images for your application:
[role='command']
```
docker build -t system:1.0-SNAPSHOT system/.
docker build -t inventory:1.0-SNAPSHOT inventory/.
```

// Cloud hosted guide instruction
ifdef::cloud-hosted[]

Push your updated images to the container registry on IBM Cloud with the following commands:

```bash
docker tag inventory:1.0-SNAPSHOT us.icr.io/$SN_ICR_NAMESPACE/inventory:1.0-SNAPSHOT
docker tag system:1.0-SNAPSHOT us.icr.io/$SN_ICR_NAMESPACE/system:1.0-SNAPSHOT
docker push us.icr.io/$SN_ICR_NAMESPACE/inventory:1.0-SNAPSHOT
docker push us.icr.io/$SN_ICR_NAMESPACE/system:1.0-SNAPSHOT
```

Update the image names and set the image pull policy to **Always** so that the images in your IBM Cloud container registry are used, and remove the **nodePort** fields so that the ports can be automatically generated:

```bash
sed -i 's=system:1.0-SNAPSHOT=us.icr.io/'"$SN_ICR_NAMESPACE"'/system:1.0-SNAPSHOT\n imagePullPolicy: Always=g' kubernetes.yaml
sed -i 's=inventory:1.0-SNAPSHOT=us.icr.io/'"$SN_ICR_NAMESPACE"'/inventory:1.0-SNAPSHOT\n imagePullPolicy: Always=g' kubernetes.yaml
sed -i 's=nodePort: 31000==g' kubernetes.yaml
sed -i 's=nodePort: 32000==g' kubernetes.yaml
```
endif::[]

Run the following command to deploy your changes to the {kube} cluster.
[role='command']
```
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.
[role='command']
```
kubectl get pods
```

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

// Static guide instruction
ifndef::cloud-hosted[]

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:

include::{common-includes}/os-tabs.adoc[]

[.tab_content.windows_section]
--
[role=command]
```
curl http://localhost:31000/dev/system/properties -u alice:wonderland
```
If the `curl` command is unavailable, then use https://www.getpostman.com/[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.
--

[.tab_content.mac_section]
--
[role=command]
```
curl http://localhost:31000/dev/system/properties -u alice:wonderland
```
--

[.tab_content.linux_section]
--
[role=command]
```
curl http://$(minikube ip):31000/dev/system/properties -u alice:wonderland
```
--

endif::[]

// Cloud hosted guide instruction
ifdef::cloud-hosted[]
Set up port forwarding to the new services.

Run the following commands to set up port forwarding to access the ***system*** service.

```bash
SYSTEM_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services system-service`
kubectl port-forward svc/system-service $SYSTEM_NODEPORT:9090
```

Then, run the following commands to set up port forwarding to access the **inventory** service.

```bash
INVENTORY_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services inventory-service`
kubectl port-forward svc/inventory-service $INVENTORY_NODEPORT:9090
```

You now need to use the new username, ***alice***, and the new password, ***wonderland***, to log in. Access your application with the following commands:

```bash
SYSTEM_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services system-service`
curl -s http://localhost:$SYSTEM_NODEPORT/dev/system/properties -u alice:wonderland | jq
```
endif::[]

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

// Static guide instruction
ifndef::cloud-hosted[]
Verify that `{inventory-api}/system-service` is working as intended. If it is not working, then check the configuration of the credentials.
endif::[]

// Cloud hosted guide instruction
ifdef::cloud-hosted[]
Verify the inventory service is working as intended by using the following commands:

```bash
INVENTORY_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services inventory-service`
curl -s http://localhost:$INVENTORY_NODEPORT/inventory/systems/system-service | jq
```

If it is not working, then check the configuration of the credentials.
endif::[]

== Testing the microservices

// Static guide instruction
ifndef::cloud-hosted[]

include::{common-includes}/os-tabs.adoc[]

[.tab_content.windows_section.mac_section]
--
Run the integration tests:
[role='command']
```
mvn failsafe:integration-test -Dsystem.context.root=/dev
```
--

[.tab_content.linux_section]
--
Run the integration tests against a cluster running at Minikube's IP address:
[role='command']
```
mvn failsafe:integration-test -Dsystem.context.root=/dev -Dsystem.service.root=$(minikube ip):31000 -Dinventory.service.root=$(minikube ip):32000

```
--

endif::[]

// Cloud hosted guide instruction
ifdef::cloud-hosted[]
Update the ***pom.xml*** files so that the ***system.service.root*** and ***inventory.service.root*** properties have the correct ports to access the **system** and **inventory** services.

```bash
cd /home/project/guide-kubernetes-microprofile-config/start
SYSTEM_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services system-service`
INVENTORY_NODEPORT=`kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services inventory-service`
sed -i 's=localhost:31000='"localhost:$SYSTEM_NODEPORT"'=g' inventory/pom.xml
sed -i 's=localhost:32000='"localhost:$INVENTORY_NODEPORT"'=g' inventory/pom.xml
sed -i 's=localhost:31000='"localhost:$SYSTEM_NODEPORT"'=g' system/pom.xml
```

Run the integration tests by using the following command:

```bash
mvn failsafe:integration-test \
-Dsystem.service.root=localhost:$SYSTEM_NODEPORT \
-Dsystem.context.root=/dev \
-Dinventory.service.root=localhost:$INVENTORY_NODEPORT
```
endif::[]

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.

[source, role="no_copy"]
----
-------------------------------------------------------
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
----

[source, role="no_copy"]
----
-------------------------------------------------------
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

// Cloud-hosted guide instruction
ifdef::cloud-hosted[]
Press **CTRL+C** in the command-line sessions where you ran ***kubectl port-forward*** to stop the port forwarding.
endif::[]

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

[role='command']
```
kubectl delete -f kubernetes.yaml
kubectl delete configmap sys-app-root
kubectl delete secret sys-app-credentials
```

// Static guide instruction
ifndef::cloud-hosted[]
[role='command']
include::{common-includes}/kube-minikube-teardown.adoc[]
endif::[]

// =================================================================================================
// finish
// =================================================================================================

== 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 {kube} cluster.

// Include the below from the guides-common repo to tell users how they can contribute to the guide

include::{common-includes}/attribution.adoc[subs="attributes"]