Tekton es un framework de CI/CD que se ejecuta dentro de Kubernetes: cada paso de un pipeline es un contenedor, y las tareas, pipelines y ejecuciones son recursos del clúster que se gestionan con kubectl. En este tutorial instalarás Tekton Pipelines y la CLI tkn, crearás tus primeras Task, las combinarás en un Pipeline que clona un repositorio Git y pasa datos entre tareas, y usarás Tekton Triggers para lanzarlo desde un webhook de GitHub.
Requisitos previos
Para seguir esta guía necesitas:
- Un clúster de Kubernetes en una versión reciente y soportada, con acceso de administrador desde
kubectl. - Una
StorageClasspor defecto en el clúster, porque los pipelines usarán volúmenes persistentes para compartir archivos entre tareas. Compruébalo conkubectl get storageclass(una de ellas debe aparecer como(default)). - Una máquina con Ubuntu 24.04 desde la que administras el clúster, por ejemplo un VPS de CubePath, con
kubectlconfigurado.
Todos los recursos de ejemplo se crean en el namespace default.
Conceptos básicos
Tekton define pocos objetos y conviene tenerlos claros antes de empezar:
| Recurso | Qué es |
|---|---|
Task | Una serie de steps (contenedores) que se ejecutan en orden dentro del mismo pod. |
TaskRun | Una ejecución concreta de una Task con sus parámetros. |
Pipeline | Un grafo de Task con dependencias, parámetros y espacios de trabajo compartidos. |
PipelineRun | Una ejecución concreta de un Pipeline. |
Workspace | Un volumen que las tareas montan para compartir archivos. |
EventListener, TriggerBinding, TriggerTemplate | Los objetos de Tekton Triggers que convierten un evento HTTP (un webhook) en un PipelineRun. |
Paso 1: Instalar Tekton Pipelines
Tekton publica cada versión como un único manifiesto que crea el namespace tekton-pipelines, los CRD y los controladores. Aplica la última versión estable:
kubectl apply --filename https://infra.tekton.dev/tekton-releases/pipeline/latest/release.yaml
Espera a que los pods estén en ejecución y pulsa Ctrl+C cuando lo estén:
kubectl get pods --namespace tekton-pipelines --watch
NAME READY STATUS RESTARTS AGE
tekton-events-controller-6d7b8f9c5d-m2x8q 1/1 Running 0 45s
tekton-pipelines-controller-7c9f4d6b8-kq2lw 1/1 Running 0 45s
tekton-pipelines-webhook-5b8d7c9f6-7zj4n 1/1 Running 0 45s
Notapara instalar una versión concreta en lugar de la última, sustituye
latesten la URL por la etiqueta de versión, por ejemploprevious/v1.16.0. En producción es preferible fijar la versión para que las actualizaciones sean deliberadas.
Paso 2: Instalar la CLI tkn
tkn permite lanzar tareas y pipelines y seguir sus logs sin escribir manifiestos de TaskRun a mano. El proyecto publica paquetes .deb en GitHub. Descarga el de la última versión (consulta la página de releases de tektoncd/cli y ajusta TKN_VERSION si hay una más reciente):
TKN_VERSION=0.46.1
curl -LO "https://github.com/tektoncd/cli/releases/download/v${TKN_VERSION}/tektoncd-cli-${TKN_VERSION}_Linux-64bit.deb"
Instálalo con apt, que resuelve las dependencias si las hubiera:
sudo apt install "./tektoncd-cli-${TKN_VERSION}_Linux-64bit.deb"
Comprueba que la CLI se conecta al clúster y detecta la versión de Tekton instalada:
tkn version
Client version: 0.46.1
Pipeline version: v1.16.0
En servidores arm64 descarga el paquete tektoncd-cli-${TKN_VERSION}_Linux-ARM64.deb.
Paso 3: Crear y ejecutar una Task
Empieza con una tarea mínima que recibe un parámetro y lo imprime. Crea el archivo:
nano hello-task.yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: hello
spec:
params:
- name: name
type: string
default: mundo
steps:
- name: say-hello
image: alpine:3.22
script: |
#!/bin/sh
echo "Hola, $(params.name)"
Tekton sustituye $(params.name) por el valor del parámetro antes de ejecutar el script. Crea la tarea en el clúster:
kubectl apply -f hello-task.yaml
Ejecútala con tkn, pasando un valor al parámetro y mostrando el log al momento:
tkn task start hello --param name=CubePath --showlog
TaskRun started: hello-run-8k2fz
Waiting for logs to be available...
[say-hello] Hola, CubePath
Cada ejecución crea un TaskRun que queda en el clúster con su estado y sus logs:
tkn taskrun list
NAME STARTED DURATION STATUS
hello-run-8k2fz 30 seconds ago 9s Succeeded
Paso 4: Crear tareas que comparten archivos y resultados
Un pipeline real necesita pasar archivos de una tarea a otra (el código clonado) y, a veces, pequeños valores (el hash del commit). Para lo primero se usan workspaces y para lo segundo results.
Crea la tarea que clona el repositorio:
nano git-clone-task.yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: git-clone-simple
spec:
params:
- name: url
type: string
description: URL del repositorio Git
- name: revision
type: string
description: Rama, etiqueta o hash del commit
default: main
workspaces:
- name: output
description: Volumen donde se deja el código en el directorio source
results:
- name: commit
description: Hash del commit clonado
steps:
- name: clone
image: alpine/git:latest
workingDir: $(workspaces.output.path)
script: |
#!/bin/sh
set -eu
rm -rf source
git init -q source
cd source
git remote add origin "$(params.url)"
git fetch -q --depth 1 origin "$(params.revision)"
git checkout -q FETCH_HEAD
printf '%s' "$(git rev-parse HEAD)" > "$(results.commit.path)"
echo "Clonado $(params.url) en $(git rev-parse --short HEAD)"
El git fetch de una sola revisión funciona igual con un nombre de rama que con el hash de un commit, lo que será útil cuando el pipeline se lance desde un webhook. El hash queda escrito en $(results.commit.path) para que otras tareas lo lean.
Crea la segunda tarea, que usa el código clonado y el resultado de la primera. Aquí solo muestra información del repositorio; en un proyecto real es el lugar para ejecutar los tests (npm test, go test ./..., pytest) con la imagen adecuada:
nano show-info-task.yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: show-info
spec:
params:
- name: commit
type: string
workspaces:
- name: source
steps:
- name: info
image: alpine:3.22
workingDir: $(workspaces.source.path)/source
script: |
#!/bin/sh
set -eu
echo "Commit: $(params.commit)"
echo "Archivos en la raíz del repositorio:"
ls -1
Aplica ambas tareas:
kubectl apply -f git-clone-task.yaml -f show-info-task.yaml
tkn task list
NAME DESCRIPTION AGE
git-clone-simple 5 seconds ago
hello 3 minutes ago
show-info 5 seconds ago
Paso 5: Combinar las tareas en un Pipeline
El Pipeline declara sus propios parámetros y un workspace llamado shared, y se los pasa a cada tarea. La referencia $(tasks.clone.results.commit) hace que inspect reciba el hash calculado por clone y, además, fuerza a Tekton a ejecutarla después:
nano pipeline.yaml
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: clone-and-inspect
spec:
params:
- name: repo-url
type: string
- name: revision
type: string
default: main
workspaces:
- name: shared
tasks:
- name: clone
taskRef:
name: git-clone-simple
params:
- name: url
value: $(params.repo-url)
- name: revision
value: $(params.revision)
workspaces:
- name: output
workspace: shared
- name: inspect
runAfter:
- clone
taskRef:
name: show-info
params:
- name: commit
value: $(tasks.clone.results.commit)
workspaces:
- name: source
workspace: shared
kubectl apply -f pipeline.yaml
Paso 6: Ejecutar el pipeline con un PipelineRun
Un PipelineRun indica qué pipeline ejecutar, con qué parámetros y qué volumen usar para cada workspace. Con volumeClaimTemplate, Tekton crea un PVC nuevo para cada ejecución y lo borra cuando se elimina el PipelineRun, así que las ejecuciones no se pisan entre sí:
nano pipelinerun.yaml
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: clone-and-inspect-
spec:
pipelineRef:
name: clone-and-inspect
params:
- name: repo-url
value: https://github.com/stefanprodan/podinfo.git
- name: revision
value: master
workspaces:
- name: shared
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Como el manifiesto usa generateName para obtener un nombre único en cada ejecución, se crea con kubectl create en lugar de kubectl apply:
kubectl create -f pipelinerun.yaml
Sigue los logs de la última ejecución:
tkn pipelinerun logs --last -f
[clone : clone] Clonado https://github.com/stefanprodan/podinfo.git en 7a1e3c9
[inspect : info] Commit: 7a1e3c9d4b2f8e6a0c5d1f3b9e7a2c4d6f8b0e1a
[inspect : info] Archivos en la raíz del repositorio:
[inspect : info] Dockerfile
[inspect : info] Makefile
[inspect : info] README.md
[inspect : info] charts
[inspect : info] cmd
[inspect : info] kustomize
Comprueba el estado final:
tkn pipelinerun list
NAME STARTED DURATION STATUS
clone-and-inspect-x7k9p 1 minute ago 41s Succeeded
Paso 7: Lanzar el pipeline desde un webhook de GitHub
Tekton Triggers añade un servidor HTTP (EventListener) que recibe webhooks, valida la firma, extrae campos del cuerpo (TriggerBinding) y crea un PipelineRun a partir de una plantilla (TriggerTemplate).
Instala Triggers y sus interceptores (el interceptor de GitHub es el que valida la firma de los webhooks):
kubectl apply --filename https://infra.tekton.dev/tekton-releases/triggers/latest/release.yaml
kubectl apply --filename https://infra.tekton.dev/tekton-releases/triggers/latest/interceptors.yaml
kubectl get pods --namespace tekton-pipelines
Deberías ver además tekton-triggers-controller, tekton-triggers-webhook y tekton-triggers-core-interceptors en estado Running.
El EventListener necesita una cuenta de servicio con permisos para leer los objetos de Triggers y crear PipelineRun. La instalación incluye dos ClusterRole preparados para ello; solo hay que vincularlos:
nano triggers-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: tekton-triggers-sa
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tekton-triggers-sa-binding
namespace: default
subjects:
- kind: ServiceAccount
name: tekton-triggers-sa
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: tekton-triggers-eventlistener-roles
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-triggers-sa-clusterbinding
subjects:
- kind: ServiceAccount
name: tekton-triggers-sa
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: tekton-triggers-eventlistener-clusterroles
kubectl apply -f triggers-rbac.yaml
Genera un secreto aleatorio para firmar los webhooks y guárdalo en el clúster. GitHub firmará cada petición con él y el interceptor rechazará las que no coincidan:
kubectl create secret generic github-webhook-secret \
--from-literal=token="$(openssl rand -hex 20)"
Ahora crea los tres objetos de Triggers. El binding toma la URL de clonado y el hash del commit del evento push de GitHub (body.after), y la plantilla los pasa al pipeline:
nano triggers.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
name: github-push-binding
spec:
params:
- name: repo-url
value: $(body.repository.clone_url)
- name: revision
value: $(body.after)
---
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: clone-and-inspect-template
spec:
params:
- name: repo-url
- name: revision
resourcetemplates:
- apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: clone-and-inspect-
spec:
pipelineRef:
name: clone-and-inspect
params:
- name: repo-url
value: $(tt.params.repo-url)
- name: revision
value: $(tt.params.revision)
workspaces:
- name: shared
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: github-listener
spec:
serviceAccountName: tekton-triggers-sa
triggers:
- name: github-push
interceptors:
- ref:
name: github
params:
- name: secretRef
value:
secretName: github-webhook-secret
secretKey: token
- name: eventTypes
value: ["push"]
bindings:
- ref: github-push-binding
template:
ref: clone-and-inspect-template
kubectl apply -f triggers.yaml
Tekton crea un deployment y un servicio llamados el-github-listener que escuchan en el puerto 8080:
kubectl get pods,svc -l eventlistener=github-listener
Probar el EventListener sin GitHub
Antes de exponerlo a Internet, pruébalo desde tu máquina. Abre un túnel al servicio en una terminal:
kubectl port-forward svc/el-github-listener 8080:8080
En otra terminal, construye un cuerpo similar al de GitHub, fírmalo con el mismo secreto y envíalo. El interceptor comprueba la cabecera X-Hub-Signature-256 y el tipo de evento de X-GitHub-Event:
SECRET=$(kubectl get secret github-webhook-secret -o jsonpath='{.data.token}' | base64 -d)
BODY='{"after":"master","repository":{"clone_url":"https://github.com/stefanprodan/podinfo.git"}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
curl -s -X POST http://localhost:8080 \
-H 'Content-Type: application/json' \
-H 'X-GitHub-Event: push' \
-H "X-Hub-Signature-256: sha256=$SIG" \
-d "$BODY"
{"eventListener":"github-listener","namespace":"default","eventListenerUID":"4f0d2b6e-9a1c-4e7b-8d3f-2c5a7b9e1d0f","eventID":"b1e7c3a9-2d4f-4a6b-9c8e-0f1d3b5a7c9e"}
Un nuevo PipelineRun debería aparecer al momento:
tkn pipelinerun list
Conectar el repositorio de GitHub
Para recibir webhooks reales, el servicio el-github-listener tiene que ser accesible desde Internet, normalmente con un Ingress y un certificado TLS en un dominio como tekton.your_domain. Después, en el repositorio de GitHub, ve a Settings, Webhooks, Add webhook y configura:
- Payload URL:
https://tekton.your_domain/ - Content type:
application/json - Secret: el valor que devuelve
kubectl get secret github-webhook-secret -o jsonpath='{.data.token}' | base64 -d - Which events: solo el evento
push
Cada git push creará un PipelineRun con el commit exacto que se ha subido. Si el repositorio es privado, la tarea de clonado necesitará además credenciales (un secret de tipo kubernetes.io/basic-auth o kubernetes.io/ssh-auth asociado a la cuenta de servicio del pipeline).
Paso 8: Instalar Tekton Dashboard (opcional)
Tekton Dashboard es una interfaz web para ver tareas, pipelines, ejecuciones y sus logs. Instálalo con su manifiesto:
kubectl apply --filename https://infra.tekton.dev/tekton-releases/dashboard/latest/release.yaml
El servicio tekton-dashboard no tiene autenticación propia, así que no lo expongas públicamente sin un proxy que la añada. Para consultarlo, abre un túnel y visita http://localhost:9097 en el navegador:
kubectl port-forward -n tekton-pipelines svc/tekton-dashboard 9097:9097
Solución de problemas
Un PipelineRun se queda en Pending o Running sin avanzar. Normalmente es el PVC del workspace, que no se enlaza porque no hay StorageClass por defecto:
kubectl get pvc
kubectl describe pipelinerun <nombre>
Una tarea falla. tkn muestra el paso que ha fallado y su salida:
tkn pipelinerun describe --last
tkn pipelinerun logs --last
El webhook no crea ningún PipelineRun. Revisa los logs del EventListener. Un mensaje sobre la firma indica que el secreto de GitHub y el del clúster no coinciden; uno sobre el tipo de evento, que GitHub está enviando eventos distintos de push (por ejemplo, el ping inicial, que es normal que se rechace):
kubectl logs deploy/el-github-listener
Limpiar ejecuciones antiguas. Los TaskRun y PipelineRun se acumulan, y cada uno conserva su PVC. Borra las más antiguas manteniendo las últimas cinco:
tkn pipelinerun delete --keep 5 -f
Conclusión
Tienes Tekton Pipelines y Triggers funcionando en Kubernetes, con tareas reutilizables que comparten código a través de un workspace y resultados entre sí, un pipeline que se ejecuta a mano con kubectl create o automáticamente con cada push a GitHub, y el Dashboard para consultar las ejecuciones. Como siguientes pasos puedes añadir una tarea que construya y publique una imagen de contenedor con Buildah, guardar las credenciales del registro en un secret asociado a la cuenta de servicio del pipeline, y gestionar todos estos manifiestos desde Git con una herramienta de GitOps como Flux o Argo CD.