OpenShift s2i deep dive
Learn about OpenShift’s killer feature, s2i, how it works and how to create your own base builder image and use it for s2i deployments.
We saw in the previous posts how we create apps in OpenShift. Also, we concluded the last post mentioning about source-to-image strategy. We shall do a deep dive of S2I in this post, by finding out how it works and how to create our own customized S2I image.
First, let’s download the latest s2i binary from here.
Download code used in this post.
A simple OpenShift s2i build
We will begin by dissecting a simple s2i image which already exists. Let’s clone the s2i code.
$ git clone firstname.lastname@example.org:openshift/source-to-image.git
This contains an Nginx souce to image specification. The Dockerfile is simple. It starts off with some metadata required for any source-to-image. This is specified by the
LABEL directive of docker.
The s2i.scripts-url is of note here. It contains the path of all the scripts which make source to image work. Then, we installed the required software, which in our case is Nginx for the most part. OpenShift normally does not run a process in a container as root. This holds true for s2i images as well. We take steps in the Dockerfile to run nginx as a non root user. This involves,
- Running nginx in a non standard port, like 8080, because only root can run it in 80.
- Assume a non root user with UID and GID of 1001. This is because Linux reserves the first 100 UIDs and any new user starts from 1000.
- Change the ownership of nginx config, log and process ID files to user 1001.
You can see that the CMD is a usage script. So, if you spawn an S2I container instance, it won’t run nginx. Let’s go ahead and build this s2i image. NOTE make sure you use your namespace instead of “lakshminp” when building the images.
Download code used in this post.
Test driving our s2i image
Let’s run the s2i image.
You can see that it runs the usage script and exits. Let’s use it the way it was intended to. The command is,
$ s2i build <source-code> <s2i-image> <resulting-image>
The above example ships with sample test code to test the nginx image, and the name of the s2i image we built was “lakshminp/nginx-s2i”. Let’s test the s2i image we built just now.
To run the resulting intermediate image “test-nginx”,
Time to move our s2i image to an OpenShift cluster. Before we start using it, we have to import the s2i image into our cluster.
That should import our custom s2i image. Now, let’s create an app from this image. I’ve been playing with a CSS framework called Tailwind for one of my projects. Let’s try deploying an app using our s2i image and a sample Tailwind HTML scaffold.
NOTE This might not create a new pod due to the permission issues associated with running Nginx in an OpenShift cluster, in spite of fixing the permissions in the Dockerfile. It happened for me. I’m still not sure why.
I had to go with the official Nginx s2i image, so I changed my command to,
$ oc new-app centos/nginx-18-centos7~https://github.com/adamwathan/theming-tailwind-demo.git --name tw2
The workings of both images are almost the same, except some permissions magic happening in the latter.
So, what happened in the last few commands? When OpenShift is given an S2I image and the app source code, it creates a new intermediate image by adding the source code on top of the base S2I image. This is done in the “assemble” script of the S2I image. This is an example of convention over configuration where your code artifacts live in “/tmp/src” from which they are copied to respective locations(in our case, the htdocs directory) when building the intermediate image.
The other important script is the “run” script, which runs the actual process of your container. For our case it will be Nginx. If you are running a Python app, it will be gunicorn. If you are running a PHP app, this will be the php-fpm process. Note that all these processes will run in the foreground.
To build an s2i image, you only need a “Dockerfile”, an “assemble” script and a “run” script. The only other conditions are,
- Making sure your process and the directories/files it operates upon have non-root permissions.
- Running the main process in the foreground.
Other than that, you can pretty much create an s2i image for any stack from any base images(like Alpine and Ubuntu). In order to enhance the developer experience, we add the following optional scripts.
This is used to save the artifacts between builds. By artifacts, I mean stuff other than the source code of your app, like dependency libraries. This might be yout “node_modules” directory if you are building a node.js app or the composer vendor directory if you are using a PHP app. This speeds up build time drastically depending on what you save in the save-artifacts script. You simply tar the required artifacts. The only criterion is, your save-artifacts script should emit a tar archive stream of saved artifacts.
When rebuilding the image next time, you can pick it up in the “assemble” script and use it from a predefined location,
Here we save the file in /etc/nginx but this is dependent on the application you’re building, but you get the idea!
It is useful to show information on what the s2i image is all about and the fact that it is an s2i image. If you recall, when we ran the intermediate container on our laptop, it prints the contents of the “usage” script and terminates. This can be simple information about what the s2i image is all about and how to consume it to create new OpenShift apps.
We shall see a more complex example of an s2i build and proceed to create our own in the next post.