Today’s devops teams have a lot of choices when it comes to CI/CD tools and how they want to configure their environments to take advantage of containers and cloud native technologies. We see a lot of customers using various configurations of Jenkins to build their container images, while using the Twistlock Jenkins plugin to scan the images for vulnerability and compliance issues and enforce policy thresholds before images can be pushed to the team’s registries.

Recently, we’ve seen customers using Jenkins pipelines within their Kubernetes environments. As I was constructing key steps and researching tutorials from around the web, I thought it would be a good idea share what I learned so you can integrate the Twistlock scanner into a pipeline build running in a Kubernetes cluster.

Jenkins Plugin Key Functions

Jenkins is fundamentally architected as a distributed system, with a master that coordinates the builds and agents that do the work. The Kubernetes plugin enables deploying a distributed Jenkins build system to a Kubernetes cluster. Everything required to deploy Jenkins to a Kubernetes cluster is nicely packaged in the Jenkins Helm chart.

A pipeline is a script that tells Jenkins what to do when your pipeline is run. The Kubernetes Plugin for Jenkins lets you control the creation of the Jenkins slave pod from the pipeline, and add one or more build containers to the slave pod to accommodate build requirements and dependencies.

When the Jenkins master schedules the new build, it creates a new slave pod. Each stage of the build is run in a container in the slave pod. By default, each stage runs in the Jenkins slave (jnlp) container, unless other specified. The following diagram shows a slave pod being launched on a worker node using the Java Network Launch Protocol (JNLP) protocol:

A slave pod is composed of at least one container, which must be the Jenkins jnlp container. Your pipeline defines a podTemplate, which specifies all the containers that make up the Jenkins slave pod. You’ll want your podTemplate to include any images that provide the tools required to execute the build. For example, if one part of your app consists of a C library, then your podTemplate should include a container that provides the GCC toolchain, and the build stage for the library should execute within the context of the GCC container.

The Twistlock Jenkins plugin lets you scan images generated in your pipeline. It exposes two functions that can be called from your pipeline script: twistlockScan and twistlockPublish. The first function scans an image according to custom-defined thresholds and other policy parameters. The second function publishes the results to Jenkins UI widgets called portlets so that you can review the scan results. The Twistlock scanner acts as a quality gate that stalls the pipeline when an image doesn’t meet standards set by your security team.

Scripted pipeline

This section provides a pipeline script that you can use as a starting point for your own script.

Prerequisites

  • You have set up a Kubernetes cluster.
  • You have installed Twistlock Console. You can install Twistlock inside or outside of the cluster, as long as any cluster node can reach Console over the network.
  • You have installed Jenkins in your cluster. The Jenkins Helm chart is the easiest path for bringing up Jenkins in a Kubernetes cluster.
  • Install the Twistlock Jenkins plugin.

Pipeline example

The following template can used as a starting point for your own scripted pipeline. If you have an active Twistlock license, you can access a fully functional pipeline template via the Twistlock Documentation Portal.

This example includes key elements for a pipeline that pulls the nginx:stable-alpine image from Docker Hub, and then scans it with the Twistlock scanner.

#!/usr/bin/groovy

podTemplate(label: 'twistlock-example-builder', 
  containers: [
    containerTemplate(
      name: 'jnlp',
      image: 'jenkinsci/jnlp-slave:3.10-1-alpine',
      args: '${computer.jnlpmac} ${computer.name}'
    ),
    containerTemplate(
      name: 'alpine',
      image: 'twistian/alpine:latest',
      command: 'cat',
      ttyEnabled: true
    ),
  ],
  volumes: [ 
    hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'), 
  ]
)
{
  node ('twistlock-example-builder') {

    stage ('Pull image') { 
      container('alpine') {
        sh """
        curl --unix-socket /var/run/docker.sock \ 
             -X POST "http:/v1.24/images/create?fromImage=nginx:stable-alpine"
        """
      }
    }

    stage ('Twistlock scan') { 
        twistlockScan image: 'nginx:stable-alpine',
                    compliancePolicy: 'critical',
                    ...
    }

    stage ('Twistlock publish') {
        twistlockPublish image: 'nginx:stable-alpine',
                    ...
    }
  }
}

This template has the following characteristics:

This podTemplate defines two containers: the required jnlp-slave container and a custom alpine container. The custom alpine container extends the official alpine image by adding the curl package.

The docker socket is mounted into all containers in the pod. For more information about the volumes field, see Pod and container template configuration.

By default, the docker socket lets the root user or any member of the docker group read or write to it. The default user in the jnlp container is jenkins The Twistlock plugin functions need access to the docker socket, so you must add the jenkins user to the docker group. The following listing shows the default permissions for the docker socket:

$ ls -l /var/run/docker.sock
srw-rw----  1 root docker   0 May 30 07:58 docker.sock

The first stage of the build pulls down the nginx image. We run the curl command inside the alpine container because the alpine container was specifically built to provide curl. Note that the twistlockScan and twistlockPublish functions cannot be run inside the container NAME block . The must be run in the default jnlp container context.

There is a lot of debate about docker-in-docker, especially with respect to CI/CD pipelines. In most cases, docker-in-docker is not required for build pipelines. In this example, we run docker commands using the API exposed by the docker socket. Alternatively, we could use a container with just the Docker client installed.

The second stage runs the Twistlock scanner on the nginx image in the default jnlp container.

Conclusion

I hope this is a helpful resource to get you started with Jenkins pipelines on K8s. If you interested in learning more about how the Twistlock Jenkins plugin works, I recommend reading How Jenkins, Docker & Twistlock Delivers on the Promise of Continuous Delivery.

Related Jenkins Plugin Posts:

  • How Jenkins, Docker & Twistlock Delivers on the Promise of Continuous Delivery
  • Twistlock Jenkins Plugin and Time-Based Vulnerability Blocking : 2.4 Deep Dive
  • CVE-2018-1000142: Jenkins Github Pull Request Builder Vulnerability Explained
  • ← Back to All Posts Next Post →