[JAVA] MicroProfile Config operation verification in Azure Web App for Containers environment

1. MicroProfile Config operation verification in Azure Web App for Containers environment

The application separates the connection information (DB and external HTTP endpoint) for cooperation with the external system and the difference in environment settings such as the development environment, test environment, and production environment from the program source code, and externally. You can easily switch the connection destination and settings by writing to a setting file. Also, by writing to an external configuration file, you do not need to edit or build the application source code to switch the connection destination, and you can use the same source code and execution library.

In order to build a cloud-native application, it is very important to remove the setting information. "Reference: The Twelve Factors III. Settings: Store settings in environment variables"

Twelve-Factor requires that the settings be strictly separated from the code.

By using MicroProfile Config, you can get the setting information from various places such as the following. These configuration locations are called ConfigSources, and if the same property is defined in multiple ConfigSources, apply the policy and specify which values are valid.

Also, in some situations, you may want to switch some data sources dynamically. And for the changed value, it is necessary to use the updated contents programmatically without restarting the application. To meet these needs, MicroProfile Config makes the configured values available immediately after the change.

About the implementation of MicroProfile Config

Only Microprofile Config is API is specified and no implementation is included. .. MicroProfile Config implementations are provided individually by each MicroProfile implementation provider.

Overview of MicroProfile Config

MicroProfile Config consists of a few APIs.

List of MicroProfile Config API 1.4

ConfigSource Priority

Config consists of the information collected from the registered org.eclipse.microprofile.config.spi.ConfigSource. These ConfigSources are sorted in order. This allows you to override less important settings from the outside.

By default, there are three default ConfigSources.

Default values can be specified in the file when the application is packaged, and values can be overridden later for each deployment. *** "The higher the value, the higher the priority." ***

Setting information acquisition example

The MicroProfile Config specification provides two ways to read the settings.

1. Acquisition of programmatic setting information

The following is a sample to programmatically acquire a Config instance and acquire configuration information.

public class  MyAppWithGetConfigFromProgram {

    public Response invokeMicroserviceWithConfig() {
        //Get a Config instance
        Config config = ConfigProvider.getConfig();
        //Get the URL of microservice A
        String microserviceA = config.getValue("URL_OF_MICROSERVICE_A", String.class);
        //Call microservice A
        return invokeMicroservice(microserviceA);
    }
}

To get the configuration information, first [Config](https://javadoc.io/doc/org.eclipse.microprofile.config/microprofile-config-api/latest/org/eclipse/microprofile/config/Config .html) You have to get an instance. To get a Config instance programmatically, [ConfigProvider # getConfig ()](https://javadoc.io/static/org.eclipse.microprofile.config/microprofile-config-api/1.4/org/eclipse/ You can get it by calling microprofile / config / ConfigProvider.html # getConfig--). (An instance of the Config class is created and then registered with the context class loader.)

2. Acquisition of setting information using annotations (recommended)

The following is a sample to get the Config instance using annotation and get the setting information with @ConfigProperty.

@ApplicationScoped
public class MyAppWithGetConfigFromAnnotation {

    @Inject
    private Config config;

    //The property myprj.some.url must exist in one of the configsources, otherwise a
    //DeploymentException will be thrown.
    @Inject
    @ConfigProperty(name="myprj.some.url")
    private String someUrl;

    //The following code injects an Optional value of myprj.some.port property.
    //Contrary to natively injecting the configured value, this will not lead to a
    //DeploymentException if the value is missing.
    @Inject
    @ConfigProperty(name="myprj.some.port")
    private Optional<Integer> somePort;
}

MicroProfile Config sample application

1. Create a MicroProfile Config sample project

Go to the MicroProfile Starter (https://start.microprofile.io/) and create a MicroProfile project.

You can download the MPConfigSample.zip file by clicking the (DOWNLOAD) link. When the file is expanded, the following file / directory structure is automatically generated.

.
├── pom.xml
├── readme.md
└── src
    └── main
        ├── java
        │   └── com
        │       └── yoshio3
        │           └── MPConfigSample
        │               ├── HelloController.java
        │               ├── MPConfigSampleRestApplication.java
        │               └── config
        │                   └── ConfigTestController.java
        ├── resources
        │   └── META-INF
        │       └── microprofile-config.properties
        └── webapp
            ├── WEB-INF
            │   └── beans.xml
            └── index.html
11 directories, 8 files

And the sample code of MicroProfile Config is described in ConfigTestController.java as follows.

package com.yoshio3.MPConfigSample.config;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/config")
@RequestScoped
public class ConfigTestController {

    @Inject
    @ConfigProperty(name = "injected.value")
    private String injectedValue;

    @Path("/injected")
    @GET
    public String getInjectedConfigValue() {
        return "Config value as Injected by CDI " + injectedValue;
    }

    @Path("/lookup")
    @GET
    public String getLookupConfigValue() {
        Config config = ConfigProvider.getConfig();
        String value = config.getValue("value", String.class);
        return "Config value from ConfigProvider " + value;
    }
}

The above code is a simple code that returns the value described in the property as an HTTP response. If you call it with the HTTP GET method as shown below, the character string described in the return statement will be returned.

$ curl -X GET http://localhost:8080/data/config/injected
$ curl -X GET http://localhost:8080/data/config/lookup

The actual settings are described in the microprofile-config.properties file under the META_INF directory.

#Property file location
└── src
    └── main
        ├── resources
        │   └── META-INF
        │       └── microprofile-config.properties

The following properties are set by default.

#The value set in the properties file
injected.value=Injected value
value=lookup value

2. Build and run the sample project

To check the operation of MicroProfile Config, build the project and start the application.

#Build the project
$ mvn clean package

#Run application
$ java -jar target/MPConfigSample-microbundle.jar 

......
Payara Micro URLs:
http://192.168.100.7:8080/

'ROOT' REST Endpoints:
GET     /data/application.wadl
GET     /data/config/injected
GET     /data/config/lookup
GET     /data/hello
GET     /openapi/
GET     /openapi/application.wadl
]]
[2020-03-10T22:19:06.610+0900] [] [information] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1583846346610] [levelValue: 800] Payara Micro  5.194 #badassmicrofish (build 327) ready in 32,755 (ms)
[2020-03-10T22:19:33.646+0900] [] [information] [] [javax.enterprise.system.container.web.com.sun.web.security] [tid: _ThreadID=29 _ThreadName=http-thread-pool::http-listener(1)] [timeMillis: 1583846373646] [levelValue: 800] Context path from ServletContext:  differs from path from bundle: /

After the application starts as described above, execute the curl command to check the operation. If it is working properly, the string of the settings (Injected value, value) obtained from the property file will be displayed as shown below.

#Call to endpoint implemented with annotation
$ curl localhost:8080/data/config/injected
Config value as Injected by CDI Injected value

#Call to a programmatically implemented endpoint
$ curl localhost:8080/data/config/lookup
Config value from ConfigProvider lookup value

MicroProfile allows you to override the settings in the properties file with system properties. Therefore, set the environment variable, assign the value of the environment variable to the Java system property, and execute it. Then, you can overwrite the value set in the "microprofile-config.properties" file and confirm that the value set in the environment variable is displayed.

#Setting environment variables[.(Dot)_(Underscore) and set]
$ export injected_value="Environment Value"

#Run your app with environment variables set in Java system properties
$ java -D"$injected_value" -jar target/MPConfigSample-microbundle.jar

#Checking the operation of the application
$ curl http://localhost:8080/data/config/injected
Config value as Injected by CDI Environment Value

*** Note: Although it is described in. (Dot) notation in the properties file, the. (Dot) notation cannot be used for environment variables depending on the OS. Therefore, when setting environment variables, replace the. (Dot) notation with _ (underscore). The conversion is done automatically inside the implementation. *** ***

3. Run in local Docker environment

Now that we have confirmed the operation of the application in the local environment, let's run MicroProfile in the local Docker environment. To create an image of Payara Micro's Docker container, create a Dockerfile like the one below.

FROM payara/micro:5.201

USER payara
WORKDIR ${PAYARA_HOME}

# Deploy Artifact
COPY ./target/MPConfigSample.war $DEPLOY_DIR

CMD ["--nocluster","--deploymentDir", "/opt/payara/deployments", "--contextroot", "app"]

Next, use this Dockerfile to create an image of the container. Run the docker build command to create an image of the container.

$ docker build -t tyoshio2002/payara-config-sample:1.0 .

#Console output example when executing a command
Sending build context to Docker daemon  151.2MB
Step 1/5 : FROM payara/micro:5.201
5.201: Pulling from payara/micro
050382585609: Already exists 
59f5185426ac: Already exists 
4d95208cd9c0: Pull complete 
c1409397cf71: Pull complete 
Digest: sha256:3ff92627d0d9b67454ee241cc7d5f2e485e46db81a886c87cf16035df7c80cc8
Status: Downloaded newer image for payara/micro:5.201
 ---> a11a548b0a25
Step 2/5 : USER payara
 ---> Running in cb755e484e79
Removing intermediate container cb755e484e79
 ---> 564283252ae4
Step 3/5 : WORKDIR ${PAYARA_HOME}
 ---> Running in f26dd5cd172c
Removing intermediate container f26dd5cd172c
 ---> f2bf88b18a77
Step 4/5 : COPY ./target/MPConfigSample.war $DEPLOY_DIR
 ---> 1b54373fe95a
Step 5/5 : CMD ["--nocluster","--deploymentDir", "/opt/payara/deployments", "--contextroot", "app"]
 ---> Running in 3eb731eb77c3
Removing intermediate container 3eb731eb77c3
 ---> 1d11549e99b8
Successfully built 1d11549e99b8
Successfully tagged tyoshio2002/payara-config-sample:1.0

After creating the image of the container, start the container. Execute the following command to start the container.

$ docker run -p 8080:8080 -e injected_value=hogehoge -it tyoshio2002/payara-config-sample:1.0

#Console output example when executing a command
..... (Omission)
[2020-03-11T07:46:59.119+0000] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1583912819119] [levelValue: 800] [[ 
{
    "Instance Configuration": {
        "Host": "3877abb54d57",
        "Http Port(s)": "8080",
        "Https Port(s)": "",
        "Instance Name": "payara-micro",
        "Instance Group": "no-cluster",
        "Deployed": [
            {
                "Name": "MPConfigSample",
                "Type": "war",
                "Context Root": "/app"
            }
        ]
    }
}]]
[2020-03-11T07:46:59.131+0000] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1583912819131] [levelValue: 800] [[
Payara Micro URLs:
http://3877abb54d57:8080/app
'MPConfigSample' REST Endpoints:
GET	/app/data/application.wadl
GET	/app/data/config/injected
GET	/app/data/config/lookup
GET	/app/data/hello
]]
[2020-03-11T07:46:59.131+0000] [] [INFO] [] [PayaraMicro] [tid: _ThreadID=1 _ThreadName=main] [timeMillis: 1583912819131] [levelValue: 800] Payara Micro  5.201 #badassmicrofish (build 512) ready in 31,286 (ms)

Now that the created container has started, connect to the application running on the container. This time, port 8080 inside the container is mapped to the local port 8080 at startup, so you can connect to the container's application by accessing port 8080 in the local environment. Execute the following command.

$ curl http://localhost:8080/app/data/config/injected
Config value as Injected by CDI hogehoge

Since the environment variable (***-e injected_value = hogehoge ***) is given as an argument when the container is started, the character string entered at the time of startup is displayed.

4. Run in Azure Web App for Containers environment

Now that we have confirmed the operation in the local Docker environment, we will confirm the operation in the Web App for Containers environment. Check the operation according to the following procedure.

  1. Create a resource group for Azure Container Registry (https://aka.ms/docs-container-registry)
  2. Create Azure Container Registry
  3. Confirm the password of Azure Container Registry
  4. Log in to Azure Container Registry and push the image
  5. Check the image pushed to Azure Container Registry
  6. Create a resource group for Web App for Containers (https://aka.ms/docs-webapp-for-containers)
  7. Create an AppService plan for Web App for Containers (https://aka.ms/docs-webapp-for-containers)
  8. Create Web App for Containers by specifying the container image.
  9. Check the operation of the deployed application
  10. Add application settings for Web App for Containers
  11. Check the operation of the application after changing the settings

4.1. Create a resource group for Azure Container Registry

First, create Azure Container Registry and upload the image of the locally created Docker container. So, create a resource group to create the Azure Container Registry.

$ az group create --name WebApp-Containers --location "Japan East"
{
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp-Containers",
  "location": "japaneast",
  "managedBy": null,
  "name": "WebApp-Containers",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}

4.2. Create Azure Container Registry

Next, create an Azure Container Registry.

$ az acr create --name containerreg4yoshio --resource-group WebApp-Containers --sku Basic --admin-enabled true
{
  "adminUserEnabled": true,
  "creationDate": "2020-03-12T02:27:59.357654+00:00",
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp-Containers/providers/Microsoft.ContainerRegistry/registries/containerreg4yoshio",
  "location": "japaneast",
  "loginServer": "containerreg4yoshio.azurecr.io",
  "name": "containerreg4yoshio",
  "networkRuleSet": null,
  "policies": {
    "quarantinePolicy": {
      "status": "disabled"
    },
    "retentionPolicy": {
      "days": 7,
      "lastUpdatedTime": "2020-03-12T02:28:01.654662+00:00",
      "status": "disabled"
    },
    "trustPolicy": {
      "status": "disabled",
      "type": "Notary"
    }
  },
  "provisioningState": "Succeeded",
  "resourceGroup": "WebApp-Containers",
  "sku": {
    "name": "Basic",
    "tier": "Basic"
  },
  "status": null,
  "storageAccount": null,
  "tags": {},
  "type": "Microsoft.ContainerRegistry/registries"
}

4.3. Confirming the Azure Container Registry password

Next, check the password to connect to Azure Container Registry.

$ az acr credential show --name containerreg4yoshio --resource-group WebApp-Containers
{
  "passwords": [
    {
      "name": "password",
      "value": "4zaIiLk*************+H1XO4AlYFvN"
    },
    {
      "name": "password2",
      "value": "fT03XPs*************Oq2cAZiVHV+L"
    }
  ],
  "username": "containerreg4yoshio"
}

4.4. Log in to Azure Container Registry and push the image

Then run the docker login command to connect to Azure Container Registry. (For the password, enter the password obtained above.)

After logging in, use the docker tag command to tag the image. Tag the locally created Docker container image name with the container registry's "loginServer" name (for example, "containerreg4yoshio.azurecr.io").

Finally, run the docker push command to push the image into Azure Container Registry.

#Log in to Azure Container Registry
$ docker login containerreg4yoshio.azurecr.io -u containerreg4yoshio
Password: 
Login Succeeded

#Docker container tagging
$ docker tag tyoshio2002/payara-config-sample:1.0 containerreg4yoshio.azurecr.io/tyoshio2002/payara-config-sample:1.0

#Push images tagged in Azure Container Registry
$ docker push containerreg4yoshio.azurecr.io/tyoshio2002/payara-config-sample:1.0

The push refers to repository [containerreg4yoshio.azurecr.io/tyoshio2002/payara-config-sample]
bbd197848553: Pushed 
ec40a5d738cc: Pushed 
f95fe3528c56: Pushed 
bded2364df91: Pushed 
1bfeebd65323: Pushed 
1.0: digest: sha256:689dbacc212d37afe09c43417bc79d8e241c3fa7b5cf71c27097ef535cf77f76 size: 1368

4.5. Checking the image pushed to Azure Container Registry

Make sure the image is pushed correctly into the Azure Container Registry.

$ az acr repository list -n containerreg4yoshio -g WebApp-Containers
Argument 'resource_group_name' has been deprecated and will be removed in a future release.
[
  "tyoshio2002/payara-config-sample"
]

4.6. Create a resource group for Web App for Containers

Now that you've created the Azure Conginer Registry, it's time to create your Web App for Containers. First, create a resource group to create the Web App for Containers.

$ az group create --name WebApp --location "Japan East"
{
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp",
  "location": "japaneast",
  "managedBy": null,
  "name": "WebApp",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}

4.7. Creating an AppService Plan for Web App for Containers

Next, create an AppService plan for Linux. This time, the SKU uses B1 to check the operation in the verification environment, but please select as appropriate according to the environment.

$ az appservice plan create --name webapp4container --resource-group WebApp --sku B1 --is-linux
{
  "freeOfferExpirationTime": "2020-04-11T02:38:56.873333",
  "geoRegion": "Japan East",
  "hostingEnvironmentProfile": null,
  "hyperV": false,
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp/providers/Microsoft.Web/serverfarms/webapp4container",
  "isSpot": false,
  "isXenon": false,
  "kind": "linux",
  "location": "Japan East",
  "maximumElasticWorkerCount": 1,
  "maximumNumberOfWorkers": 3,
  "name": "webapp4container",
  "numberOfSites": 0,
  "perSiteScaling": false,
  "provisioningState": "Succeeded",
  "reserved": true,
  "resourceGroup": "WebApp",
  "sku": {
    "capabilities": null,
    "capacity": 1,
    "family": "B",
    "locations": null,
    "name": "B1",
    "size": "B1",
    "skuCapacity": null,
    "tier": "Basic"
  },
  "spotExpirationTime": null,
  "status": "Ready",
  "subscription": "f77aafe8-****-****-****-d0c37687ef70",
  "tags": null,
  "targetWorkerCount": 0,
  "targetWorkerSizeId": 0,
  "type": "Microsoft.Web/serverfarms",
  "workerTierName": null
}

4.8. Create Web App for Containers by specifying container image

Next, create a Web App for Containers using the image pushed to Azure Container Registry.

$ az webapp create --resource-group WebApp \ 
                               --plan webapp4container \
                               --name yoshiowebapp \
                               --deployment-container-image-name containerreg4yoshio.azurecr.io/tyoshio2002/payara-config-sample:1.0

No credential was provided to access Azure Container Registry. Trying to look up...
{
  "availabilityState": "Normal",
  "clientAffinityEnabled": true,
  "clientCertEnabled": false,
  "clientCertExclusionPaths": null,
  "cloningInfo": null,
  "containerSize": 0,
  "dailyMemoryTimeQuota": 0,
  "defaultHostName": "yoshiowebapp.azurewebsites.net",
  "enabled": true,
  "enabledHostNames": [
    "yoshiowebapp.azurewebsites.net",
    "yoshiowebapp.scm.azurewebsites.net"
  ],
  "ftpPublishingUrl": "ftp://waws-prod-ty1-***.ftp.azurewebsites.windows.net/site/wwwroot",
  "geoDistributions": null,
  "hostNameSslStates": [
    {
      "hostType": "Standard",
      "ipBasedSslResult": null,
      "ipBasedSslState": "NotConfigured",
      "name": "yoshiowebapp.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "toUpdateIpBasedSsl": null,
      "virtualIp": null
    },
    {
      "hostType": "Repository",
      "ipBasedSslResult": null,
      "ipBasedSslState": "NotConfigured",
      "name": "yoshiowebapp.scm.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "toUpdateIpBasedSsl": null,
      "virtualIp": null
    }
  ],
  "hostNames": [
    "yoshiowebapp.azurewebsites.net"
  ],
  "hostNamesDisabled": false,
  "hostingEnvironmentProfile": null,
  "httpsOnly": false,
  "hyperV": false,
  "id": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp/providers/Microsoft.Web/sites/yoshiowebapp",
  "identity": null,
  "inProgressOperationId": null,
  "isDefaultContainer": null,
  "isXenon": false,
  "kind": "app,linux,container",
  "lastModifiedTimeUtc": "2020-03-12T02:39:50.356666",
  "location": "Japan East",
  "maxNumberOfWorkers": null,
  "name": "yoshiowebapp",
  "outboundIpAddresses": "13.**.***.96,13.**.**.49,13.**.**.66,13.**.**.140,13.**.**.186",
  "possibleOutboundIpAddresses": "13.**.**.96,13.**.**.49,13.**.**.66,13.**.**.140,13.**.**.186,13.**.**.30,13.**.**.70,13.**.**.101,13.**.**.163,13.**.**.200",
  "redundancyMode": "None",
  "repositorySiteName": "yoshiowebapp",
  "reserved": true,
  "resourceGroup": "WebApp",
  "scmSiteAlsoStopped": false,
  "serverFarmId": "/subscriptions/f77aafe8-****-****-****-d0c37687ef70/resourceGroups/WebApp/providers/Microsoft.Web/serverfarms/webapp4container",
  "siteConfig": null,
  "slotSwapStatus": null,
  "state": "Running",
  "suspendedTill": null,
  "tags": null,
  "targetSwapSlot": null,
  "trafficManagerHostNames": null,
  "type": "Microsoft.Web/sites",
  "usageState": "Normal"
}

4.9. Checking the operation of the deployed application

After creating the Web App for Containers, access the Web App for Containers endpoint to see if the application is working properly. Here, the environment variable is not set, so the value set in the property (Injected value) is displayed.

$ curl https://yoshiowebapp.azurewebsites.net/app/data/config/injected
Config value as Injected by CDI Injected value

4.10. Add application settings for Web App for Containers

Then add the application settings in the Web App Config and set the injected_value to the string "Value from Server App Setting".

$ az webapp config appsettings set --resource-group WebApp --name yoshiowebapp --settings injected_value="Value from Server App Setting"
[
  {
    "name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
    "slotSetting": false,
    "value": "false"
  },
  {
    "name": "DOCKER_REGISTRY_SERVER_URL",
    "slotSetting": false,
    "value": "containerreg4yoshio.azurecr.io/tyoshio2002"
  },
  {
    "name": "DOCKER_REGISTRY_SERVER_USERNAME",
    "slotSetting": false,
    "value": "containerreg4yoshio"
  },
  {
    "name": "DOCKER_REGISTRY_SERVER_PASSWORD",
    "slotSetting": false,
    "value": null
  },
  {
    "name": "WEBSITES_PORT",
    "slotSetting": false,
    "value": "8080"
  },
  {
    "name": "injected_value",
    "slotSetting": false,
    "value": "Value from Server App Setting"
  }
]

4.11. Checking the operation of the application after changing the settings

Finally, check if the settings added in the application settings are reflected.

$ curl https://yoshiowebapp.azurewebsites.net/app/data/config/injected
Config value as Injected by CDI Value from Server App Setting

After setting 4.10 above, the container will be restarted internally and the settings will be reflected without explicitly restarting the container.

You have successfully run an application using MicroProfile Config in the Azure Web App for Containers environment. Also, the application settings (external settings) of Web App for Containers could be read by the application.

Recommended Posts

MicroProfile Config operation verification in Azure Web App for Containers environment
Comparison of Web App for Containers and Azure Container Instances
[January 2020 version] See Azure Key Vault in Quarkus with MicroProfile Config
Best practice to change settings for each environment in iOS app (Swift)