In the following post I am going to describe how to create a custom docker image that contains the following:
– Glassfish Application Server
– Java JDK 1.8
– A custom deployed EJB 3.1 Application
In connection with this a Postgres docker image from which we start a container with a certain database name and user to be used by the application docker image.
STEP 1: Pull from docker.io registry the Glassfish and Postgres images
Note that we already have a local registry.
# docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker.io/postgres latest f91e27f33f26 4 weeks ago 263.8 MB docker.io/registry 2 541a6732eadb 4 weeks ago 33.27 MB docker.io/mtuanp/glassfish latest 46d7536ed8af 7 months ago 700 MB
STEP 2: Start new container from postgres image
Specify the following environment variables to the postgres image:
– POSTGRES_USER= a user name to be created when the postgres container is initialized
– POSTGRES_PASSWORD= a user name password to be created when the postgres container is initialized
– POSTGRES_DB= a database to be created when the postgres container is initialized and associated with the above user
This way we can create the custom parameters the EJB3 application expects to exist. The above parameters must be specified in the data source definition in the glassfish container.
docker run -e POSTGRES_USER=myDB -e POSTGRES_PASSWORD=myPassword -e POSTGRES_DB=myDB --name postgres docker.io/postgres
STEP 3: Start a new container from glassfish image
docker run --link postgres:postgres -p 7001:7001 f91e27f33f26
Note: In a docker run command we can either use the alias of the image or the Image ID as the last parameter of the command. Note also that we link the container with the postgres container and we give it the alias postgres. As a result we will be able to access from this container the db with the server name postgres.
List the containers that are started in docker.
# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2a5eb73b4c45 735c23377d5d "/bin/sh -c 'asadmin " 9 days ago Up 2 hours 4848/tcp, 8080/tcp, 8181/tcp, 9009/tcp, 0.0.0.0:5001->7001/tcp, 0.0.0.0:5031->7031/tcp demo 6a4c538e09eb f91e27f33f26 "/docker-entrypoint.s" 2 weeks ago Up 23 hours 0.0.0.0:5432->5432/tcp postgres 48c832c6333c 541a6732eadb "/entrypoint.sh /etc/" 2 weeks ago Up 6 days 0.0.0.0:5000->5000/tcp
STEP 4: Customize the glassfish based application image with our own JAVA JDK.
The default image we use as a base point has a JAVA 1.7 JRE installed. We need to upgrade it to JDK 1.8 for our EJB 3.1 application.
The tricky part will start now. We will copy inside the container file-system the new JDK.
First try to identify under which devicemapper device the container file-system is mounted.
# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 7.8G 0 7.8G 0% /dev tmpfs 7.8G 11M 7.8G 1% /dev/shm tmpfs 7.8G 2.3M 7.8G 1% /run tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup /dev/mapper/vg-lv_root 50G 30G 18G 63% / tmpfs 7.8G 306M 7.5G 4% /tmp /dev/sda6 477M 156M 292M 35% /boot /dev/sda4 200M 5.4M 195M 3% /boot/efi /dev/mapper/vg-lv_home 411G 372G 19G 96% /home tmpfs 1.6G 24K 1.6G 1% /run/user/42 tmpfs 1.6G 80K 1.6G 1% /run/user/1000 /dev/sdb1 165G 153G 4.0G 98% /ssd /dev/mapper/truecrypt1 99G 78G 16G 84% /media/truecrypt1 tmpfs 1.6G 0 1.6G 0% /run/user/0 /dev/mapper/truecrypt2 591G 543G 18G 97% /media/truecrypt2 /dev/dm-6 10G 66M 10G 1% /home/docker/devicemapper/mnt/317b5cfb884027e4a4087b5bc92f5792169df1c13a94e9da66ad38938fe1d457 shm 64M 0 64M 0% /home/docker/containers/48c832c6333cda0b8b1d6e7200a13d28ef941f6907505f19fed68e951713034e/shm /dev/dm-7 10G 314M 9.7G 4% /home/docker/devicemapper/mnt/e259eb0e289c565bd8bcefee87df6a7e874aeab7d7cedb4d9658d7201b4f50e7 shm 64M 4.0K 64M 1% /home/docker/containers/6a4c538e09eb4245c71be3d056366f253c1a6e96b511b67e6dcdaa603d9e8876/shm /dev/dm-8 10G 1.2G 8.9G 12% /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764 shm 64M 0 64M 0% /home/docker/containers/2a5eb73b4c4525440d69d92cb00c77672a7f1b4b150fcb08e2fb38271c66a02f/shm
Our application container id is 2a5eb73b4c45 and as we can see there is a devicemapper that has that prefix
/dev/dm-8 10G 1.2G 8.9G 12% /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764
Copy the new Java JDK in the container filesystem:
cp -R /usr/java/jdk1.8.0_45 /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/usr/java/jdk1.8.0_45
Remove the old Java from the container filesystem:
rm -fR /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/usr/java/jdk1.7
Note that this change is only on this container. In case we start a new container from the original image all is lost. In case we stop and restart this same container the change is still there. If we need to interrupt the config at any point make sure to restart container 2a5eb73b4c45 to continue.
STEP 5: Customize the glassfish based application image with our own glassfish 3.1.2
I already have a glassfish domain under which I deployed my EJB 3.1 application. I am not going to insist on how to setup the application specific configurations and how to deploy the binary. In short the application has a JMS connection factory, several JMS queues , a data source and some other glassfish application specific settings for sign-on and security.
There are several steps to set all in order.
STEP 5.1 Copy in the container my own glassfish
I do not like the glassfish application server deployed in the default image. I need Glassfish 3.1.2 with mq support.
Remove the old glassfish from the container/image filesystem:
rm -fR /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/opt/glassfish3
Copy my own glassfish 3.1.2 in the container filesystem:
cp -R /opt/glassfish3 /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/opt/glassfish3
STEP 5.2 Activate secure admin to the container
I want to be able to connect only on https port to the glassfish admin console.
Copy a password.txt file under glassfish3/glassfish in the container filesystem.
Content is like the following:
AS_ADMIN_PASSWORD=adminadmin ## Required for Glassfish V1 AS_ADMIN_ADMINPASSWORD=adminadmin ## Required for Glassfish V1 AS_ADMIN_MASTERPASSWORD=adminadmin
Then execute the following command to execute the config on the running container
docker exec 2a5eb73b4c45 bin/asadmin --host 0.0.0.0 --port 7001 --passwordfile=glassfish/password.txt enable-secure-admin
After the above command is executed and we stop aqnd start the container we can access the glassfish admin console with:
STEP 5.3 Change the data source to point to the postgres container
Copy first the postgres JDBC driver postgresql-9.4.1211.jar under opt/glassfish3/glassfish/lib in the container filesystem.
Change the JDBC Connection Pool defined to be used by our application to use:
Datasource Classname: org.postgresql.ds.PGConnectionPoolDataSource
Then under Additional Properties:
serverName postgres password *** user myDB databaseName myDB portNumber 5432
Note we refer to the postgres alias we gave to the database container when we started our application container.
Note that we assumed that also the ear binary of the application was custom build to support postgres also.
STEP 5.4 Finalize the configuration and clean-up
At this point we have to do all the other configuration changes in the glassfish admin console.
Execute also a basic clean-up so we can create a new image from this running container.
– delete glassfish logs
– delete /tmp
– delete imq logs
– delete imq lock: /rootfs/opt/glassfish3/glassfish/domains/demo/imq/instances/imqbroker/lock
STEP 6 Create a new image with all the changes
Now this are some very counter intuitive steps one must perform. Due to the layered way docker framework is constructed all the changes we have done in the previous steps are visible only in the container layer.
The devicemapper mount is the container/image file-system. It means that any change to it will be carried out if a new container will be instantiated from the associated image. This also means that some changes from a running container will not be visible there. To make the changes permanent we have to save the changes from the running container and add them to a new image.
STEP 6.1 Copy files from container to host
Using docker cp we will copy the modified files we want to put in the new image to the host. This includes the entire /opt/glassfish3 directory. We are forced to do this because all the changes made from GUI of the glassfish admin console are stored only as file diffs in the container layer.
mkdir container-bkp docker cp 2a5eb73b4c45:/opt container-bkp
STEP 6.2 Copy files from host to the container/image filesystem
We copy the files we saved from the container back to the container/image devicemapper
cp -R container-bkp /home/docker/devicemapper/mnt/20bc2f7c1869c1b5e4aa1b8a874e5c06694fd4b59962edef4237d674104b4764/rootfs/opt/
STEP 6.3 Take a snapshot of current running state of the application container
# docker commit -p 2a5eb73b4c45 10.0.0.245:5000/demo_app.v3 sha256:4810c76ceed0e56347c3e7002ff51a86a9dd31770ddd2cbea9031fc503d80ec3
With the above command we have first paused a running container with -p option, made a commit to save the entire snapshot as a docker image with a new name.
As an alternative if we had to create several layers during the creation of our image, we can flatten the image by taking a snapshot of the container and then importing it.
#docker export 2a5eb73b4c45 | docker import - 10.0.0.245:5000/demo_app.v3:flat
STEP 6.4 Save on disk the new image
The following command will actually save the devicemapper content.
docker save -o demo_app.v3.tar demo_app.v3 gzip demo_app.v3.tar
STEP 6.5 Push the new image to the local private registry
docker push 10.0.0.245:5000/demo_app.v3
STEP 7 Test the new image on a remote site
Copy to remote site and load the image
docker load -i demo_app.v3.tar.gz
Then to start the new environment:
docker pull docker.io/postgres docker run -e POSTGRES_USER=myDB -e POSTGRES_PASSWORD=myPassword -e POSTGRES_DB=myDB --name postgres docker.io/postgres docker run --link postgres:postgres -p 7031:7031 image_id
Where image_id is the image id of the loaded image.
STEP 8 Monitor the new application
To monitor the new application start a tail on the domain logs in the container.
docker exec 2a5eb73b4c45 tail -F glassfish/domains/demo/logs/server.log
Execute an interactive bash to have access to more commands, like top or ps.
docker exec -i -t 2a5eb73b4c45 /bin/bash top
Using the above method allows a very easy migration of already available test environments from a development machine. Migrating the test environment to docker images allows the QA department to start in no time work without having to do configurations of a test environment. Also with every new release of the application we just have to release a new application image for the QA to continue without interruption.