Back in My OpenShift Days...
When I first began learning OpenShift two years ago, I was immediately struck by how easy it was to build really simple applications, especially when you were building in Java on JBoss or WildFly. You could literally create a project with just one command and it would generate everything you needed:
$ oc new-app openshift/jboss-eap-7/eap70-openshift~http://github.com/myuser/javacode.git --name=myjbossapp
As I've talked about in my entry on OpenShift on the command line, the new-app command creates a number of Kubernetes resources for you. The command I've used here though is slightly different. In that blog entry, I only specified an image, and a basic one at that:
$ oc new-app docker.io/nginx --name=demo-nginx
So what's different here? The difference is that I'm not only specifying an image, I'm specifying a code repository. The image that I'm specifying is not the image that will get deployed, but rather a base image upon which we will build our final image. OpenShift creates a build config resource that references both the base image and the source repo so that OpenShift knows how to build our target image. When a build is initiated, the source is pulled from the repo, compiled (it knows how to compile Java code if you are using Maven, for example), and a new image is created that extends the base image with the built code placed where it needs to be. As I'll show later, you can also specify a built binary or a directory either containing source or a built binary. If what I specify is a binary, it just skips the step of building the code.
This was all really cool. It made it so that I could easily demo OpenShift to interested parties and literally get the project built and deployed in two commands (the first step was the new-app step shown above, the second step was to kick off a build which would automatically kick off a deploy of the built image). I dug into how OpenShift was doing this, and that is when I discovered that this actually wasn't something built into OpenShift. It was actually a separate tool called s2i, which stands for source-to-image.
You can think of the s2i tool as essentially being an alternative to Dockerfiles for building Docker images. It is an open source tool, and you can find the source on github. s2i is written in Go, the same language that was used to build the kubectl and oc command line tools used for Kubernetes and OpenShift. You just need to have Docker installed if you download the binary of s2i, but you can also build it from source if you have Go installed.
Technically, s2i can work with any base image, but it is intended to work with images that are commonly called builder images, which is to say that they have code inside of them that the s2i tool uses to do its job. This is intentional, because often when you are using these builder images, how your code gets deployed is related to what base image you are using. In my above example, I used a JBoss EAP 7 image that was bundled with OpenShift. This image has some s2i code inside that knows how to build a Java war or ear project if it uses a common build tool like Maven.
A Simple Example
If you have s2i installed, you can easily see this work. For the purposes of this example, I'll deploy on a JBoss EAP 7 image pulled from Red Hat's developer registry. You can sign up for a developer account on Red Hat's developer site to get access to this registry (not to mention lots of other developer tools and resources that Red Hat gives out for free). Once you are signed up, you first need to login to the registry:
$ docker login registry.access.redhat.com Username: openshiftninja Password: <hidden> Login Succeeded
Next I will pull the JBoss EAP 7 image, the same one that gets bundled with OpenShift:
$ docker pull registry.access.redhat.com/jboss-eap-7/eap70-openshift Using default tag: latest latest: Pulling from jboss-eap-7/eap70-openshift 9cadd93b16ff: Pull complete 4aa565ad8b7a: Pull complete 343ba5353b91: Pull complete ed5233aa6fdd: Pull complete 62aae33b9ae2: Pull complete 959eddd043b4: Pull complete Digest: sha256:a6b7475638629f354a3e845c41db1e80764a688df90febf7d1f906dcb204cf68 Status: Downloaded newer image for registry.access.redhat.com/jboss-eap-7/eap70-openshift:latest
stolen borrowed an example helloworld application for demonstrating deploying a simple app onto JBoss using s2i, and so without further ado, we can build the image with a simple command:
$ s2i build https://github.com/tellmejeff/jboss-helloworld.git registry.access.redhat.com/jboss-eap-7/eap70-openshift demo-jboss-app Found pom.xml... attempting to build with 'mvn -e -Popenshift -DskipTests -Dcom.redhat.xpaas.repo.redhatga package --batch-mode -Djava.net.preferIPv4Stack=true ' Using MAVEN_OPTS '-XX:+UseParallelGC -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40 - ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 19.819 s [INFO] Finished at: 2018-07-03T02:45:32+00:00 [INFO] Final Memory: 21M/101M [INFO] ------------------------------------------------------------------------ [WARNING] The requested profile "openshift" could not be activated because it does not exist. Copying all war artifacts from /tmp/src/target directory into /opt/eap/standalone/deployments for later deployment... '/tmp/src/target/helloworld.war' -> '/opt/eap/standalone/deployments/helloworld.war' Copying all ear artifacts from /tmp/src/target directory into /opt/eap/standalone/deployments for later deployment... ... Build completed successfully
This will take a little while, but when the build is complete, you can see the image you created is now in the Docker registry:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE demo-jboss-app latest 6bc354b4fb09 About a minute ago 720MB
Simply running the image like we would any other Docker image is straightforward, and you will see the usual JBoss startup logging, with a message at the bottom telling us our war has been deployed (I've mapped the JBoss 8080 port to 9080 to avoid a port conflict on my host):
$ docker run -p 9080:8080 demo-jboss-app ...
JBoss Bootstrap Environment JBOSS_HOME: /opt/eap JAVA: /usr/lib/jvm/java-1.8.0/bin/java ... 02:50:19,035 INFO [org.jboss.as.server] (ServerService Thread Pool -- 38) WFLYSRV0010: Deployed "helloworld.war" (runtime-name : "helloworld.war") 02:50:19,035 INFO [org.jboss.as.server] (ServerService Thread Pool -- 38) ...
Now if we curl the localhost, we can see our app running (the URL path is localhost:9080/helloworld, which sends back a meta refresh to /helloworld/HelloWorld, so I'm just skipping the refresh here):
$ curl -f -i localhost:9080/helloworld/HelloWorld HTTP/1.1 200 OK Connection: keep-alive X-Powered-By: Undertow/1 Server: JBoss-EAP/7 Content-Type: text/html;charset=ISO-8859-1 Content-Length: 88 Date: Tue, 03 Jul 2018 03:05:00 GMT
<html><head><title>helloworld</title></head><body> <h1>Hello World!</h1> </body></html>
As I mentioned before, you can use a git repo to specify the source, but you can also specify a directory where the source is as well. I have the repository cloned locally, so I can just run this to build the image:
$ s2i build . registry.access.redhat.com/jboss-eap-7/eap70-openshift demo-jboss-app Found pom.xml... attempting to build with 'mvn -e -Popenshift -DskipTests -Dcom.redhat.xpaas.repo.redhatga package --batch-mode -Djava.net.preferIPv4Stack=true ' Using MAVEN_OPTS '-XX:+UseParallelGC -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:MaxMetaspaceSize=100m -XX:+ExitOnOutOfMemoryError' Using Apache Maven 3.3.9 (Red Hat 3.3.9-2.8) Maven home: /opt/rh/rh-maven33/root/usr/share/maven Java version: 1.8.0_161, vendor: Oracle Corporation Java home: /usr/lib/jvm/java-1.8.0-openjdk-22.214.171.124-0.b14.el7_4.x86_64/jre Default locale: en_US, platform encoding: ANSI_X3.4-1968 OS name: "linux", version: "4.4.0-119-generic", arch: "amd64", family: "unix" [INFO] Error stacktraces are turned on. [INFO] Scanning for projects... [INFO] Downloading: https://repo1.maven.org/maven2/org/jboss/eap/quickstarts/quickstart-parent/7.1.0.GA/quickstart-parent-7.1.0.GA.pom ...
I can also specify just the pre-compiled war so that I can skip the compile step:
$ s2i build target registry.access.redhat.com/jboss-eap-7/eap70-openshift demo-jboss-app Copying all war artifacts from /tmp/src directory into /opt/eap/standalone/deployments for later deployment... '/tmp/src/helloworld.war' -> '/opt/eap/standalone/deployments/helloworld.war' Copying all ear artifacts from /tmp/src directory into /opt/eap/standalone/deployments for later deployment... Copying all rar artifacts from /tmp/src directory into /opt/eap/standalone/deployments for later deployment... ... Build completed successfully
That's it for the first installment on s2i, but next week I'll dive into a little more detail on what s2i is doing under the hood and how you can customize its behavior.