Modern web applications on OpenShift, Part 4: Openshift Pipelines

When I wrote part 3 of this series, Modern web applications on OpenShift: Part 3 — OpenShift as a development environment, I said that was the final part. However, there is new tech that fits in very nicely with deploying modern Web Applications to OpenShift, so part 4 is necessary. As a refresher, in the first article, we looked at how to deploy a modern web application using the fewest commands. In the second part, we took a deeper look into how the new source-to-image (S2I) web app builder works and how to use it as part of a chained build. In the third, we took a look at how to run your app’s “development workflow” on Red Hat OpenShift. This article talks about OpenShift Pipelines and how this tool can be used as an alternative to a chained build.

What is OpenShift Pipelines?

OpenShift Pipelines are a cloud-native, continuous integration and delivery (CI/CD) solution for building pipelines using Tekton. Tekton is a flexible, Kubernetes-native, open source CI/CD framework that enables automating deployments across multiple platforms (Kubernetes, serverless, VMs, etc) by abstracting away the underlying details.

This post assumes some knowledge of Pipelines, so if you are new to this technology, check out this official tutorial first.

Setting up your environment

To effectively follow this article, there is some initial setup:

  1. Set up an OpenShift 4 cluster: I've been using CodeReady Containers (CRD) to set up this environment (here are the setup instructions).
  2. Install the Pipeline Operator once your cluster is up and running, which involves only a couple of clicks (here is how to install the Operator).
  3. Get the Tekton CLI (tkn) here.
  4. Run the create-react-app CLI tool to create the application that we will eventually deploy (this basic React example).
  5. (Optional) Clone the repo to run the example locally by running npm install and then npm start.

The application repo also has a k8s directory, which contains Kubernetes/OpenShift YAMLs that can be used to deploy the application. You can find the Tasks, ClusterTasks, Resources and Pipelines that we will be creating in this repo.

Getting started

We first need to create a new project on our OpenShift cluster for this example. The new project will be called webapp-pipeline. Create this new project by calling:

$ oc new-project webapp-pipeline

The naming here is important for this tutorial, so if you decide to change it, pay attention to where I call out that the project name, and update accordingly. From here, this article will work backward. We will create all of the small pieces that make up the pipeline first, and then we will create the pipeline.

So, first up…

Tasks

Let’s create a couple of tasks that can help us deploy our application later as part of our pipeline. The first task, apply_manifests_task, is responsible for applying the Kubernetes resource (service, deployment, and route) YAMLs that are in the applications k8s directory. The second task, update_deployment_task, is responsible for updating the deployed image with the new image our pipeline creates.

Don’t worry too much about these right now. These tasks are more like utilities, and we will see them in a little while. For now, create these tasks with:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/tasks/update_deployment_task.yaml
$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/tasks/apply_manifests_task.yaml

You can then use the tkn CLI to make sure they were created:

$ tkn task ls

NAME                AGE
apply-manifests     1 minute ago
update-deployment   1 minute ago

Note: These tasks are local to your current project.

Cluster tasks

A cluster task is pretty much the same thing as a task. It is still a reusable collection of steps that, when combined, perform a specific task, except that cluster task is available to the whole cluster. To see a list of the cluster tasks that came pre-installed when you added the pipeline Operator, use the tkn CLI again:

$ tkn clustertask ls

NAME                       AGE
buildah                    1 day ago
buildah-v0-10-0            1 day ago
jib-maven                  1 day ago
kn                         1 day ago
maven                      1 day ago
openshift-client           1 day ago
openshift-client-v0-10-0   1 day ago
s2i                        1 day ago
s2i-go                     1 day ago
s2i-go-v0-10-0             1 day ago
s2i-java-11                1 day ago
s2i-java-11-v0-10-0        1 day ago
s2i-java-8                 1 day ago
s2i-java-8-v0-10-0         1 day ago
s2i-nodejs                 1 day ago
s2i-nodejs-v0-10-0         1 day ago
s2i-perl                   1 day ago
s2i-perl-v0-10-0           1 day ago
s2i-php                    1 day ago
s2i-php-v0-10-0            1 day ago
s2i-python-3               1 day ago
s2i-python-3-v0-10-0       1 day ago
s2i-ruby                   1 day ago
s2i-ruby-v0-10-0           1 day ago
s2i-v0-10-0                1 day ago

We will now create two cluster tasks. The first creates an S2I image and pushes it into the internal OpenShift registry, and the second builds our NGINX-based image using the contents of our built application

Creating and pushing the image

For the first cluster task, we follow the part of the same process that we used in the previous article about chain builds. There, we used an S2I image (ubi8-s2i-web-app) to “build” our web application. This action resulted in an image that was stored in the internal OpenShift registry. We will use that web-app S2I image to create a DockerFile for our application and then use Buildah to actually build and push that image into the internal OpenShift registry: This is actually what OpenShift does if you deploy your applications with NodeShift.

If you’re wondering how I knew that I needed all of those steps, the answer is that I didn’t. I copied the official Node.js version and updated it for my needs.

Now, create the s2i-web-app cluster task:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/clustertasks/s2i-web-app-task.yaml

I won’t go into each of the entries in that file but I do want to point out a particular parameter: OUTPUT_DIR:

params:
      - name: OUTPUT_DIR
        description: The location of the build output directory
        default: build

This parameter defaults to build, which is where React puts its built content. Different frameworks could have different locations. For example, Ember uses dist. The output of this first cluster task will be an image that contains our built HTML, JavaScript, and CSS.

Build the NGINX-based image

For the second cluster task, we need to create the task that builds our NGINX-based image using the contents of our built application. This is basically the chained build part of the aforementioned article.

To do this, create the webapp-build-runtime cluster task the same as with the other one:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/clustertasks/webapp-build-runtime-task.yaml

If you looked at the code for these cluster tasks, you would notice that we are not specifying what Git repo we are working with, or what image names we are creating. We only specify that we are passing in a Git repo or an image, for example, and that we are outputting an image. This process allows us to reuse these cluster tasks with different applications.

This leads us nicely into…

Resources

Since we just learned that our cluster tasks are meant to be generic as possible, we need to create resources to use as inputs (the Git repo) and outputs (the resulting images). The first resource we need is the Git repo where our application is. This resource can look something like this:

# This resource is the location of the git repo with the web application source
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  name: web-application-repo
spec:
  type: git
  params:
    - name: url
      value: https://github.com/nodeshift-starters/react-pipeline-example
    - name: revision
      value: master

This PipelineResource is of the git type. We can see in the params section that the url targets a specific repo and that we also specify the master branch (this is optional, but I’m including it for completeness).

The next resource we need is an image where we will store the result of the s2i-web-app task. This process might look something like:

# This resource is the result of running "npm run build",  the resulting built files will be located in /opt/app-root/output
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  name: built-web-application-image
spec:
  type: image
  params:
    - name: url
      value: image-registry.openshift-image-registry.svc:5000/webapp-pipeline/built-web-application:latest

This PipelineResource is of the image type and the value of url points to the internal OpenShift Image Registry—specifically the one in the webapp-pipeline namespace. If you are using a different namespace, then change that value accordingly.

The last resource will also be an image type, and this will be the resulting NGINX image that we will eventually deploy:

# This resource is the image that will be just the static html, css, js files being run with nginx
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
  name: runtime-web-application-image
spec:
  type: image
  params:
    - name: url
      value: image-registry.openshift-image-registry.svc:5000/webapp-pipeline/runtime-web-application:latest

Again, this resource will store the image inside the internal OpenShift registry in the webapp-pipeline namespace.

To create all of these resources at once, run this create command:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/resources/resource.yaml

You can then see the created resources using:

$ tkn resource ls

The pipeline

Now that we have all of the pieces, let’s put them together in our pipeline. You can create the pipeline by running the following command:

$ oc create -f https://raw.githubusercontent.com/nodeshift/webapp-pipeline-tutorial/master/pipelines/build-and-deploy-react.yaml

Before we run this command, let’s take a look at the pieces. First, the name:

apiVersion: tekton.dev/v1alpha1
kind: Pipeline
metadata:
  name: build-and-deploy-react

Then, in the spec section, we see specify the resources that we created earlier:

spec:
  resources:
    - name: web-application-repo
      type: git
    - name: built-web-application-image
      type: image
    - name: runtime-web-application-image
      type: image

We then create tasks for our pipeline to run. The first task we want to run is the s2i-web-app cluster task we created earlier:

tasks:
    - name: build-web-application
      taskRef:
        name: s2i-web-app
        kind: ClusterTask

This task takes an input (which is the git resource) and an output (which is the built-web-application-image resource). We also pass a parameter to tell our cluster task that we don’t need to verify TLS because we are using self-signed certificates:

resources:
        inputs:
          - name: source
            resource: web-application-repo
        outputs:
          - name: image
            resource: built-web-application-image
      params:
        - name: TLSVERIFY
          value: "false"

The next task has a similar setup, but this time calls the webapp-build-runtime cluster task we created earlier:

name: build-runtime-image
    taskRef:
      name: webapp-build-runtime
      kind: ClusterTask

Similar to our previous task, we are passing a resource, but this time it is the built-web-application-image (this was the output of our previous task). Again, we specify an image as the output. This task should run after the previous task, so we add the runAfter field:

resources:
        inputs:
          - name: image
            resource: built-web-application-image
        outputs:
          - name: image
            resource: runtime-web-application-image
        params:
        - name: TLSVERIFY
          value: "false"
      runAfter:
        - build-web-application

The next two tasks are responsible for applying the service, route, and deployment YAML files that live in the web application’s k8s directory, and then updating the deployment with the newly created image. These are the two cluster tasks we defined in the beginning.

Run the pipeline

Now that all of the pieces are created, we can finally run our new pipeline with the following command:

$ tkn pipeline start build-and-deploy-react

At this point, the CLI will become interactive, and you will need to choose the appropriate resources at each prompt. For the git resource, choose web-application-repo. Then, choose built-web-application-image for the first image resource and runtime-web-application-image for the second image resource:

? Choose the git resource to use for web-application-repo: web-application-repo (https://github.com/nodeshift-starters/react-pipeline-example)
? Choose the image resource to use for built-web-application-image: built-web-application-image (image-registry.openshift-image-registry.svc:5000/webapp-pipeline/built-web-
application:latest)
? Choose the image resource to use for runtime-web-application-image: runtime-web-application-image (image-registry.openshift-image-registry.svc:5000/webapp-pipeline/runtim
e-web-application:latest)
Pipelinerun started: build-and-deploy-react-run-4xwsr

Check the status of the pipeline by running:

$ tkn pipeline logs -f

After the pipeline has finished and the application is deployed, get the exposed route with this little command:

$ oc get route react-pipeline-example --template='http://{{.spec.host}}'

For a more visual approach, we can look at our pipeline in the web console’s Developer view and click the Pipelines section, as shown in Figure 1.

Pipeline overview

Click the running pipeline to see more detail, as shown in Figure 2.

Pipeline detail

Once you have the details, you can see your running application in the Topology view, as shown in Figure 3.

Web App Topology View

Clicking the icon on the circle’s top right opens the application. Figure 4 shows what this will look like.

Running React Application

Wrapping up

In this article, we saw how to mimic the chained-build template approach with the use of OpenShift Pipelines. You can find all of the things we created in this repo. There are also some GitHub issues there for adding an example with another framework besides React, so feel free to contribute!