Update 2018-Nov-19: Incremented JBoss Image Stream to version 1.2. Thanks to Jonathan Velasquez for finding the error!
This week, I attended the FOSS4G North America conference, in St. Louis. It’s a gathering of geospatial geeks to learn and collaborate on what’s happening in the community. A few of my colleagues and I were there representing Red Hat, a sponsor of the event. We were showing how the OpenShift Container Platform enables continuous delivery and containerization of geospatial applications.
With that thought on the brain, I’d like to show how to build a GeoServer container and deploy it on OpenShift. You’ll need access to an OpenShift install. If you don’t have it running already, try OpenShift Online, or create a Red Hat Developer account to download it for free.
First, we need to login to the cluster:
$ oc login https://ocp.example.com:443 Authentication required for https://ocp.example.com:443 (openshift) Username: nsabine Password: Login successful. You don't have any projects. You can try to create a new project, by running oc new-project
And create a project:
$ oc new-project geoserver Now using project "geoserver" on server "https://ocp.example.com:443".
Now that we have a sandbox to play in, let’s tell OpenShift how we want our container image to be built:
$ oc new-build \--binary=true \ \--image-stream=jboss-webserver31-tomcat8-openshift:1.2 \ \--name=geoserver --> Found image d061e71 (5 months old) in image stream "openshift/jboss-webserver31-tomcat8-openshift" under tag "1.2" for "jboss-webserver31-tomcat8-openshift:1.2" JBoss Web Server 3.1 Platform for building and running web applications on JBoss Web Server 3.1 - Tomcat v8 Tags: builder, java, tomcat8 * A source build using binary input will be created * The resulting image will be pushed to image stream "geoserver:latest" * A binary build was created, use 'start-build --from-dir' to trigger a new build --> Creating resources with label build=geoserver ... imagestream "geoserver" created buildconfig "geoserver" created --> Success
This command creates a new Build, and specifies the Source-to-Image (S2I) builder image that we plan to use. The builder image is a container that knows how to take a set of defined inputs, such as application source code, binary files, and configuration, and produce a runnable container image for that application. It’s basically a mini CI/CD process, self contained in a container. We chose a Java servlet container builder, and told it that we will be providing a binary WAR file as input.
Now that we’ve created our builder, we need to give it the GeoServer war file as input.
Download the GeoServer Web Archive package. You can get the latest from http://geoserver.org/release/stable/. At the time of writing, the current release is 2.13.0.
$ wget http://sourceforge.net/projects/geoserver/files/GeoServer/2.13.0/geoserver-2.13.0-war.zip $ unzip geoserver*.zip
There are a number of files in the zip archive, but we’re really just interested in the war file. We need to rename it from geoserver.war to ROOT.war, so that the application server will deploy it as the default application, so we don’t have to type in /geoserver
when we go to the web interface.
$ mv geoserver.war ROOT.war
Now let’s send it to OpenShift to start the build:
$ oc start-build geoserver \--from-file=ROOT.war \--follow Uploading file "ROOT.war" as binary input for the build ... .. Uploading finished build.build.openshift.io/geoserver-1 started Receiving source from STDIN as file ROOT.war Pulling image "registry.access.redhat.com/jboss-webserver-3/webserver31-tomcat8-openshift@sha256:29470583ce1511dbbe8e2552b7f0278d0d74595c42a00632cd23fda1ffab361e" ... Copying all war artifacts from /tmp/src directory into /opt/webserver/webapps for later deployment... '/tmp/src/ROOT.war' -> '/opt/webserver/webapps/ROOT.war' Copying all war artifacts from /tmp/src/deployments directory into /opt/webserver/webapps for later deployment... Pushing image docker-registry.default.svc:5000/geoserver/geoserver:latest ... Pushed 5/7 layers, 72% complete Pushed 6/7 layers, 88% complete Pushed 7/7 layers, 100% complete Push successful
This uploads the war file and starts the build process. You’ll see it pause for a few moments while the file is uploaded. This may be pretty quick if your OpenShift is local, but could be a few minutes, depending on your available bandwidth. On my hotel WiFi, the build completed in two minutes.
Now that we have built a new container image, let’s run it:
$ oc new-app geoserver --> Found image 5b17fc9 (2 minutes old) in image stream "geoserver/geoserver" under tag "latest" for "geoserver" docker.io/geoserver/geoserver-1:2784125a Platform for building and running web applications on JBoss Web Server 3.1 - Tomcat v8 Tags: builder, java, tomcat8 * This image will be deployed in deployment config "geoserver" * Ports 8080/tcp, 8443/tcp, 8778/tcp will be load balanced by service "geoserver" * Other containers can access this service through the hostname "geoserver" --> Creating resources ... deploymentconfig "geoserver" created service "geoserver" created --> Success Run 'oc status' to view your app.
You’re now starting up GeoServer! If you log in to the web interface, it should look something like this:
It will take a minute for the application server to start up and deploy the app, so you can monitor progress by viewing the logs. First, find the name of the Kubernetes pod running the application, then tail the logs of that pod:
$ oc get pods NAME READY STATUS RESTARTS AGE geoserver-1-build 0/1 Completed 0 11m geoserver-1-sk6fd 1/1 Running 0 7m $ oc logs -f pods/geoserver-1-sk6fd ... SNIP ... 2018-05-16 02:32:53,475 [main] INFO org.apache.catalina.startup.Catalina- Server startup in 54708 ms
Ctrl-C to exit the logs once you see this last line, indicating the server has started.
Now that the server is running, we just need to create a route so we can access it. To do this, we expose a service, then query for the generated URL:
$ oc expose svc/geoserver route "geoserver" exposed $ oc get routes NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD geoserver geoserver-geoserver.apps.example.com geoserver 8080-tcp None
Open your favorite web browser to that URL, and you’ve got GeoServer!
One more thing to do… containers are immutable, so any configuration or data stored in GeoServer will be erased when the container is restarted. The pod needs persistent storage. Challenge accepted.
Create a Persistent Volume Claim. Note that this assumes your OpenShift has Persistent Volumes precreated or a Dynamic Volume Provisioner.
$ oc set volume dc/geoserver --add --name=geoserver-data -t pvc \ --claim-name=geoserver-claim \ --claim-size=50G \ --mount-path=/geoserver-data deploymentconfig "geoserver" updated
Once the the container redeploys, it will have an empty persistent volume attached. We need to populate it with the initial data load before GeoServer will recognize it. To do this, we need to run a command in the container. First, get the pods, noting the name of the pod with status Running
, then run the following exec command to copy the data:
$ oc get pods NAME READY STATUS RESTARTS AGE geoserver-1-build 0/1 Completed 0 1h geoserver-5-qg7hd 1/1 Running 0 15m $ oc exec geoserver-5-qg7hd -- bash -c 'cp -rp /deployments/ROOT/data/* /geoserver-data/'
Now that the persistent volume is populated, let’s tell GeoServer to use it:
$ oc set env dc/geoserver GEOSERVER_DATA_DIR=/geoserver-data deploymentconfig "geoserver" updated
After the application finishes redeploying, GeoServer will preserve configuration and data across restarts. Map away!