Containerized WebSphere app deployment pipeline

Another post out of the “use the quieter days after Christmas” series. :-)

This time I will write about a use case we had in our own (panagenda) environment. It was on my list for a long time and now I found some time to implement it: A containerized pipeline to deploy a WebSphere application on different WebSphere environments triggered by another pipeline. For us, it was enough to update an existing application. Therefore my pipeline does only support updates so far.

I decided to build a clientless solution to be able to deploy the application to different environments. That’s why I decided not to use a Gitlab Runner locally on the WebSphere machines (Yes, ones again I’m using Gitlab CI to build the pipeline). Instead of a local Gitlab Runner, I built a WebSphere Docker image which I then use to remotely connect to the different WebSphere instances. The Docker container will be deployed (and destroyed after the pipeline finished) on our Kubernetes Cluster which is integrated with the Gitlab platform. This is handled by the Gitlab Runner for Kubernetes (more details).

What is needed?

First of all, we need a WebSphere Docker image. I built the Docker image with the following Dockerfile. Until now, this part is not automated. It wouldn’t be hard to build it (more details) but in this case, it's just not needed.

FROM centos:centos7
LABEL maintainer="" \
ADD /install_response_file.xml /tmp/
RUN yum update -y \
&& yum install -y unzip wget psmisc glibc.i686 libgcc.i686 \
&& wget -P /tmp/ \
&& unzip -d /tmp/im /tmp/ \
&& /tmp/im/installc -log /tmp/im_install.log -acceptLicense \
&& wget http://<your-ip>:8000/ -P /tmp/ \
&& wget http://<your-ip>:8000/ -P /tmp/ \
&& wget http://<your-ip>:8000/ -P /tmp/ \
&& unzip -d /tmp/was /tmp/ \
&& unzip -d /tmp/was /tmp/ \
&& unzip -d /tmp/was /tmp/ \
&& /opt/IBM/InstallationManager/eclipse/tools/imcl -acceptLicense input /tmp/install_response_file.xml -log /tmp/was_install_log.xml \
&& /opt/IBM/WebSphere/AppServer/bin/ -create -profileName Custom01 -profilePath /opt/IBM/WebSphere/AppServer/profiles/Custom01 -templatePath=/opt/IBM/WebSphere/AppServer/profileTemplates/default \
&& yum remove -y unzip wget \
&& yum clean all \
&& rm -rf /tmp/*
CMD [""]

I saved some disk space by using wget to copy all my installation sources instead of using multiple ADD commands. The easiest solution is to use a python web server to provide the installation files. It can simply be started from the command line within your local software directory:

python -m SimpleHTTPServer 8000

Anyway, the Image is still not a small one.

I decided to go for a full WebSphere profile instead of a client installation to be able to use the retrieveSigners tool which is not part of the client package. Without it, you need to manually handle and copy the certificates from the remote WebSphere installation. For the installation itself you will need a response file (/install_response_file.xml) to be able to install WebSphere in silent mode:

<?xml version='1.0' encoding='UTF-8'?>
<repository location='/tmp/was/'/>
<profile id='IBM WebSphere Application Server V8.5' installLocation='/opt/IBM/WebSphere/AppServer'>
<data key='eclipseLocation' value='/opt/IBM/WebSphere/AppServer'/>
<data key='user.import.profile' value='false'/>
<data key='cic.selector.os' value='linux'/>
<data key='cic.selector.arch' value='x86'/>
<data key='' value='gtk'/>
<data key='' value='en'/>
<install modify='false'>
<offering id='' profile='IBM WebSphere Application Server V8.5' features='core.feature,ejbdeploy,thinclient,embeddablecontainer,' installFixes='none'/>
<preference name='' value='/opt/IBM/IMShared'/>
<preference name='' value='30'/>
<preference name='' value='45'/>
<preference name='' value='0'/>
<preference name='offering.service.repositories.areUsed' value='true'/>
<preference name='' value='false'/>
<preference name='' value='false'/>
<preference name='http.ntlm.auth.kind' value='NTLM'/>
<preference name='http.ntlm.auth.enableIntegrated.win32' value='false'/>
<preference name='' value='false'/>
<preference name='' value='false'/>
<preference name='PassportAdvantageIsEnabled' value='false'/>
<preference name='' value='false'/>
<preference name='' value='false'/>
<preference name='' value='true'/>
<preference name='' value='true'/>
<preference name='' value='true'/>

Now you are ready to build your WebSphere image and upload it into the Docker Registry of your Gitlab repository.

The pipeline will use our image to call the wsadmin client which will execute a python script. The script (/ itself will handle the update process:

def updateApp():
app_name = sys.argv[0]
app_root = sys.argv[1]
except IndexError:
print "Provide all variables!"

cell = AdminControl.getCell()

AdminApp.update(app_name, 'app', '[ -operation update -contents ./app.ear -nopreCompileJSPs -installed.ear.destination $(APP_INSTALL_ROOT)/'+cell+' -distributeApp -nouseMetaDataFromBinary -createMBeansForResources -noreloadEnabled -nodeployws -validateinstall warn -noprocessEmbeddedConfig -filepermission .*\.dll=755#.*\.so=755#.*\.a=755#.*\.sl=755 -noallowDispatchRemoteInclude -noallowServiceRemoteInclude -asyncRequestDispatchType DISABLED -nouseAutoLink -noenableClientModule -novalidateSchema -CtxRootForWebMod [[ "<your-app>" app.war,WEB-INF/web.xml /'+app_root+' ]]]' )


Finally, we are ready to build the pipeline itself. This is the pipeline definition I used:

- deploy



App deployment:
stage: deploy
- kubernetes
name: <your-registry>/app-deploy/wsadmin:latest
entrypoint: [""]
- /opt/IBM/WebSphere/AppServer/profiles/Custom01/bin/ CellDefaultTrustStore ClientDefaultTrustStore -autoAcceptBootstrapSigner -host $DMGR_HOST -user $WAS_USER -password $WAS_PWD
- curl $EAR_URL -o $CI_PROJECT_DIR/app.ear
- /opt/IBM/WebSphere/AppServer/profiles/Custom01/bin/ -host $DMGR_HOST -conntype SOAP -port 8879 -lang jython -username $WAS_USER -password $WAS_PWD -f $CI_PROJECT_DIR/ "$APP_NAME" "$APP_ROOT"

As you see I used four different variables to define the WebSphere environment, the ear URL, the Application name and context root. They are empty by default because we specify the values when we call the pipeline. The WebSphere credentials, which are used to connect to WebSphere, are stored in the repository itself and are valid for all environments.

The pipeline will start a container using our image on the integrated Kubernetes Cluster and will then execute three different commands:

  1. The retrieveSigners script downloads and saves the remote certificates (They are needed to successfully connect to remote WebSphere instance)
  2. curl is used to download the ear file from a different repository (in our case it’s a Marven repository)
  3. wsadmin calls the script which updates the application

Now we are ready to run the pipeline and update our application. In our case, this is triggered by another build pipeline which calls this pipeline with a single curl command (you will need to create a trigger token which is necessary for the authentication):

curl -X POST \
--insecure \
-F token=<trigger-token> \
-F "ref=master" \
-F "variables[DMGR_HOST]=<path-to-my-websphere>" \
-F "variables[EAR_URL]=http://<path-to-my-app>/app.ear" \
-F "variables[APP_NAME]=<app-name>" \
-F "variables[APP_ROOT]=<app-context-root>" \