Usando tekton para construir y subir las imagenes a dockerhub

2022-10-25 | Gabriel Garrido | 9 min de lectura
Share:

Apoya este blog

Si te resulta util este contenido, considera apoyar el blog.

tekton

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:

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: 0

Por favor inicie sesión para poder escribir comentarios.

2022-10-25 | Gabriel Garrido