Ya he hablado varias veces de Gitea en este sitio, así que no me repetiré (mucho) ; Gitea es una solución ligera de alojamiento de repositorios Git (a lo GitHub).

En esta entrada se indica el proceso que he seguido para la creación de los diferentes objetos necesarios para desplegar Gitea (usando SQLite como base de datos) en Kubernetes.

Puedes seguir los pasos de la documentación oficial para desplegar Gitea sobre Kubernetes usando Helm.

Como parte del proyecto devtoolbox, una de las primeras herramientas que he desplegado es Gitea, como repositorio de código para el equipo de desarrollo.

Sería mucho más sencillo usar Helm y las instrucciones de la documentación de Gitea para desplegar la aplicación en Kubernetes, pero como el objetivo principal es aprender, he partido de la imagen oficial de Gitea y he generado desde cero los ficheros de definición de los objetos para desplegar Gitea en Kubernetes.

Gitea requiere una base de datos para almacenar información de configuración de la aplicación. Esta base de datos puede ser MySQL o PostgreSQL, aunque como primera aproximación, usaré la base de datos SQLite embebida.

Esto significa que en esta iteración (voy por la segunda) todavía no puedo escalar el número de réplicas.

En la primera iteración me concentré en la kubernetización de la aplicación; en la segunda, en automatizar completamente el despliegue, realizando la configuración de Gitea mediante un configMap. Más adelante planeo añadir la opción de usar una base de datos externa antes de pasar a realizar un despliegue completo usando Helm.

Creación del namespace

Todos los componentes los despliego en un namespace personalizado llamado toolbox-${nombre-aplicación}, así que el primer paso es crear el fichero de definición del namespace:

---
apiVersion: v1
kind: Namespace
metadata:
    name: toolbox-gitea

Creación del volumen

La creación del persistent volume generalmente se realiza automáticamente usando alguno de los storage class disponibles en el clúster al crear el persistent volume claim.

Como estoy usando un clúster de un solo nodo basado en K3s, crearé un volumen de tipo hostPath. Este tipo de volumen sólo es recomendable para desarrollo, por ejemplo.

Creación del persistent volume

La creación del persistent volume la debe realizar un administrador del clúster.

La carpeta local elegida (en mi caso, /mnt/data) debe existir en los nodos del clúster: mkdir -p /mnt/data.

---
apiVersion: v1
kind: PersistentVolume
metadata:
    name: gitea-volume
    namespace: toolbox-gitea
    labels:
        type: local
spec:
    storageClassName: manual
    capacity:
        storage: 10Gi
    accessModes:
        - ReadWriteOnce
    hostPath:
        path: "/mnt/data/gitea"

Creación del persistent volume claim

Al crear el persistent volume claim solictamos al clúster una determinada cantidad de almacenamiento de un tipo concreto. Como en nuestro caso la storageClass es manual, un administrador debe haber creado anteriormente un persistent volume que permita satisfacer el requerimiento de almacenamiento expresado en el claim.

Cuando desplegamos el persistent volume claim, el sistema asocia un persistent volume disponible para su uso en los pods creados por el deployment.

Si no hay ningún volumen disponible que satisfaga el claim, el pod no arranca.

Para otros tipos de storage class diferente a manual, la creación del persistent volume se realiza de forma dinámica cuando se crea el persistent volume claim.

La definición del persistent volume claim:

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    name: gitea-volume-claim
    namespace: toolbox-gitea
spec:
    storageClass: manual
    accessModes:
        - ReadWriteOnce
    resources:
        requests:
            storage: 10Gi

Configuración de Gitea

La configuración de Gitea se realiza a través de una combinación de variables de entorno (en el deployment) y el fichero de configuración app.ini

Quizás es posible usar únicamente el fichero app.ini para realizar la configuración de Gitea, pero creo que algunos valores -como en nombre a mostrar- son más visibles en el fichero de definición del deployment.

El fichero app.ini podemos crearlo desde cero, usar un fichero de configuración de referencia (como app.example.ini en el repositorio oficial de Gitea en Github) o exportalo de una instancia en marcha (kubectl exec <gitea-pod-name> -- cat /data/gitea/conf/app.ini > app.ini)

Una vez configurado el fichero app.ini (en Configuration Cheat Sheet tienes el detalle de las opciones de configuración), crea el fichero de definición del configMap mediante:

kubectl create configmap gitea-config -n toolbox-gitea --from-file=app.ini -o yaml --dry-run
---
apiVersion: v1
kind: configMap
metadata:
    name: gitea-config
    namespace: toolbox-gitea
data:
    app.ini: "APP_NAME = GiteaToolbox\nRUN_MODE = prod\nRUN_USER = git\n\n[repository]\nROOT
    = /data/git/repositories\n\n[repository.local]\nLOCAL_COPY_PATH = /data/gitea/tmp/local-repo\n\n[repository.upload]\nTEMP_PATH
    = /data/gitea/uploads\n\n[server]\nAPP_DATA_PATH    = /data/gitea\nDOMAIN           =
    gitea.dev.lab\nSSH_DOMAIN       = gitea.dev.lab\nHTTP_PORT        = 3000\nROOT_URL
    \        = https://gitea.dev.lab/\nDISABLE_SSH      = true\nSSH_PORT         =
    22\nSSH_LISTEN_PORT  = 22\nLFS_START_SERVER = true\nLFS_CONTENT_PATH = /data/git/lfs\nLFS_JWT_SECRET
    \  = O6jMwi1miKPfeNtwFy6YQ_Xw73KIFfDumbhiyvFW000\nOFFLINE_MODE     = false\nLANDING_PAGE
    \    = login\n\n[database]\nPATH     = /data/gitea/gitea.db\nDB_TYPE  = sqlite3\nHOST
    \    = localhost:3306\nNAME     = gitea\nUSER     = root\nPASSWD   = \nLOG_SQL
    \ = false\nSCHEMA   = \nSSL_MODE = disable\nCHARSET  = utf8\n\n[indexer]\nISSUE_INDEXER_PATH
    = /data/gitea/indexers/issues.bleve\n\n[session]\nPROVIDER_CONFIG = /data/gitea/sessions\nPROVIDER
    \       = file\n\n[picture]\nAVATAR_UPLOAD_PATH            = /data/gitea/avatars\nREPOSITORY_AVATAR_UPLOAD_PATH
    = /data/gitea/repo-avatars\nDISABLE_GRAVATAR              = false\nENABLE_FEDERATED_AVATAR
    \      = true\n\n[attachment]\nPATH = /data/gitea/attachments\n\n[log]\nMODE                 =
    console\nLEVEL                = info\nREDIRECT_MACARON_LOG = true\nMACARON              =
    console\nROUTER               = console\nROOT_PATH            = /data/gitea/log\n\n[security]\nINSTALL_LOCK
    \  = true\nSECRET_KEY     = 4EmLHC2wGvIblUzjkz3bui41Otq2od3ZvK6B4mTtrc1G4S1bJlDwytmkIjLBMx0X\nINTERNAL_TOKEN
    = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE2MDUwNDQ1NTd9.L4EcHeKNY46zwK9GdXAVXEdTAl1E7dEKQuYIXFvXgBc\n\n[service]\nDISABLE_REGISTRATION
    \             = true \nREQUIRE_SIGNIN_VIEW               = false\nREGISTER_EMAIL_CONFIRM
    \           = false\nENABLE_NOTIFY_MAIL                = false\nALLOW_ONLY_EXTERNAL_REGISTRATION
    \ = false\nENABLE_CAPTCHA                    = false\nDEFAULT_KEEP_EMAIL_PRIVATE
    \       = false\nDEFAULT_ALLOW_CREATE_ORGANIZATION = false\nDEFAULT_ENABLE_TIMETRACKING
    \      = false\nNO_REPLY_ADDRESS                  = \n\n[oauth2]\nJWT_SECRET =
    PCG9Yl-fjaUi54pBTCoaJPH7C-v2r_bBIh4SYSfKysk\n\n[mailer]\nENABLED = false\n\n[openid]\nENABLE_OPENID_SIGNIN
    = false\nENABLE_OPENID_SIGNUP = false\n\n"

Fichero de despliegue

Una vez tenemos todos los elementos necesarios, creamos el fichero de definición del deployment de Gitea:

---
apiVersion: apps/v1
kind: Deployment
metadata:
    name: gitea
    namespace: toolbox-gitea
    labels:
        app: gitea
spec:
    replicas: 1
    selector:
        matchLabels:
            app: gitea
    template:
        metadata:
            labels:
                app: gitea
        spec:
            containers:
                - name: gitea
                  image: gitea/gitea
                  ports:
                    - name: gitea-ui
                      containerPort: 3000
                  env:
                    - name: APP_NAME
                      value: GiteaToolbox
                    - name: DOMAIN
                      value: gitea.dev.lab
                    - name: ROOT_URL
                      value: https://gitea.dev.lab
                  volumeMounts:
                    - name: gitea-data
                      mountPath: /data
                    - name: gitea-config
                      mountPath: /data/gitea/conf
            volumes:
                - name: gitea-data
                  persistentVolumeClaim:
                    claimName: gitea-volume-claim
                - name: gitea-config
                  configMap:
                    name: gitea-config

Especificamos una única réplica ya que al usar SQLite (dentro del contenedor), no tendría sentido escalar. En la sección spec.template.metadata.labels indicamos que los pods creados por el deployment incluyan la etiqueta app=gitea, lo que nos permite seleccionarlos en el siguiente paso, donde crearemos el service.

El fichero de configuración app.ini lo montamos en /data/gitea/conf (la ubicación por defecto) usando un volumen. El otro volumen montado en el pod contiene los datos de los repositorios gestionados por Gitea.

Creación del servicio

Gitea expone dos puertos: 22 (SSH) y 3000 (gitea-ui). El uso de Git a través de SSH requiere configuración adicional, ya que los ingress sólo permiten tráfico HTTP y HTTPS. Por este motivo sólo exponemos el puerto 3000 para conectar con Gitea usando HTTPS.

---
apiVersion: v1
kind: Service
metadata:
    name: gitea
    namespace: toolbox-gitea
spec:
    ports:
        - name: gitea-ui
          port: 3000
    selector:
        app: gitea

Creación del ingress para proporcionar acceso a Gitea desde fuera del clúster

Al crear el servicio anterior, por defecto, se asigna una IP de tipo ClusterIP al servicio. Esto impide que se pueda acceder a la aplicación desde fuera del clúster.

Usamos un ingress para exponer el servicio al exterior:

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
    name: gitea
    namespace: toolbox-gitea
    annotations:
        kubernets.io/ingress.class: traefik
        ingress.kubernetes.io/ssl-redirect: "false" # from https://github.com/rancher/k3d/blob/main/docs/usage/guides/exposing_services.md
spec:
    rules:
        - host: gitea.dev.lab # configured in DNS or hosts file
          http:
            paths:
                - path: /
                  backend:
                      serviceName: gitea
                      servicePort: gitea-ui

En K3s el ingress desplegado por defecto es Traefik, por lo que las anotaciones quizás no tengan sentido para otras ingress controllers.

Despliegue

Hemos creado el fichero gitea-sqlite.yaml con los diferentes fragmentos de código del artículo. De esta forma, podemos desplegar todos los recursos necesarios para el funcionamiento de Gitea mediante un único comando:

$ kubectl apply -f gitea-sqlite.yaml
namespace/toolbox-gitea created
persistentvolume/gitea-volume created
persistentvolumeclaim/gitea-volume-claim created
deployment.apps/gitea created
service/gitea created
ingress.extensions/gitea created

Hemos especificado metadata.namespace: toolbox-gitea en la definición de todos los recursos; si no lo has hecho, recuerda que debes especificar -n <namespace> indicando el namespace de destino (si quieres desplegar en un namespace diferente al default).

Tras algo menos de un minuto, Gitea está en marcha y ya podrás acceder a través de https://gitea.dev.lab