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:
- Set up an OpenShift 4 cluster: I've been using CodeReady Containers (CRD) to set up this environment (here are the setup instructions).
- 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).
- Get the Tekton CLI (
tkn
) here. - Run the
create-react-app
CLI tool to create the application that we will eventually deploy (this basic React example). - (Optional) Clone the repo to run the example locally by running
npm install
and thennpm 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.
Figure 1: Overview of a running pipeline.">
Click the running pipeline to see more detail, as shown in Figure 2.
Figure 2: The running pipeline's details.">
Once you have the details, you can see your running application in the Topology view, as shown in Figure 3.
Figure 3: The running pod.">
Clicking the icon on the circle's top right opens the application. Figure 4 shows what this will look like.
Figure 4: Running the 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!
Last updated: February 5, 2024