Usando tekton para construir y subir las imagenes a dockerhub
Apoya este blog
Si te resulta util este contenido, considera apoyar el blog.
Introducción
En este artículo exploraremos cómo desplegar y configurar Tekton para construir y enviar imágenes a tu registro, que luego serán consumidas por tu clúster. También veremos cómo estas imágenes se despliegan en otro artículo. En este, quiero mostrarte cómo preparar las imágenes listas para su uso, y además una solución útil para un sistema CI sin depender de factores externos. En mi caso, estaba teniendo problemas con Docker al construir imágenes de arquitectura cruzada, y después de configurar Tekton, todo fue más rápido y sencillo. Las imágenes de arquitectura cruzada son lentas por naturaleza y no siempre funcionan al 100% como esperarías. Al usar este enfoque, podemos olvidarnos de la arquitectura y simplemente construir donde ejecutamos las cosas, lo cual es definitivamente más rápido, y algunos de tus nodos ya tendrán las imágenes disponibles, lo que significa un menor consumo de ancho de banda a largo plazo.
Fuentes
-
tr, échale un vistazo. Mi nuevo blog corre allí: https://techsquad.rocks. Puedes consultar los manifiestos utilizados en la carpeta
manifests.
El código fuente y/o la documentación de los proyectos que probaremos están listados aquí:
Instalación de tekton-pipelines y tekton-triggers
¿Por qué necesitamos tekton-pipelines o tekton-triggers? Pipelines te permite ejecutar múltiples tareas en orden y pasar información entre ellas (esto es básico en Tekton y en cualquier sistema CI/CD). Además, necesitamos hacer algo cuando hacemos un push, por ejemplo, en nuestro repositorio git. Ahí es donde Tekton-triggers resulta útil, ya que nos permite reaccionar a cambios y desencadenar una compilación o algún proceso. Los interceptores son parte de Tekton-triggers, y te brindan flexibilidad utilizando eventos.
kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml
kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml
Luego necesitamos instalar tkn localmente y configurar algunos paquetes desde el hub:
tkn -n tekton-pipelines hub install task git-clone
tkn -n tekton-pipelines hub install task kaniko
tkn -n tekton-pipelines hub install task kubernetes-actions
En mi despliegue utilicé versiones fijas, lo cual es recomendable para cualquier tipo de implementación en “producción”. Puedes ver el readme aquí.
Vamos al grano
tekton-pipelines
Bien, tenemos Tekton y sus componentes instalados, listos para funcionar, pero ¿ahora qué? Bueno, es un poco complicado y requiere algunos manifiestos para empezar. Intentaré explicarte qué está pasando con cada archivo y por qué los necesitamos.
Puedes ver este archivo en GitHub también: 01-pipeline.yaml. Básicamente, necesitamos definir una pipeline que establezca los pasos y lo que sucederá. Aquí estamos clonando el repositorio, luego construyéndolo con Kaniko y finalmente enviándolo al registro de Docker. Ten en cuenta que el script está codificado, eso podría ser dinámico, pero no es realmente necesario para mi caso de uso.
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: clone-build-push
namespace: tekton-pipelines
spec:
description: |
This pipeline clones a git repo, builds a Docker image with Kaniko and
pushes it to a registry
params:
- name: repo-url
type: string
- name: image-reference
type: string
workspaces:
- name: shared-data
- name: docker-credentials
tasks:
- name: fetch-source
taskRef:
name: git-clone
workspaces:
- name: output
workspace: shared-data
params:
- name: url
value: $(params.repo-url)
- name: build-push
runAfter: ["fetch-source"]
taskRef:
name: kaniko
workspaces:
- name: source
workspace: shared-data
- name: dockerconfig
workspace: docker-credentials
params:
- name: IMAGE
value: $(params.image-reference)
- name: restart-deployment
runAfter: ["build-push"]
taskRef:
name: kubernetes-actions
params:
- name: script
value: |
kubectl -n tr rollout restart deployment/tr-deployment
Puedes ver este archivo en GitHub también:
02-pipeline-run.yaml.
Básicamente, este archivo se usa para ejecutar nuestra pipeline previamente definida con valores específicos. Utilizaremos algo muy similar desde el trigger para ejecutarlo automáticamente cuando hagamos un push de commits a nuestro repositorio. El secreto de Docker es un secreto tipo dockercfg normal, montado para que podamos hacer push a ese registro.
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: clone-build-push-run
namespace: tekton-pipelines
spec:
pipelineRef:
name: clone-build-push
podTemplate:
securityContext:
fsGroup: 65532
workspaces:
- name: shared-data
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- name: kubeconfig-dir
configMap:
name: kubeconfig
- name: docker-credentials
secret:
secretName: docker-credentials
params:
- name: repo-url
value: https://github.com/kainlite/tr.git
- name: image-reference
value: kainlite/tr:latest
Con todo lo que tenemos hasta ahora, ya contamos con una pipeline básica, pero necesitamos activarla o ejecutarla manualmente. Ahora agreguemos los manifiestos necesarios para que reaccione a los cambios en nuestro repositorio de GitHub.
tekton-triggers
Puedes ver este archivo en GitHub también: 01-rbac.yaml,
aquí le damos a tekton-triggers los permisos necesarios para poder funcionar correctamente.
apiVersion: v1
kind: ServiceAccount
metadata:
name: tekton-triggers-sa
namespace: tekton-pipelines
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tekton-triggers-minimal
namespace: tekton-pipelines
rules:
# EventListeners need to be able to fetch all namespaced resources
- apiGroups: ["triggers.tekton.dev"]
resources: ["eventlisteners", "triggerbindings", "triggertemplates", "triggers"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
# configmaps is needed for updating logging config
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
# Permissions to create resources in associated TriggerTemplates
- apiGroups: ["tekton.dev"]
resources: ["pipelineruns", "pipelineresources", "taskruns"]
verbs: ["create"]
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["impersonate"]
- apiGroups: ["policy"]
resources: ["podsecuritypolicies"]
resourceNames: ["tekton-triggers"]
verbs: ["use"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: tekton-triggers-binding
namespace: tekton-pipelines
subjects:
- kind: ServiceAccount
name: tekton-triggers-sa
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: tekton-triggers-minimal
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tekton-triggers-clusterrole
rules:
# EventListeners need to be able to fetch any clustertriggerbindings
- apiGroups: ["triggers.tekton.dev"]
resources: ["clustertriggerbindings", "clusterinterceptors"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tekton-triggers-clusterbinding
subjects:
- kind: ServiceAccount
name: tekton-triggers-sa
namespace: tekton-pipelines
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: tekton-triggers-clusterrole
También puedes ver este archivo en GitHub:
02-eventlistener.yaml.
Aquí es donde las cosas se complican un poco. En teoría, no necesitas un secret para leer tu repositorio si es público, pero cuando comencé a probar esto, mi repositorio era privado, luego lo hice público. Si te interesa el formato del secret, échale un vistazo al final de este YAML. Sin embargo, este archivo solo “escucha” eventos en nuestro repositorio y dispara un evento usando nuestra pipeline. Todavía necesitamos un ingress para el webhook y otras configuraciones, como veremos en los próximos pasos.
apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
name: clone-build-push
namespace: tekton-pipelines
spec:
serviceAccountName: tekton-triggers-sa
triggers:
- name: github-listener
interceptors:
- ref:
name: "github"
params:
- name: "secretRef"
value:
secretName: github-interceptor-secret
secretKey: secretToken
- name: "eventTypes"
value: ["push"]
- ref:
name: "cel"
bindings:
- ref: clone-build-push-binding
template:
ref: clone-build-push-template
El secret sería algo parecido al que se muestra a continuación. Reemplaza secretToken con tu token generado, este será utilizado para la configuración del webhook, así que guárdalo en un lugar seguro hasta que esté configurado.
apiVersion: v1
kind: Secret
metadata:
name: github-interceptor-secret
type: Opaque
stringData:
secretToken: "1234567"
También puedes ver este archivo en GitHub 04-triggerbinding.yaml.
Cuando recibimos el webhook, podemos obtener cierta información útil de él. Básicamente, nos interesa la URL del repositorio y el commit SHA. Aquí se definen los parámetros que queremos extraer del evento del webhook para pasárselos al pipeline de Tekton. Estos valores nos permiten especificar qué código o commit queremos construir y desplegar, lo cual es crucial para mantener un flujo de CI/CD automatizado.
Este archivo define la binding entre los datos que llegan del webhook y cómo se utilizan en nuestro pipeline.
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
metadata:
name: clone-build-push-binding
namespace: tekton-pipelines
spec:
params:
- name: gitrepositoryurl
value: $(body.repository.clone_url)
- name: gitrevision
value: $(body.pull_request.head.sha)
También puedes ver este archivo en GitHub 05-triggertemplate.yaml.
Este archivo sería el equivalente al pipelinerun que ejecutamos manualmente, pero en este caso utiliza el trigger y la template para disparar el pipeline automáticamente cuando se recibe un evento del webhook. De ahí las similitudes. En otras palabras, el TriggerTemplate define cómo se va a generar el pipelinerun con los parámetros que vienen del evento del webhook.
El objetivo es que cada vez que haya un nuevo push o evento relevante en el repositorio, se ejecute el pipeline automáticamente con los valores dinámicos que defina el webhook, como el SHA del commit o la URL del repositorio, lo que facilita la integración continua.
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
name: clone-build-push-template
namespace: tekton-pipelines
spec:
params:
- name: gitrevision
description: The git revision (SHA)
default: master
- name: gitrepositoryurl
description: The git repository url ("https://github.com/foo/bar.git")
resourcetemplates:
- apiVersion: tekton.dev/v1beta1
metadata:
namespace: tekton-pipelines
generateName: clone-build-push-
spec:
pipelineRef:
name: clone-build-push
podTemplate:
securityContext:
fsGroup: 65532
workspaces:
- name: shared-data
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- name: kubeconfig-dir
configMap:
name: kubeconfig
- name: docker-credentials
secret:
secretName: docker-credentials
params:
- name: repo-url
value: https://github.com/kainlite/tr.git
- name: image-reference
value: kainlite/tr:$(tt.params.gitrevision)
kind: PipelineRun
También puedes ver este archivo en GitHub 06-ingress.yaml.
Este es el último paso y uno de los más importantes: la configuración del ingress. Sin este archivo, no funcionará porque necesitamos recibir las solicitudes entrantes desde GitHub.
Para configurar el webhook en GitHub, simplemente ve a los settings de tu repositorio, haz clic en webhooks y crea uno nuevo. Usa el secret token que generaste anteriormente y establece la URL como https://subdomain.domain/hooks. Asegúrate de activar TLS, seleccionar solo la opción de push, y que esté activo. Esto permitirá que el webhook de GitHub dispare eventos en tu clúster Tekton cuando ocurra un push en el repositorio.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tr-ingress
namespace: tekton-pipelines
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: "/"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
service.beta.kubernetes.io/do-loadbalancer-enable-proxy-protocol: "true"
use-proxy-protocol: "true"
spec:
tls:
- hosts:
- trgh.techsquad.rocks
secretName: tr-prod-tls
rules:
- host: trgh.techsquad.rocks
http:
paths:
- path: /hooks
pathType: Exact
backend:
service:
name: el-clone-build-push
port:
number: 8080
¡Uf! ¡Eso fue mucho trabajo pero te aseguro que vale la pena! Ahora ya puedes construir, subir y ejecutar tus imágenes desde tu clúster, sin depender de un sistema de CI/CD externo ni raro, y siguiendo un modelo GitOps donde todo puede ser comprometido y aplicado desde tu repositorio. En mi caso, estoy usando ArgoCD y Kustomize para aplicar todo, pero eso será tema para otro capítulo.
Ahora validemos que funcione
Tenemos el event listener listo:
❯ tkn -n tekton-pipelines eventlistener list
NAME AGE URL AVAILABLE
clone-build-push 5 seconds ago http://el-clone-build-push.tekton-pipelines.svc.cluster.local:8080 True
Tenemos el pipeline, nota que dice “failed”, pero esto es porque hay un problema con ARM que aún no se ha resuelto, aunque todo en realidad funciona como se espera:
❯ tkn -n tekton-pipelines pipeline list
NAME AGE LAST RUN STARTED DURATION STATUS
clone-build-push 5 seconds ago clone-build-push-5qkv6 5 weeks ago 4m26s Failed
Podemos ver el pipelinerun siendo activado, con el mismo problema descrito antes. Revisa las notas en los issues de GitHub:
❯ tkn -n tekton-pipelines pipelinerun list
NAME STARTED DURATION STATUS
clone-build-push-5qkv6 5 seconds ago 4m26s Failed
clone-build-push-blkrm 5 seconds ago 3m58s Failed
También podemos ver algunos de los otros recursos creados para Tekton:
❯ tkn -n tekton-pipelines triggertemplate list
NAME AGE
clone-build-push-template 5 seconds ago
❯ tkn -n tekton-pipelines triggertemplate describe clone-build-push-template
Name: clone-build-push-template
Namespace: tekton-pipelines
⚓ Params
NAME DESCRIPTION DEFAULT VALUE
∙ gitrevision The git revision (S... master
∙ gitrepositoryurl The git repository ... ---
📦 ResourceTemplates
NAME GENERATENAME KIND APIVERSION
∙ --- clone-build-push- PipelineRun tekton.dev/v1beta1
También puedes ver los pods creados o los logs usando kubectl o tkn:
tekton-pipelines clone-build-push-vt6jz-fetch-source-pod 0/1 Completed 0 1d
tekton-pipelines clone-build-push-wzlkb-build-push-pod 0/2 Completed 0 1d
Espero que esto sea útil para alguien, y si tienes problemas con tu sistema de CI/CD, dale una oportunidad a Tekton, ¡te encantará! En mi caso particular, tuve muchos problemas al construir para ARM: era lento, tenía un montón de errores raros, y todo eso desapareció al construir las imágenes donde corro las cosas. Es más rápido y también utiliza la potencia de cómputo inactiva.
Algunas fuentes y problemas conocidos
Este post se inspiró mucho en estos artículos, y fue configurado y probado siguiendo estos ejemplos:
- https://github.com/tektoncd/triggers/blob/main/docs/getting-started/README.md
- https://tekton.dev/docs/how-to-guides/kaniko-build-push/#full-code-samples
- https://www.arthurkoziel.com/tutorial-tekton-triggers-with-github-integration/
Hay algunos problemas ejecutándose en ARM, en otras arquitecturas simplemente funciona, más información en:
Pero todo debería funcionar sin problemas ™.
Errata
Si encuentras algún error o tienes alguna sugerencia, mándame un mensaje para corregirlo.
$ Comentarios
Online: 0Por favor inicie sesión para poder escribir comentarios.