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 git@github.com: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.

s2i Docker label
s2i Docker label

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,

  1. Running nginx in a non standard port, like 8080, because only root can run it in 80.
  2. 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.
  3. Change the ownership of nginx config, log and process ID files to user 1001.
s2i build steps for non root user
s2i build steps for non root user

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.

Build a custom s2i image for nginx
Build a custom s2i image for nginx

 

Download code used in this post.

Test driving our s2i image

Let’s run the s2i image.

S2I Nginx usage
S2I Nginx usage

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.

Build app image from S2I image
Build app image from S2I image

To run the resulting intermediate image “test-nginx”,

Run s2i image
Run s2i image
testing S2I intermediate image on local
testing S2I intermediate image on local

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.

Import s2i image into OpenShift cluster
Import s2i image into OpenShift 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.

OpenShift create new app from nginx s2i image
OpenShift create new app from nginx s2i image

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.

New app gives error.
New app gives error.

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.

Assemble image from s2i assemble script
Assemble image from s2i assemble script

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.

s2i run script
s2i run script

To build an s2i image, you only need a “Dockerfile”, an “assemble” script and a “run” script. The only other conditions are,

  1. Making sure your process and the directories/files it operates upon have non-root permissions.
  2. 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.

save-artifacts script

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.

A simple save-artifacts script which saves an empty file across builds
A simple save-artifacts script which saves an empty file across builds

When rebuilding the image next time, you can pick it up in the “assemble” script and use it from a predefined location,

Restore a saved artifact when assembling a build
Restore a saved artifact when assembling a build

Here we save the file in /etc/nginx but this is dependent on the application you’re building, but you get the idea!

 

usage script

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.

docker OKD OpenShift s2i

Download code used in this post.