The final versions of the files created in this section can be found in this repo
Summary
This section has the following steps:
- Overview
- Code changes
- Create the WebSphere Liberty server.xml file
- Handling environment specific configuration
- Monitoring
- Project structure
- Create the Docker Dockerfile
- Run the application locally
Overview
This section covers the following steps that are required to build and run the CustomerOrderServices application locally in a Docker container.
Make minimal code changes to the application based on the analysis from IBM Cloud Transformation Advisor
Create the WebSphere Liberty configuration file
server.xml
for the Customer Order Services application. This includes:- listing the WebSphere Liberty runtime features that are required
- configuring a DB2 Data Source
- configuring a local user registry
- using environment variables in the
server.xml
file to allow configuration to be injected in to the runtime environment dynamically such as the DB2 user and password.
Configuring WebSphere Liberty to expose internal metrics in a way that they can be collected by Prometheus
Creating a
Dockerfile
and running the application in a Docker container locally
Code changes
In the previous section IBM Cloud Transformation Advisor highlighted a change in the way that Enterprise JavaBeans are looked up in Liberty.
Behavior change on lookups for Enterprise JavaBeans In Liberty, EJB components are not bound to a server root Java Naming and Directory Interface (JNDI) namespace as they are in WebSphere Application Server traditional. The fix for this is to change the three classes that use ejblocal
to use the correct URL for Liberty
The three Java classes that should be modified to look up Enterprise JavaBeans differently are shown in the detailed analysis view of IBM Cloud Transformation Advisor (it isn’t necessary to change the ibm-web-bnd.xml
file in the Test
project in this scenario) to be in the CustomerOrderServicesWeb
project
org.pwte.example.resources.CategoryResource.java
is changed from using ejblocal
on line 28 as shown below:
...InitialContext().lookup("ejblocal:org.pwte.example.service.ProductSearchService");...
to use java:app
as shown below:
...InitialContext().lookup("java:app/CustomerOrderServices/ProductSearchServiceImpl!org.pwte.example.service.ProductSearchService");...
line 53 of org.pwte.example.resources.CustomerOrderResource.java
is changed as shown below:
...ctx.lookup("java:app/CustomerOrderServices/CustomerOrderServicesImpl!org.pwte.example.service.CustomerOrderServices");...
line 38 of org.pwte.example.resources.ProductResource.java
is changed as shown below:
...InitialContext().lookup("java:app/CustomerOrderServices/ProductSearchServiceImpl!org.pwte.example.service.ProductSearchService");...
This completes all of the required code changes for the Customer Order Services application to run on the WebSphere Liberty runtime.
Create the WebSphere Liberty server.xml file
WebSphere Liberty is configured by exception. The runtime environment operates from a set of built-in configuration default settings, and you only need to specify configuration that overrides those default settings. You do this by editing either the server.xml
file or another XML file that is included in server.xml
at run time. The configuration has the following characteristics:
- Described in XML files.
- Human-readable, and editable in a text editor.
- Small, easy to back up, and easy to copy to another system.
- Shareable across an application development team.
- Composable, so that features can easily add their own configuration to the system.
- Extensibly-typed, so you don’t have to modify the current configuration to work with later versions of the runtime environment.
- Dynamically responsive to updates.
- Forgiving, so that missing values are assumed and unrecognized properties are ignored.
Features are the units of functionality by which you control the pieces of the runtime environment that are loaded into a particular server. They are the primary mechanism that makes the server composable. The list of features that you specify in the server configuration provides a functional server. See Liberty features for more information.
In this section, the server.xml
file to prepare the Liberty server to run the Customer Order Services application will be reviewed. The file can be found here. and is shown below:
<server><featureManager><feature>appSecurity-2.0</feature><feature>ldapRegistry-3.0</feature><feature>localConnector-1.0</feature><feature>ejbLite-3.1</feature><feature>jaxrs-1.1</feature><feature>jdbc-4.1</feature><feature>jpa-2.0</feature>
The features
section contains the following features:
ejbLite-3.1
: the feature required to run simple Stateless Session Enterprise Java Beansjpa-2.0
,jdbc-4.1
: the features required for the JPA 2.0 runtimejaxrs-1-1
: the feature required for the JAX-RS 1.1 runtimeservlet-3.1
,jsp-2.3
: the features required for Servlets and JSPsldapRegistry-3.0
,appSecurity-2.0
: the features required to secure access to the application using LDAP or a local user registrymonitor-1.0
: this feature enables the Performance Monitoring Infrastructure (PMI) for other features the server is running. Monitoring data is accessible through standard MXBeans.
The library
and dataSource
sections configure a JDBC Driver
for DB2 and a DataSource
for the database used by the application. Note that some values have a {env.}
prefix which is used to allow the values to be read from the environment instead of being hard-coded. This will be discussed in more detail in the Environment Specific Configuration section later.
The basicRegistry
section configures a local user registry with a single user (rbarcia
) and a single group (SecureShopper
). This is often used for development and testing when an LDAP server isn’t available and is sufficient for this scenario.
The applicationMonitor
tag is used with Eclipse and triggers the application to be reloaded when a deployment from Eclipse occurs.
The application
tag describes the CustomerOrderServices EAR with an id
, name
and location
. In this case the default location of the apps
folder is used. The classloader
tag is used to allow the application access to the Java Classes that make up the JEE specification (spec
), some IBM provided APIs (ibm-api
) and third-party classes such as apache-wink
as these classes are hidden from the application by default.
The classloader
is configured becuase IBM Cloud Transformation Advisor highlighted that the application needed access to the Apach Wink APIs:
The user of system provided Apache Wink APIs requires configuration To use system-provided third-party APIs in Liberty applications, you must configure the applications to include the APIs. In WebSphere Application Server traditional, these APIs are available without configuration. This is a configuration only change and can be achieved by using a classloader
definition in the Liberty server.xml file.
Handling environment specific configuration
Applications deployed to an Application Server typically require access to backend services such as Databases, Message Queues, LDAP servers and other applications. Connection information for the backend services might include URL, Hostname, Port, UserID and Password and this information is often different depending on the environment (e.g. Dev, Test or Production) that the application is deployed to. In the case of the Customer Order Services application, a connection to a DB2 database is required.
In order to take advantage of the portability provided by a platform such as Kubernetes and the speed of DevOps it is necessary to deploy applications in immutable containers using automation.
In this context, immutable means that a container will not be modified during its life: no updates, no patches, no configuration changes. If you need to update the application code or apply a patch, you build a new image and deploy it. In order to achieve this, it is necessary to inject environment specific configuration in to the container in each environment (Database UserIDs and Passwords for example) instead of hard-coding the values in properties files in the container.
The goal is to deploy the application in a portable, immutable container and in order to achieve that any environment specific configuration should be externalized from the container image and injected by the platform. In the Kubernetes world this is achieved using Environment Variables, ConfigMaps and Secrets.
Using the env.
prefex in the WebSphere Liberty server.xml
forces the runtime to use the value of the environment variable when the server starts. See the documentation for more details. In the snippet below from the Customer Order Services application server.xml
file, the host
, portNumber
, databaseName
, user
and password
have all been externalized as environment variables which allows this application image to be moved between environments that have different databases without changing the image.
<dataSource id="OrderDS" jndiName="jdbc/orderds" type="javax.sql.XADataSource"><jdbcDriver libraryRef="DB2Lib"/><properties.db2.jcc databaseName="${env.DB2_DBNAME}" password="${env.DB2_PASSWORD}" portNumber="${env.DB2_PORT}" serverName="${env.DB2_HOST}" user="${env.DB2_USER}"/><connectionManager agedTimeout="0" connectionTimeout="180" maxIdleTime="1800" maxPoolSize="10" minPoolSize="1" reapTime="180"/></dataSource>
In a native development environment (non-Docker) a server.env
file can be used to inject the environment variables directly in to WebSphere Liberty when it starts. This avoids having to set environment variables on the development machine. This file is placed in the same folder as the server.xml
file and is read by Liberty on startup. The file can be found here. and is shown below:
DB2_HOST=172.16.52.252DB2_PORT=50000DB2_DBNAME=ORDERDBDB2_USER=db2inst1DB2_PASSWORD=db2inst1
Monitoring
Prometheus and Grafana are widely used in Kubernetes to monitor applications. Prometheus is a systems and service monitoring system. It collects metrics from configured targets at given intervals, evaluates rule expressions, displays the results, and can trigger alerts if some condition is observed to be true.
Prometheus has several components for the collection of Time Series Data, an Alert Manager and a central Prometheus Server which scrapes and stores the data. The data is visualized using a Grafana instance.
Kubernetes Monitoring
Prometheus can monitor Kubernetes and the application pods. For Kubernetes data is related to the overall cluster state such as pod state (running, pending, error etc), cluster CPU usage and cluster RAM usage. Individual pod data is also available including CPU and RAM usage. This data is useful for an overall cluster view but it doesn’t provide application specific data.
Application Monitoring
Prometheus can pull metrics data from applications using scraping. It is the responsibility of the application to make their metrics available on a /metrics
endpoint which Prometheus scrapes and stores the data in its database for later visualization using Grafana.
An example of the results from a /metrics
scrape are shown below. Metrics related to connection pools, thread pools, CPU, memory and JVM heap usage are more useful for a Java application that the basic pod metrics available by default.
base:classloader_current_loaded_class_count 16266base:classloader_total_loaded_class_count 16372base:classloader_total_unloaded_class_count 106base:cpu_available_processors 8base:cpu_process_cpu_load_percent 8.262355256251326E-4base:gc_global_count 245base:gc_global_time_seconds 13.756base:gc_scavenge_count 9467base:gc_scavenge_time_seconds 21.759
Metrics scraping with WebSphere Liberty
WebSphere Liberty provides a monitoring-1.0
feature which enables the Performance Monitoring Infrastructure (PMI) for other features the server is running and makes the monitoring data available via MXBeans. Prometheus cannot scrape MXBeans.
WebSphere Liberty also provides a mpMetrics
feature which takes the data from the MXBeans and exposes it on a /metrics
endpoint that can be read by Prometheus. However, the mpMetrics feature is only available to applications using JavaEE7 or newer features.
The Customer Order Services application uses the jpa-2.0
, jaxrs-1.1
and ejbLite-3.1
features which are older than JavaEE7 and is therefore unable to use the mpMetrics
feature and can’t expose a /metrics
endpoint for Prometheus.
Prometheus JMX exporter
A solution to the problem that the Customer Order Services application has is to use Prometheus JMX exporter. The Prometheus JMX exporter connects to any MXBeans on a JVM, retrieves their data and exposes the results on a /metrics
endpoint so that the data can be scraped by Prometheus.
The JMX exporter is made up of a JAR file and a configuration file. The configuration file used for the Customer Order Services application is shown below.
---startDelaySeconds: 0ssl: falselowercaseOutputName: falselowercaseOutputLabelNames: false
In order to connect the JMX exporter and configuration file to Liberty on startup, a jvm.options
file is required. The jvm.options file is shown below.
-javaagent:/opt/ibm/wlp/usr/shared/resources/jmx_exporter/jmx_prometheus_javaagent-0.11.0.jar=9081:/opt/ibm/wlp/usr/shared/resources/jmx_exporter/jmx-config.yml
An example of the dashboard that is now possible in Grafana is shown below. In this case the dashboard shows data about Servlets and Thread Pools.
More details related to the metrics exposed by WebSphere Liberty and how to visualize them with Grafana will be covered in the Liberty - Deploy section
Project Structure
The git project for the Customer Order Services application already has the following structure:
├── CustomerOrderServices| ├── ejbModule| | └── <source code>│ └── pom.xml├── CustomerOrderServicesApp│ ├── META-INF| | └── <descriptor files>│ └── pom.xml├── CustomerOrderServiceProject
It is now necessary to add the WebSphere Liberty, DB2 and JMX Exporter files to the git project so that they can be pulled in to the Docker image during Docker build.
The following folders and files have been added to the project:
├── resources| ├── db2| | └── db2jcc4.jar| | └── db2jcc_license_cu.jar│ └── jmx_exporter| | └── jmx-config.yml| | └── jmx_prometheus_javaagent-0.11.0.jar└── liberty└── server.xml
Create the Docker Dockerfile
Once the application code changes have been made and WebSphere Liberty configuration has been created the next step is to build a Docker image that contains the application and configuration. The build script to create a Docker image is a Dockerfile.
The Dockerfile
for the Customer Order Services application is shown below. The file can be found here
FROM ibmcom/websphere-liberty:kernel-java8-ibmjava-ubiCOPY --chown=1001:0 ./liberty/server.xml /config/server.xmlCOPY --chown=1001:0 ./liberty/jvm.options /config/jvm.optionsARG SSL=falseARG MP_MONITORING=falseARG HTTP_ENDPOINT=false
IBM provides a set of WebSphere Liberty Docker images. This scenario uses the Docker images that are based on the RedHat Universal Base Image. The ibmcom/websphere-liberty:kernel-java8-ibmjava-ubi
image contains only the smallest components of the Liberty server. The features used by the application are loaded in to the Docker image later in the RUN configure.sh
step.
The first two COPY
commands copy the server.xml
and jvm.options
files to the /config
folder on the WebSphere Liberty image. Later in the Dockerfile there are other COPY
commands to copy the application that has been created by Maven to /config/apps
and the DB2 drivers
, JMX exporter JAR
and JMX exporter configuration file
to the shared resources folder for Liberty.
Run the application locally
Now that the application code changes have been made, the WebSphere Liberty configuration has been created and the Dockerfile is ready, the next step is to build a Docker image and run an instance locally to validate that the application runs correctly.
- Clone the GitHub repo using the following steps:
git clone https://github.com/ibm-cloud-architecture/appmod-liberty-tektoncd appmod-liberty-tekton
- Use Maven to build the application
mvn clean package
The CustomerOrderServicesApp-0.1.0-SNAPSHOT.ear
file should now be present in CustomerOrderServicesApp/target/
- Modify the
liberty/server.env
file for your environment as shown below:
DB2_HOST=172.16.52.252DB2_PORT=50000DB2_DBNAME=ORDERDBDB2_USER=db2inst1DB2_PASSWORD=db2inst1
- Build the Docker image using the following commands:
docker build -t customerorderservices-local:1.0 .
The end of the docker build
command output should be similar to that shown below:
...All assets were successfully installed.Start product validation...Product validation completed successfully.+ sort -z+ xargs -0 -n 1 -r -I '{}' java -jar '{}' --installLocation /opt/ibm/wlp+ find /opt/ibm/fixes -type f -name '*.jar' -print0+ xargs -0 -r chmod -R g+rw
- Run the newly created Docker image using the commands below:
docker run --name customerorderservices-local -d -p 9081:9081 -p 9443:9443 -v ${PWD}/liberty/server.env:/config/server.env customerorderservices-local:1.0
This command injects the server.env file in the correct location so that it is loaded by WebSphere Liberty on startup.
- Check the logs for the WebSphere Liberty server using:
docker logs customerorderservices-local
The result should be that the server is started without error and the application is loaded:
Launching defaultServer (WebSphere Application Server 19.0.0.5/wlp-1.0.28.cl190520190522-2227) on IBM J9 VM, version 8.0.5.36 - pxa6480sr5fp36-20190510_01(SR5 FP36) (en_US)[AUDIT ] CWWKE0001I: The server defaultServer has been launched.[AUDIT ] CWWKE0100I: This product is licensed for development, and limited production use. The full license terms can be viewed here: https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/wasdev/license/base_ilan/ilan/19.0.0.5/lafiles/en.html[AUDIT ] CWWKG0093A: Processing configuration drop-ins resource: /opt/ibm/wlp/usr/servers/defaultServer/configDropins/defaults/keystore.xml[AUDIT ] CWWKG0102I: Found conflicting settings for defaultKeyStore instance of keyStore configuration.Property password has conflicting values:Secure value is set in file:/opt/ibm/wlp/usr/servers/defaultServer/configDropins/defaults/keystore.xml.Secure value is set in file:/opt/ibm/wlp/usr/servers/defaultServer/server.xml.Property password will be set to the value defined in file:/opt/ibm/wlp/usr/servers/defaultServer/server.xml.
- Access the application in a browser using
https://127.0.0.1:9443/CustomerOrderServicesWeb
. Login usingrbarcia
andbl0wfish
and then add an item to the shopping cart
- Validate that the
/metrics
endpoint is available athttp://127.0.0.1:9081/metrics
- Stop the Docker container using the commands below:
docker stop customerorderservices-local
Review and Next Steps
The intention of this traditional WebSphere V855 —> WebSphere Liberty scenario is to migrate the Customer Order Services application to the cloud-ready new runtime with minimal code changes.
In this section you have moved the application to WebSphere Liberty and tested it locally in a Docker container.
Now proceed to the either the Liberty - Deploy using Cloud Native Toolkit section or the Liberty - Deploy using OpenShift Pipelines section where the process of automating the deployment of the application to a Kubernetes runtime will be covered step-by-step