Digital Developer Conference: Hybrid Cloud. On Sep 22 & 24, start your journey to OpenShift certification. Free registration

Build and deploy a Hello World application on Kubernetes using a Tekton pipeline

Container-based software development is growing. Since it’s easy to replicate the environment, developers generally create applications on their desktop, and debug and test them locally. Later, they build and deploy the application to a Kubernetes cluster.

In this tutorial, I show you two ways to deploy an application to a Kubernetes cluster on IBM Cloud:

  • Using a kubectl CLI without a DevOps pipeline
  • Using a Tekton Pipeline (which is a Kubernetes-style continuous integration and continuous delivery (CI/CD) pipeline)

Prerequisites

To complete this tutorial, you need to:

  • Create an IBM Cloud account.
  • Get an instance of Kubernetes Service on IBM Cloud, which should take approximately 20 minutes.
  • Access a Kubernetes cluster through the kubectl CLI. To access the instructions, go to the IBM Cloud dashboard > [your cluster] > Access.
  • Create a namespace on the IBM Cloud container registry. To do so, go to your IBM Cloud dashboard and click Navigation > Kubernetes > Registry > Namespaces.
  • Configure the Git CLI. Clone the repository to your workstation using the following command:

    git clone https://github.com/IBM/deploy-app-using-tekton-on-kubernetes.git
    

Estimated time

After your prerequisites are configured, this tutorial takes about 40 minutes.

Build and deploy an application on IBM Cloud Kubernetes Service using kubectl

To build and deploy an application on a Kubernetes cluster, perform the following steps:

  1. Write a Dockerfile for your application and build the container image using Dockerfile.
  2. Upload the built container image to the accessible container registry.
  3. Create a Kubernetes deployment using the container image and deploy the application to an IBM Cloud Kubernetes Service cluster using configuration (YAML) files.

For this tutorial, we have taken a simple Hello World! Node.js application to deploy on Kubernetes as shown. The following is code from our sample app; use one that you have on hand.

  const app = require('express')()

  app.get('/', (req, res) => {
    res.send("Hello from Appsody!");
  });

  var port = 3300;

  var server = app.listen(port, function () {
    console.log("Server listening on " + port);
  })

  module.exports.app = app;

The Dockerfile and the deployment configuration deploy.yaml is available in the GitHub repository that you cloned earlier. The steps explained in this section help you to deploy your application to a cluster using CLIs.

1. Set up a deploy target

As a first step, you need to set the correct deploy target for the container image to upload to the accessible container registry. Depending on the region you created your cluster in, your image URL will be in the following format:

  <REGION_ABBREVIATION>.icr.io/<YOUR_NAMESPACE>/<YOUR_IMAGE_NAME>:<VERSION>

The following command tells you the Registry API endpoint for your cluster. You can get the region abbreviation from the output.

   ibmcloud cr api

To get a namespace, use the following command:

    ibmcloud cr namespaces

For example, the deployment target for the US-South region is:

       us.icr.io/test_namespace/sampleapp:1.0

2. Deploy the application

Run the following commands to deploy your application on a Kubernetes cluster:

  cd ~/deploy-app-using-tekton-on-kubernetes/src

  # Build and push it to IBM Cloud Container registry. Following command takes care of build and push to container registry and eliminates the overhead to run docker commands individually.
  ibmcloud cr build -t us.icr.io/test_namespace/sampleapp:1.0 .

  # Verify whether the image is uploaded to the container registry
  ibmcloud cr images

  # Update deploy target in deploy.yaml
  sed -i '' s#IMAGE#us.icr.io/test_namespace/sampleapp:1.0# deploy.yaml

  # Run deploy configuration
  kubectl create -f deploy.yaml

  # Verify output - pod and service should be up and running
  kubectl get pods
  kubectl get service

After successful deployment, your application is accessible at http://<public-ip-of-kubernetes-cluster>:32426/ where you can retrieve the public IP of a Kubernetes cluster from your IBM Cloud dashboard and the port 32426 is defined as nodePort in deploy.yaml.

The steps above showed you how to deploy onto a Kubernetes cluster using CLIs. If you change your application after deployment, you need to rerun the steps.

To build, test, and deploy applications faster and more reliably, you need to automate this entire workflow. Following a CI/CD methodology reduces the overhead of development and manual deployment processes, which can save you significant time and effort.

The next section of this tutorial explains the build and deploy approach using Tekton Pipelines.

Build and deploy an application on IBM Cloud Kubernetes Service using Tekton Pipeline

Tekton is a powerful and flexible Kubernetes-native open source framework for creating CI/CD systems. It allows you to build, test, and deploy across multiple cloud providers or on-premises systems by abstracting away the underlying implementation details.

Before I show you how to use Tekton Pipelines, here are some of the high-level concepts you need to understand:

The Tekton Pipeline project extends the Kubernetes API by five additional custom resource definitions (CRDs) to define pipelines:

  • A task is an individual job and defines a set of build steps such as compiling code, running tests, and building and deploying images.
  • A Taskrun runs the task you defined. With taskrun, it’s possible to execute a single task, which binds the inputs and outputs of the task.
  • Pipeline describes a list of tasks that compose a pipeline.
  • Pipelinerun defines the execution of a pipeline. It references the pipeline to run and which PipelineResource(s) to use as input and output.
  • The PipelineResource defines an object that is an input (such as a Git repository) or an output (such as a Docker image) of the pipeline.

To automate the application’s build and deploy workflow using Tekton Pipelines, follow these steps:

1. Add the Tekton Pipelines component to your Kubernetes cluster

As a first step, add the Tekton Pipelines to your Kubernetes cluster using the following command:

  kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml

The installation creates two pods which you can check using the following command. Make sure to wait until the pods are in a running state.

  kubectl get pods --namespace tekton-pipelines

For more information on this, refer to the Tekton documentation. After completing these steps, your Kubernetes cluster is ready to run Tekton Pipelines. Let’s start by creating the definition of custom resources.

2. Create a PipelineResource

In this tutorial’s example, the source code of the application, Dockerfile, and deployment configuration is available in the GitHub repository that you cloned earlier.

To create the input PipelineResource to access the Git repository, do the following:

In the git.yaml file, define the PipelineResource for the Git repository by doing the following:

  • Specify the resource type as Git.
  • Provide the Git repository URL as url.
  • Provivde revision as the name of the branch of the Git repository to be used.

The complete YAML file is available at ~/tekton-pipeline/resources/git.yaml. Apply the file to the cluster as shown.

  cd ~/tekton-pipeline
  kubectl apply -f resources/git.yaml

3. Create tasks

A task defines the steps of the pipeline. To deploy an application to a cluster using source code in the Git repository, we define two tasks — build-image-from-source and deploy-to-cluster. In the task definition, the parameters used as arguments (args) are referred to as $(inputs.params.<var_name>).

Define build-image-from-source

This task includes two steps:

  1. The list-src step lists the source code from the cloned repository. This is done to verify whether source code is cloned properly.
  2. The build-and-push step builds the container image using Dockerfile and pushes the built image to the container registry. In this example, we use Kaniko to build and push the image. You can use Kaniko, buildah, podman, etc. Kaniko uses the Dockerfile name, its location, and destination to upload the container image as arguments.

All required parameters are passed through parameters. Apply the file to the cluster using the following command:

  kubectl apply -f task/build-src-code.yaml

Define deploy-to-cluster

Now let’s deploy the application in a pod using the built container image, and make it available as a service to access from anywhere. This task uses the deployment configuration located as ~/src/deploy.yaml.

This task includes two steps:

  1. The update-yaml step updates the container image URL in place of IMAGE in the deploy.yaml.
  2. The deploy-app step deploys the application in a Kubernetes pod and exposes it as a service using ~/src/deploy.yaml. This step uses kubectl to create a deployment configuration on a Kubernetes cluster.

All required parameters are passed through parameters.

Apply the file to the cluster as:

  kubectl apply -f task/deploy-to-cluster.yaml

4. Create a pipeline

A pipeline lists the tasks to be executed. It provides the input, output resources, and input parameters required by each task. If there is any dependency between the tasks, that is also addressed.

In the tekton-pipeline/resources/pipeline.yaml:

  • A pipeline uses the above mentioned tasks build-image-from-source and deploy-to-cluster.
  • The runAfter key is used here because the tasks need to be executed one after the other.
  • The PipelineResource (Git repository) is provided through the resources key.

All required parameters are passed through parameters. The parameters value is defined in the pipeline as $(params.imageUrl) which is different than the args in the task definition. Apply this configuration as:

  kubectl apply -f pipeline/pipeline.yaml

5. Create PipelineRun

To execute the pipeline, you need a PipelineRun resource definition, which passes all required parameters. PipelineRun triggers the pipeline, and the pipeline, in turn, creates TaskRuns and so on. In a similar manner, all parameters get substituted down to the tasks.

If a parameter is not defined in the PipelineRun, then the default value gets picked up from the params under spec from the resource definition itself. For example, the pathToDockerfile parameter is used in the task build-image-from-source, but its value is not provided in pipeline-run.yaml. Because of this, its default value defined in ~/tekton-pipeline/build-src-code.yaml is used during the task execution.

In the PipelineRun definition, tekton-pipeline/pipeline/pipeline-run.yaml:

  • References the pipeline application-pipeline created through pipeline.yaml.
  • References the PipelineResource git to use as input.
  • Provides the value of parameters under params which are required during the execution of the pipeline and the tasks.
  • Specifies a service account.

Note that through the pipeline, you can push images to the registry and deploy it to a cluster. You need to ensure that it has the sufficient privileges to access the container registry and the cluster. The credentials for the registry are provided by a service account. You need to define a service account before executing PipelineRun.

Note: Do not apply the PipelineRun file yet because you still need to define the service account for it.

6. Create a service account

To access the protected resources, set up a service account which uses secrets to create or modify Kubernetes resources. The IBM Cloud Kubernetes Service is configured to use IBM Cloud Identity and Access Management (IAM) roles. These roles determine the actions that users can perform on IBM Cloud Kubernetes.

Generate an API key

To generate an API key using IBM Cloud Dashboard, follow the instructions in the IBM Cloud documentation. You can also use the following CLI command to create the API key:

  ibmcloud iam api-key-create MyKey -d "this is my API key" --file key_file.json
  cat key_file.json | grep apikey

Copy the apikey. You will use it in the next step.

Create secrets

To create a secret, use the following code. APIKEY is the one that you created and REGISTRY is the registry API endpoint for your cluster. An example would be us.icr.io.

  kubectl create secret generic ibm-cr-secret --type="kubernetes.io/basic-auth" --from-literal=username=iamapikey --from-literal=password=<APIKEY>

  kubectl annotate secret ibm-cr-secret tekton.dev/docker-0=<REGISTRY>

This creates a secret named ibm-cr-secret which is then used in the configuration file for the service account.

In the configuration file tekton-pipeline/pipeline/service-account.yaml:

  • The ServiceAccount resource uses the secret ibm-cr-secret.
  • As per the definition of a secret resource, the newly built secret is populated with an API token for the service account.

The next step is to define roles. A role can only be used to grant access to resources within a single namespace. You must include appropriate resources and apiGroups in rules or it will fail because of access issues. A role binding grants the permissions defined in a role to a user or set of users. It holds a list of subjects (users, groups, or service accounts) and a reference to the role being granted.

Apply this configuration as:

  kubectl apply -f pipeline/service-account.yaml

7.Execute the pipeline

Before executing the PipelineRun, modify the imageUrl and the imageTag in tekton-pipeline/pipeline/pipelinerun.yaml. Refer to the Set up deploy target section above to decide on an image URL and tag. If the image URL is us.icr.io/test_namespace/sampleapp and the image tag is 1.0, then update the configuration file to:

  sed -i '' s#IMAGE_URL#us.icr.io/test_namespace/sampleapp# pipeline/pipelinerun.yaml
  sed -i '' s#IMAGE_TAG#1.0# pipeline/pipelinerun.yaml

Now, create the PipelineRun configuration, like so:

  kubectl create -f pipeline/pipeline-run.yaml

This creates a pipeline with the below message on your terminal:

  pipelinerun.tekton.dev/application-pipeline-run created

Check the status of your new pipeline:

  kubectl describe pipelinerun application-pipeline-run

You may need to rerun this command based on the status. It shows the interim status as:

Status:
  Conditions:
    Last Transition Time:  2020-07-09T08:43:53Z
    Message:               Tasks Completed: 0 (Failed: 0, Cancelled 0), Incomplete: 2, Skipped: 0
    Reason:                Running
    Status:                Unknown
    Type:                  Succeeded

   ...
   ...
Events:
   Type     Reason     Age     From     Message
   ----     ------     ---     ----     -------
   Normal Started 33s PipelineRun
   Normal Running 33s PipelineRun Tasks Completed: 0 (Failed: 0, Cancelled 0), Incomplete: 2, Skipped: 0

Note the message where it tells you that “Tasks Completed: 0”.

Once the execution of your pipeline is complete, you should see the following as an output of the describe command.

Events:
   Type     Reason     Age     From     Message
   ----     ------     ---     ----     -------
   Normal Started 2m33s PipelineRun
   Normal Running 2m33s PipelineRun Tasks Completed: 0 (Failed: 0, Cancelled 0), Incomplete: 2, Skipped: 0
   Normal Running 57s   PipelineRun Tasks Completed: 1 (Failed: 0, Cancelled 0), Incomplete: 1, Skipped: 0
   Normal Succeeded 43s PipelineRun Tasks Completed: 2 (Failed: 0, Cancelled 0), Skipped: 0

In case of failure, it shows which task has failed. It also gives you the additional details to check logs. For details about a resource (for instance, the pipeline) use the kubectl describe command to get more information.

  kubectl describe <resource> <resource-name>

8. Verify your results

To verify whether the pod and service is running as expected, check the output of the following commands:

  kubectl get pods
  # Output should be something like this
    NAME                                                                READY   STATUS      RESTARTS   AGE
    app-59dff7b655-7ggbt                                                1/1     Running     0          81s
    application-pipeline-run-build-image-from-source-2m62g-pod-f4eb96   0/3     Completed   0          119s
    application-pipeline-run-deploy-application-kg2jm-pod-89f884        0/3     Completed   0          89s

  kubectl get service
  # Output
    NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
    app          NodePort    xxx.xx.xx.xxx   <none>        3300:32426/TCP   4m51s

After successful execution of PipelineRun, the application is accessible at http://<public-ip-of-kubernetes-cluster>:32426/, where you can retrieve the public IP of your Kubernetes cluster from your IBM Cloud dashboard, and the port 32426 is defined as nodePort in deploy.yaml.

Congratulations! You successfully deployed your application using a Tekton Pipeline. You should now understand the basics of Tekton Pipelines and how to get started on building your own. There are more features available, including webhooks and web-based dashboards. I suggest trying it out with IBM Cloud Kubernetes Service.

Next steps

This tutorial showed you how and why you should use Tekton Pipelines for deploying an application to Kubernetes. Tekton is included in IBM Cloud Pak for Applications, which offer a faster, more secure way to move your business applications to the cloud, in a container-enabled environment. Cloud Pak for Applications is built and supported on Red Hat OpenShift. Explore and try out more with the help of this developer guide.