Getting Started with Tekton Part 2: Building a Container
Brian McClain
In part one of this guide, you learned how to install Tekton on Minikube, as well as what a basic Task
looks like. In part two, you’ll create a more complex Task
, which will use Kaniko to build and publish a container image. After that, you’ll learn how to use a preexisting Task
and provide parameters to build your code using Cloud Native Buildpacks.
Before You Begin
If you went through the lessons in part one of this guide, you’re all set! This guide picks up where that guide left off, using the same Tekton installation on top of Minikube, with the same secrets, service accounts, and other resources defined. If you haven’t gone through part one yet, make sure you start there.
Building a Container with Kaniko
Since Tekton is a tool for automating CI/CD pipelines, you probably want to learn how to create and publish container images. For this example, you’ll use Kaniko, a tool used to build container images from a Dockerfile on top of Kubernetes. Kaniko provides its own container image that you can use as a base. By adding your own code and Dockerfile, Kaniko will build and publish a container image based on that Dockerfile.
You can see the complete example here on GitHub.
First, since you’ll be pushing the resulting container image to Docker Hub, you’ll need to create a service account that uses the secret that you created earlier:
apiVersion: v1
kind: ServiceAccount
metadata:
name: dockerhub-service
secrets:
- name: dockercreds
Next, you’ll need to define one input for the code that will be built, and one output for where to publish the container image:
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: sinatra-hello-world-git
spec:
type: git
params:
- name: revision
value: main
- name: url
value: https://github.com/BrianMMcClain/sinatra-hello-world
This introduces a new concept—a PipelineResource
, e—which defines an input into, or an output from, a Task
. If you want to learn more, make sure to check out the PipelineResource
documentation. This PipelineResource
is of type git
, which points to the branch named main
of the code to build on GitHub. It also gives it the name “sinatra-hello-world-git”, which is what you’ll use to reference it later on in the example.
You’ll need one other PipelineResource
to define where to publish the container image:
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: sinatra-hello-world-tekton-demo-image
spec:
type: image
params:
- name: url
value: <DOCKER_USERNAME>/sinatra-hello-world-tekton-demo
This PipelineResource
is of type image
, as in a container image. It’s also been given the name “sinatra-hello-world-tekton-demo-image”. In this case, it simply takes the image name and tag. Since no full URL is provided, it’s assumed that it will be published to Docker Hub, but you can also point to your own container registry.
Note: Make sure to replace <DOCKER_USERNAME> with your Docker Hub username
With your input and output defined, it’s time to create the Task
that will build the container. Take some time to carefully read this through:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: build-docker-image-from-git-source
spec:
params:
- name: pathToDockerFile
type: string
description: The path to the dockerfile to build
default: $(resources.inputs.docker-source.path)/Dockerfile
- name: pathToContext
type: string
description: |
The build context used by Kaniko
(https://github.com/GoogleContainerTools/kaniko#kaniko-build-contexts)
default: $(resources.inputs.docker-source.path)
resources:
inputs:
- name: docker-source
type: git
outputs:
- name: builtImage
type: image
steps:
- name: build-and-push
image: gcr.io/kaniko-project/executor:v0.17.1
# specifying DOCKER_CONFIG is required to allow kaniko to detect docker credential
env:
- name: "DOCKER_CONFIG"
value: "/tekton/home/.docker/"
command:
- /kaniko/executor
args:
- --dockerfile=$(params.pathToDockerFile)
- --destination=$(resources.outputs.builtImage.url)
- --context=$(params.pathToContext)
Here, a new Task
named “build-docker-image-from-git-source” is created. The best way to understand this is to walk through the spec step by step.
First, there are two params
that the Task
will expect:
pathToDockerFile
— Where the Dockerfile is in your code, defaulting to the root directory.pathToContext
— The directory in which Kaniko should look for your code. If no alternative directory is provided, it assumes that the root directory of your code is the build context.
Next, it defines two resources
that it expects. It expects one input
(which it will refer to as “docker-source”) of type git
. It also expects one output (referred to as builtImage
) of type image
. As a reminder, a Task
is simply outlining what inputs and output it expects, but it’s not yet defining them. You might expect that these will match the two PipelineResource
objects that were defined earlier, and you’d be right. The final piece of YAML that you’ll define later will tie the two together.
Finally, the Task
needs to define what steps
to take. Since Kaniko contains all the logic it needs inside the container image, there’s just a single step. Using the Kaniko container image, this step runs the /kaniko/executor
command with three flags: --dockerfile
, --destination
, and --context.
Each of these flags takes in the information defined in the params
and resources
sections.
Phew, that was a lot to digest. Take a moment to make sure you understand each of these sections. At a high level, this Task takes two parameters with two inputs and runs one executable.
There’s one final piece, which is the TaskRunner
to run this Task
:
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: build-docker-image-from-git-source-task-run
spec:
serviceAccountName: dockerhub-service
taskRef:
name: build-docker-image-from-git-source
params:
- name: pathToDockerFile
value: Dockerfile
resources:
inputs:
- name: docker-source
resourceRef:
name: sinatra-hello-world-git
outputs:
- name: builtImage
resourceRef:
name: sinatra-hello-world-tekton-demo-image
This TaskRun
object says that you want to run the build-docker-image-from-git-source
Task
that you just defined and provide the two PipelineResource
objects that you defined as resources. This is how Tekton knows that it should use the sinatra-hello-world-git
PipelineResource
for the docker-source
.
One other thing to notice is that the pathToDockerFile
parameter was defined, despite being the same as the default value. This is done to show how params
are defined in TaskRun
objects, but note as well that pathToContext
is omitted. If params
have a default value, they do not necessarily need to be defined in your TaskRun
.
If you want an easy way apply this all at once, you can store your Docker Hub username in a Bash variable:
export DOCKER_USERNAME=<DOCKERHUB_USERNAME>
Then you can run the following one-liner to apply all of the objects at once:
wget -O - https://raw.githubusercontent.com/BrianMMcClain/tekton-examples/main/kaniko-task.yml | sed -e "s/\<DOCKER_USERNAME\>/$DOCKER_USERNAME/" | kubectl apply -f -
Once applied, make sure to check the status of the TaskRun
using the Tekton CLI:
tkn taskrun describe build-docker-image-from-git-source-task-run
ame: build-docker-image-from-git-source-task-run
Namespace: default
Task Ref: build-docker-image-from-git-source
Service Account: dockerhub-service
Timeout: 1h0m0s
Labels:
app.kubernetes.io/managed-by=tekton-pipelines
tekton.dev/task=build-docker-image-from-git-source
🌡️ Status
STARTED DURATION STATUS
8 seconds ago --- Running
📨 Input Resources
NAME RESOURCE REF
∙ docker-source sinatra-hello-world-git
📡 Output Resources
NAME RESOURCE REF
∙ builtImage sinatra-hello-world-tekton-demo-image
⚓ Params
NAME VALUE
∙ pathToDockerFile Dockerfile
🦶 Steps
NAME STATUS
∙ image-digest-exporter-grgxm ---
∙ git-source-sinatra-hello-world-git-2w7hp ---
∙ create-dir-builtimage-dzt9g ---
∙ build-and-push ---
🚗 Sidecars
No sidecars
In this case, it looks like the Status
is already Running
, great! Take a look at the logs to monitor the build:
tkn taskrun logs build-docker-image-from-git-source-task-run -f
If all goes well, once the logs finish, you should see your new image up in Docker Hub!
Cloud-Native Buildpacks
So far, you’ve been defining your own tasks and steps to run. However, one of the benefits of Tekton’s design is that since each component is shareable through YAML files, you can plug in a Task
developed by someone else. For this example, you’ll be bringing in a Task
that’s already defined, specifically one to use Cloud Native Buildpacks. If you’re unfamiliar with Cloud Native Buildpacks, make sure to check out Cloud Native Buildpacks: What Are They?.
To install the Task
, you can use kubectl apply
, passing the URL to the YAML directly:
kubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/master/buildpacks/buildpacks-v3.yaml
Much like how you can use the Tekton CLI to describe a TaskRun
, you can also use it to describe a Task
to see what resources, parameters, and steps it defines:
tkn task describe buildpacks-v3
Name: buildpacks-v3
Namespace: default
📨 Input Resources
NAME TYPE
∙ source git
📡 Output Resources
NAME TYPE
∙ image image
⚓ Params
NAME TYPE DESCRIPTION DEFAULT VALUE
∙ BUILDER_IMAGE string The image on which ... ---
∙ CACHE string The name of the per... empty-dir
∙ USER_ID string The user ID of the ... 1000
∙ GROUP_ID string The group ID of the... 1000
∙ PROCESS_TYPE string The default process... web
∙ SOURCE_SUBPATH string A subpath within th...
🦶 Steps
∙ prepare
∙ detect
∙ analyze
∙ restore
∙ build
∙ export
🗂 Taskruns
NAME STARTED DURATION STATUS
build-spring-api-with-buildpacks 18 hours ago 7 minutes Succeeded
Here you can see this Task
expects an input resource of type git
and an output resource of type image
. You can define these just as you did in the previous example. For this example, you’ll be building a different application, in Spring. Start by creating the Service Account to authenticate against Docker Hub, the input git
resource, and the output image
resource:
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dockerhub-service
secrets:
- name: regcred # Create secret for your container registry
---
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: spring-api-git
spec:
type: git
params:
- name: revision
value: main
- name: url
value: https://github.com/BrianMMcClain/spring-boot-api-demo
---
apiVersion: tekton.dev/v1alpha1
kind: PipelineResource
metadata:
name: spring-api-tekton-demo
spec:
type: image
params:
- name: url
value: <DOCKER_USERNAME>/spring-api-tekton-demo
This should all look familiar from the previous example. The service account uses the secret defined at the beginning of the guide, the git
PipelineResource
points to the code that you’ll be building, and the image PipelineResource
will tell Tekton where to send the resulting image.
Finally, define the TaskRun
to tie it all together:
apiVersion: tekton.dev/v1alpha1
kind: TaskRun
metadata:
name: build-spring-api-with-buildpacks
spec:
serviceAccountName: dockerhub-service
taskRef:
name: buildpacks-v3
inputs:
resources:
- name: source
resourceRef:
name: spring-api-git
params:
- name: BUILDER_IMAGE
value: cloudfoundry/cnb:bionic
outputs:
resources:
- name: image
resourceRef:
name: spring-api-tekton-demo
As you might have expected, this denotes your two PipelineResource
objects as the input and output resources. It also declares that you’ll be using the cloudfoundry/cnb:bionic
image for the buildpack builder.
As with the previous example, you can apply this all at once by first storing your Docker Hub username in a Bash variable:
export DOCKER_USERNAME=<DOCKERHUB_USERNAME>
Then you can apply the YAML directly:
wget -O - https://raw.githubusercontent.com/BrianMMcClain/tekton-examples/main/cnb-spring-api-demo.yml | sed -e "s/\<DOCKER_USERNAME\>/$DOCKER_USERNAME/" | kubectl apply -f -
Check the status with tkn taskrun describe
:
tkn taskrun describe build-spring-api-with-buildpacks
Name: build-spring-api-with-buildpacks
Namespace: default
Task Ref: buildpacks-v3
Service Account: dockerhub-service
Timeout: 1h0m0s
Labels:
app.kubernetes.io/managed-by=tekton-pipelines
tekton.dev/task=buildpacks-v3
🌡️ Status
STARTED DURATION STATUS
2 seconds ago --- Running(Pending)
📨 Input Resources
NAME RESOURCE REF
∙ source spring-api-git
📡 Output Resources
NAME RESOURCE REF
∙ image spring-api-tekton-demo
⚓ Params
NAME VALUE
∙ BUILDER_IMAGE cloudfoundry/cnb:bionic
🦶 Steps
NAME STATUS
∙ analyze ---
∙ detect ---
∙ prepare ---
∙ export ---
∙ build ---
∙ restore ---
∙ git-source-spring-api-git-sg9vs ---
∙ create-dir-image-8fk7w ---
∙ image-digest-exporter-sxrxt ---
🚗 Sidecars
No sidecars
You can also follow along with the logs with tkn taskrun logs
:
tkn taskrun logs build-spring-api-with-buildpacks -f
Once complete, you’ll see your newly created container image up in Docker Hub! Note that there was never a Dockerfile created or any other set of instructions on how to build this container. Instead, Cloud Native Buildpacks looked at your code and determined what it needed in terms of runtime, dependencies, etc.
Keep Learning
There’s still more to learn, and the best place to go next is the official documentation. There are also some great examples for those looking to get some hands-on learning.