--- title: "Cómo crear pipelines de CI/CD con Tekton en Kubernetes" description: "Instala Tekton Pipelines y Triggers en Kubernetes, crea Tasks y un Pipeline que clona un repositorio y lánzalo automáticamente con un webhook de GitHub." category: "CI/CD DevOps" language: es source: https://cubepath.com/es/docs/ci-cd-devops/pipelines-de-tekton-en-kubernetes updated: 2026-09-25 --- # Cómo crear pipelines de CI/CD con Tekton en Kubernetes 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: | 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: ```bash 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: ```bash kubectl get pods --namespace tekton-pipelines --watch ``` ```output 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 ``` > Nota: para instalar una versión concreta en lugar de la última, sustituye `latest` en la URL por la etiqueta de versión, por ejemplo `previous/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): ```bash 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: ```bash 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: ```bash tkn version ``` ```output 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: ```bash nano hello-task.yaml ``` ```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: ```bash kubectl apply -f hello-task.yaml ``` Ejecútala con `tkn`, pasando un valor al parámetro y mostrando el log al momento: ```bash tkn task start hello --param name=CubePath --showlog ``` ```output 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: ```bash tkn taskrun list ``` ```output 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: ```bash nano git-clone-task.yaml ``` ```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: ```bash nano show-info-task.yaml ``` ```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: ```bash kubectl apply -f git-clone-task.yaml -f show-info-task.yaml tkn task list ``` ```output 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: ```bash nano pipeline.yaml ``` ```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 ``` ```bash 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í: ```bash nano pipelinerun.yaml ``` ```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`: ```bash kubectl create -f pipelinerun.yaml ``` Sigue los logs de la última ejecución: ```bash tkn pipelinerun logs --last -f ``` ```output [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: ```bash tkn pipelinerun list ``` ```output 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): ```bash 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: ```bash nano triggers-rbac.yaml ``` ```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 ``` ```bash 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: ```bash 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: ```bash nano triggers.yaml ``` ```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 ``` ```bash kubectl apply -f triggers.yaml ``` Tekton crea un deployment y un servicio llamados `el-github-listener` que escuchan en el puerto 8080: ```bash 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: ```bash 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`: ```bash 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" ``` ```output {"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: ```bash 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: ```bash 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: ```bash 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: ```bash kubectl get pvc kubectl describe pipelinerun ``` **Una tarea falla.** `tkn` muestra el paso que ha fallado y su salida: ```bash 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): ```bash 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: ```bash 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.