Tekton Pipelines (v0.11.0-rc2)
Old school cool tekton pipelines.
Looking for a Cloud Native CI/CD pipelines for your Kubernetes cluster? Check out the tekton-pipelines project from the great folks over at Tekton.
Tekton bridges the ever-decreasing gap between Development and Operations a little bit more with their state of the art pipeline runners and command line interface (which lacks in most modern CI/CD solutions imho, finally start and stop pipeline executions from the CLI)! In this first series of blog posts on tekton (part 1 of 3) we'll setup a Tekton Pipeline to release our public tkn docker image.
Setup
Let's deploy a quick kind cluster using our public kind image, install tekton-pipelines then setup a Pipeline and related tekton resources to build and push our docker images using kaniko after passing our Dockerfile through the Haskell Dockerfile Linter.
Tekton
tekton-pipelines v0.11.0-rc2 is available for download from the projects releases.
KinD
Our kind in dind container mounts the local ${PWD}/tekton directory which should contain the tekton-pipelines release.yaml to the containers /tekton directory. The users docker config (${HOME}/.docker/config.json) also needs to be mounted in the container:
docker run --rm -d \
--privileged \
-v ${HOME}/.docker/config.json:/root/.docker/config.json \
-v ${PWD}/tekton:/tekton \
--name tekton \
docker.pkg.github.com/lazybit-ch/kind/kind:v0.7.0
Create the kind cluster: docker exec -it tekton kind create cluster --name tekton then install tekton-pipelines: docker exec -it tekton kubectl apply -f /tekton/release.yaml.
Tip: watch the pods being created in the tekton-pipelines namespace: docker exec -it tekton kubectl get pod -n tekton-pipelines -w.
We need to create an Opaque Secret for the kaniko to access the private registry that we'll be pushing our images to: docker exec -it tekton kubectl -n tekton-pipelines create generic regcred --from-file=/root/.docker/config.json.
Note: if you don't have a docker registry consider running one alongside the kind cluster i.e.: docker exec -it tekton docker run -d -p 5000:5000 --name registry -e REGISTRY_STORAGE_DELETE_ENABLED=true registry:2. In this blog post we'll be pushing images to our official registry.
Tekton Pipelines
tekton-pipelines extends Kubernetes with the Tasks, ClusterTasks, TaskRuns, PipelineResources, Pipelines and PipelineRuns Custom Resources. Breaking down our release pipeline we need to:
- checkout the source code
- lint the Dockerfile
buildthedockerimageloginto theregistrypushthedockerimage
Task
Tasks define steps to execute and each step of the Task is run in a container. The lint Task uses the git PipelineResource as input to the Task then lints the Dockerfile inside a hadolint/hadolint container (tip: by default objects are created under /workspace and the git repository is cloned to the directory named from the spec.inputs.resources[0].name):
apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
name: hadolint
spec:
inputs:
resources:
- name: source
type: git
steps:
- name: hadolint
image: hadolint/hadolint
command: ["/bin/ash"]
args:
- -c
- |
hadolint /workspace/source/Dockerfile
Tip: splitting the lint and build logic into separate Tasks allows us to share the Tasks in various Pipelines.
The build Task specifies Task scoped parameters with defaults, the Task input (git repository clone) and output (image) resources and the steps to build our image using kaniko:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: kaniko
namespace: tekton-pipelines
spec:
params:
- name: dockerfile
type: string
description: The path to the Dockerfile to build
default: /workspace/source/Dockerfile
- name: context
type: string
description: The Kaniko build context
default: /workspace/source
resources:
inputs:
- name: source
type: git
outputs:
- name: image
type: image
steps:
- name: build-and-push
image: gcr.io/kaniko-project/executor:v0.17.1
command:
- /kaniko/executor
args:
- --dockerfile=$(params.dockerfile)
- --destination=$(resources.outputs.image.url)
- --context=$(params.context)
- --cache=false
env:
- name: DOCKER_CONFIG
value: /tekton/home/.docker
volumeMounts:
- mountPath: /tekton/home/.docker/
name: regcred
readOnly: true
volumes:
- name: regcred
secret:
secretName: regcred
Note: our regcred secret is mounted in the Task spec with the required environment set for kaniko to push the docker image.
TaskRuns
PipelineResources are bound to Tasks during TaskRuns. We can simulate our Pipeline by creating the PipelineResources and TaskRun definitions then applying them to the cluster ordering the execution manually:
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: tkn-git
namespace: tekton-pipelines
spec:
type: git
params:
- name: url
value: https://github.com/lazybit-ch/tkn.git
- name: revision
value: master
---
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: hadolint
namespace: tekton-pipelines
spec:
taskRef:
name: hadolint
resources:
inputs:
- name: source
resourceRef:
name: tkn-git
After the Dockerfile is linted the PipelineResources and TaskRun for our build can be created then applied to the cluster:
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: tkn-image
namespace: tekton-pipelines
spec:
type: image
params:
- name: url
value: lazybit.ch/tkn:latest
---
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: kaniko
namespace: tekton-pipelines
spec:
taskRef:
name: kaniko
resources:
inputs:
- name: source
resourceRef:
name: tkn-git
outputs:
- name: image
resourceRef:
name: tkn-image
Note: the kaniko TaskRun reuses the tkn-git PipelineResources that were used in the hadolint TaskRun.
Pipelines
Tekton Pipeline resources order our TaskRuns so we don't have to. Our Pipeline definition will ensure that our lint task is executed before executing our build:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: tkn
spec:
resources:
- name: source
type: git
- name: image
type: image
tasks:
- name: lint
taskRef:
name: hadolint
resources:
inputs:
- name: source
resource: source
- name: build
taskRef:
name: kaniko
resources:
inputs:
- name: source
resource: source
outputs:
- name: image
resource: image
PipelineRuns
The Pipelines are triggered by PipelineRuns. Similar to TaskRuns the PipelineRun objects bind Tasks with PipelineResources and executes the Tasks steps in the order specified in the Pipeline:
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: tkn-run
spec:
pipelineRef:
name: tkn
resources:
- name: source
resourceRef:
name: tkn-git
- name: image
resourceRef:
name: tkn-image
Conclusion
This blog post barely scratches the surface of tekton-pipelines but pretty, pretty cool. I like an "all the things in docker" approach to everything and it only makes sense for modern CI/CD (why use Jenkins to build code in docker containers when you could use the docker containers directly). Hard-coded like it is makes it such that the Pipeline will only execute once, we need to delete then create a new PipelineRun to trigger another execution - stay tuned for our future blog post on tekton-triggers to template PipelineRuns with EventListeners!