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 StorageClass por defecto en el clúster, porque los pipelines usarán volúmenes persistentes para compartir archivos entre tareas. Compruébalo con kubectl 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 kubectl configurado.

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:

RecursoQué es
TaskUna serie de steps (contenedores) que se ejecutan en orden dentro del mismo pod.
TaskRunUna ejecución concreta de una Task con sus parámetros.
PipelineUn grafo de Task con dependencias, parámetros y espacios de trabajo compartidos.
PipelineRunUna ejecución concreta de un Pipeline.
WorkspaceUn volumen que las tareas montan para compartir archivos.
EventListener, TriggerBinding, TriggerTemplateLos 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

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.