Postgres corre sin SSL configurado (imagen postgres:17-alpine estandar, sin certificados). El cliente pg de Medusa intenta negociar SSL por defecto al no encontrar sslmode explicito en la URL, lo que cuelga 10s y hace fallar el initContainer migrations con CrashLoopBackOff. Verificado con un pod efimero usando la imagen real de la app: con sslmode=disable la migracion conecta al instante y corre exitosamente.
Laboratorio k3d estable para ZimaOS
Este paquete reemplaza el deploy-lab.sh original por una versión idempotente y no destructiva.
Qué corrige
- No elimina
lab-clusteren cada ejecución. - Crea el clúster solo cuando no existe.
- Usa
subnet: autopara mantener IP internas estables tras reinicios. - Conserva el datastore SQLite, certificados y token de K3s en
/DATA/AppData/k3d-lab/k3s/server. - Conserva los datos de
local-path-provisionerfuera de los contenedores. - Usa un token de clúster persistente.
- Mantiene fijos los puertos
46421,90y9443. - Valida que ZimaOS siga usando
192.168.68.61antes de desplegar. - Instala Argo CD con versión fijada y
server-side apply. - No crea el
argocd-managerinnecesario para desplegar en el mismo clúster. - Crea RBAC de Headlamp y RBAC de solo lectura para la validación postdeploy.
- Regenera los archivos requeridos para los secrets de Gitea Actions.
- Instala opcionalmente un watchdog systemd para recuperar el clúster después del boot.
Primera recuperación / creación
Ejecutar como el usuario devops, no con sudo:
chmod +x deploy-lab.sh
LAB_HOST_IP=192.168.68.61 \
./deploy-lab.sh bootstrap
Como Docker y k3d están actualmente vacíos, bootstrap creará un clúster nuevo. En ejecuciones posteriores iniciará y reconciliará el clúster existente.
Validación
./deploy-lab.sh status
kubectl get nodes -o wide
kubectl get pods -A
kubectl get ingress -A
Autostart después de reinicios
Ejecutar solamente después de que bootstrap termine correctamente:
sudo ./deploy-lab.sh install-autostart
Verificar:
systemctl status k3d-lab-ensure.timer --no-pager
journalctl -u k3d-lab-ensure.service -n 200 --no-pager
El timer se ejecuta 60 segundos después del arranque y posteriormente cada cinco minutos. El servicio intenta iniciar el clúster, regenera el kubeconfig y valida los tres nodos.
Recuperación manual
./deploy-lab.sh recover
recover no crea ni elimina el clúster. Solo intenta iniciar uno existente y reconcilia Argo CD.
Applications de Argo CD
Guarda los manifiestos Application, ApplicationSet o el app-of-apps en:
/DATA/AppData/k3d-lab/bootstrap/
Al ejecutar nuevamente bootstrap, el script aplicará automáticamente todos los .yaml y .yml de ese directorio.
Secrets para la validación postdeploy
Después de crear un clúster nuevo, se generan:
/DATA/AppData/k3d-lab/gitea-actions-secrets/K8S_SERVER.txt
/DATA/AppData/k3d-lab/gitea-actions-secrets/K8S_CA_B64.txt
/DATA/AppData/k3d-lab/gitea-actions-secrets/K8S_TOKEN.txt
/DATA/AppData/k3d-lab/gitea-actions-secrets/K8S_TLS_SERVER_NAME.txt
Copia sus valores a los secrets equivalentes del repositorio devops/apps-registry en Gitea. No publiques el contenido de K8S_TOKEN.txt.
Headlamp
Genera un token cuando sea necesario:
kubectl create token headlamp-admin -n headlamp
El script crea el RBAC, pero el Deployment/Ingress de Headlamp debe provenir del repositorio GitOps.
Reset limpio
Solo cuando realmente quieras iniciar desde cero:
CONFIRM_RESET=DELETE-lab-cluster ./deploy-lab.sh reset
Antes de limpiar el estado, el script crea un archivo en:
/DATA/AppData/k3d-lab/backups/
Variables principales
CLUSTER_NAME=lab-cluster
APP_DATA_PATH=/DATA/AppData/k3d-lab
KUBECONFIG_PATH=/DATA/.kube/config
LAB_HOST_IP=192.168.68.61
EXPECTED_HOST_IP=192.168.68.61
K3S_IMAGE=rancher/k3s:v1.35.5-k3s1
ARGOCD_VERSION=v3.2.0
Para aceptar conscientemente una IP distinta:
LAB_HOST_IP=192.168.68.X \
ALLOW_IP_CHANGE=true \
./deploy-lab.sh bootstrap
También deberás actualizar NPM y los secrets de Gitea Actions.
Arranque automático reforzado v3
La versión v3 agrega:
subnet: autoal crear el clúster para mantener IP estática por nodo.restart=unless-stoppedsolamente a los contenedores delab-cluster.- Dependencias systemd sobre el
DockerRootDirreal yAPP_DATA_PATH. - Inicio 90 segundos después del boot.
- Reintento automático del servicio cada 30 segundos si falla.
- Watchdog cada 5 minutos.
- Regeneración del kubeconfig.
- Recuperación de agentes
NotReady. - Re-registro automático de un agente persistentemente obsoleto.
- Validación de Traefik, Argo CD y el puerto HTTP publicado.
Instalar
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh bootstrap
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Prueba de reinicio
Antes:
./deploy-lab.sh status
systemctl status k3d-lab-ensure.timer --no-pager
systemctl cat k3d-lab-ensure.service
systemctl cat docker.service
Después del reboot, espera hasta 10 minutos y valida:
systemctl status k3d-lab-ensure.service --no-pager
journalctl -b -u k3d-lab-ensure.service --no-pager
./deploy-lab.sh status
curl -I -H 'Host: argocd.cruzcloud.net' http://127.0.0.1:90
curl -I -H 'Host: shop.cruzcloud.net' http://127.0.0.1:90
Corrección v3.1 para ZimaOS
ZimaOS puede no incluir el comando openssl. El script ya no lo exige:
- Usa
opensslcuando está disponible. - Si no, usa
/dev/urandomconod. - Como último fallback, usa
hexdump.
Para crear o recuperar el laboratorio, no uses sudo sh:
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh bootstrap
Solo la instalación de systemd requiere sudo:
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Corrección v3.2: rollout de Argo CD
argocd-serversolo se reinicia cuandoserver.insecurecambia.- El timeout específico de Argo CD aumenta a 420 segundos.
- Si el rollout falla, el script muestra Pods y eventos.
- Si la réplica nueva ya está disponible y únicamente queda un Pod antiguo
atascado en
Terminating, elimina solo ese Pod antiguo y reintenta. - No fuerza la eliminación cuando todavía no existe una réplica nueva disponible.
Ejecución:
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh bootstrap
Corrección v3.3: permisos de credenciales Gitea Actions
La generación de credenciales ya no ejecuta chmod sobre todos los .txt
históricos. Cada archivo se genera temporalmente y se instala con:
- propietario: usuario que ejecuta
bootstrap; - grupo: grupo principal del usuario;
- permisos:
0600.
Además se regenera KUBE_CONFIG_DATA.txt con un kubeconfig Base64 de
privilegios mínimos para gitea-postdeploy-validator, manteniendo
compatibilidad con el workflow anterior.
Para continuar:
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh bootstrap
Corrección v3.4: propietario sin asumir grupo devops
ZimaOS tiene al usuario devops con grupo principal samba; no existe
necesariamente un grupo llamado devops.
La v3.4:
- usa
chown devopsy nochown devops:devops; - usa
install -o devopssin forzar grupo para archivos privados; - mantiene permisos
0700en el directorio de secretos; - mantiene permisos
0600en credenciales y kubeconfigs; - conserva el grupo existente del filesystem cuando solo cambia el propietario.
El grupo de la unidad systemd sigue obteniéndose dinámicamente con
id -gn devops, por lo que en ZimaOS será samba.
Corrección v3.5: kubeconfig sin current-context
La v3.5 valida y corrige el kubeconfig antes de instalarlo:
- obtiene el kubeconfig directamente desde k3d;
- confirma que contenga al menos un contexto;
- activa explícitamente ese contexto;
- confirma que exista un API Server;
- instala
/DATA/.kube/configcon propietariodevopsy permisos0600; - no depende de
.profile; - systemd continúa usando la ruta explícita de kubeconfig.
Reparación manual equivalente:
TMP_KUBECONFIG="$(mktemp)"
k3d kubeconfig get lab-cluster > "$TMP_KUBECONFIG"
CTX="$(KUBECONFIG="$TMP_KUBECONFIG" kubectl config get-contexts -o name | head -1)"
KUBECONFIG="$TMP_KUBECONFIG" kubectl config use-context "$CTX"
sudo install -d -m 0700 -o devops /DATA/.kube
sudo install -m 0600 -o devops "$TMP_KUBECONFIG" /DATA/.kube/config
rm -f "$TMP_KUBECONFIG"
Corrección v3.6: systemd en ZimaOS sin /usr/local/sbin
ZimaOS no garantiza que /usr/local/sbin exista o sea escribible. La v3.6
instala la copia persistente del script en:
/DATA/AppData/k3d-lab/bin/deploy-k3d-lab
La unidad usa Bash explícitamente:
ExecStart=/bin/bash /DATA/AppData/k3d-lab/bin/deploy-k3d-lab ensure
Esto también funciona cuando /DATA tiene la opción de montaje noexec.
Instalación:
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Corrección v3.7: watchdog systemd sin sudo
El servicio se ejecuta como devops y no dispone de una terminal para
introducir la contraseña de sudo. La v3.7 elimina sudo del camino de
ejecución del modo ensure.
Cambios:
- comprueba escritura en
/DATA/AppData/k3d-lab, no en la raíz/DATA; - regenera
/DATA/.kube/configdirectamente comodevops; - prepara propietario y permisos durante
install-autostart; - no intenta iniciar Docker mediante sudo desde systemd;
- muestra automáticamente
systemctl statusyjournalctlsi la prueba inicial del servicio falla.
Instalación:
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Corrección v3.8: PATH de systemd y ubicación de k3d
La shell interactiva de ZimaOS puede encontrar k3d en una ruta bajo
/DATA, mientras systemd utiliza un PATH mínimo. La v3.8:
- localiza automáticamente
k3d,kubectl,dockerycurl; - agrega los directorios detectados al PATH de la unidad;
- guarda las rutas en
/etc/default/k3d-lab; - permite indicar rutas explícitas con
K3D_BIN,KUBECTL_BIN,DOCKER_BINyCURL_BIN; - muestra el PATH efectivo al terminar.
Instalación normal:
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Fallback explícito:
K3D_BIN="$(command -v k3d)" \
KUBECTL_BIN="$(command -v kubectl)" \
DOCKER_BIN="$(command -v docker)" \
CURL_BIN="$(command -v curl)" \
sudo -E LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Bootstrap GitOps completo v4.0
La versión v4.0 agrega dos piezas al laboratorio:
-
App-of-Apps
- Aplica
root-apps-registry. - Fuente:
https://gitea.cruzcloud.net/devops/apps-registry.git. - Revisión:
main. - Ruta:
apps. - Espera las ocho Applications existentes en el repositorio.
- Informa cuáles siguen
Progressing,OutOfSyncoDegraded.
- Aplica
-
Gitea Actions runner persistente
- Contenedor:
gitea-runner. - Registro persistente:
/DATA/AppData/k3d-lab/gitea-runner/data/.runner. - Monta
/var/run/docker.sock. - Política
unless-stopped. - Etiqueta compatible con
runs-on: ubuntu-latest. - El servicio systemd también valida y recupera el runner.
- Contenedor:
Primera adopción del runner existente
Primero confirma que no haya un workflow activo y ejecuta:
MIGRATE_EXISTING_RUNNER=true ./deploy-lab.sh runner
El script:
- detecta la imagen actual;
- copia
/datadesde el contenedor existente; - guarda
.runnerfuera del contenedor; - recrea
gitea-runnercon el volumen persistente; - no requiere un token nuevo si
.runnerpudo recuperarse.
Runner desde cero
Obtén un token desde Gitea:
apps-registry -> Settings -> Actions -> Runners
Guárdalo sin mostrarlo:
sudo install -d -m 0700 -o devops /DATA/AppData/k3d-lab/secrets
sudo sh -c 'umask 077; cat > /DATA/AppData/k3d-lab/secrets/gitea-runner-registration-token'
Pega únicamente el token y finaliza con Ctrl+D. Después:
sudo chown devops /DATA/AppData/k3d-lab/secrets/gitea-runner-registration-token
sudo chmod 0600 /DATA/AppData/k3d-lab/secrets/gitea-runner-registration-token
./deploy-lab.sh runner
El token solo se utiliza para generar /data/.runner. El contenedor permanente no conserva el token de registro como variable de entorno.
Repositorio privado de Argo CD
Cuando apps-registry sea privado, crea:
sudo sh -c 'umask 077; printf "%s\n" "devops" > /DATA/AppData/k3d-lab/secrets/argocd-repo-username'
sudo sh -c 'umask 077; cat > /DATA/AppData/k3d-lab/secrets/argocd-repo-password'
Pega un token de Gitea con lectura del repositorio y finaliza con Ctrl+D.
sudo chown devops /DATA/AppData/k3d-lab/secrets/argocd-repo-username
sudo chown devops /DATA/AppData/k3d-lab/secrets/argocd-repo-password
sudo chmod 0600 /DATA/AppData/k3d-lab/secrets/argocd-repo-*
Aplicar solo GitOps al clúster actual
./deploy-lab.sh gitops
Reconstrucción completa
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh bootstrap
El orden es:
k3d -> Argo CD -> repositorio Gitea -> root-apps-registry
-> Applications hijas -> RBAC postdeploy -> Gitea runner
Reinstalar systemd
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Verificación
kubectl get applications.argoproj.io -n argocd
docker ps --filter name=gitea-runner
docker logs --tail 100 gitea-runner
Caché Git persistente v4.1
La versión v4.1 no recrea root-apps-registry como fuente principal.
Antes de aplicar GitOps:
- clona
apps-registrycuando no existe; - ejecuta
git fetch --prunecuando ya existe; - alinea la copia local con
origin/main; - valida
application.yamly la carpetaapps/; - aplica
application.yamldirectamente desde el clon; - registra el commit utilizado;
- conserva el clon en
/DATApara una recuperación cuando Gitea esté temporalmente fuera de servicio.
Ruta:
/DATA/AppData/k3d-lab/git/apps-registry
Commit utilizado:
/DATA/AppData/k3d-lab/git/apps-registry.commit
Actualizar únicamente el clon
./deploy-lab.sh repo-sync
Actualizar el clon y reconciliar Argo CD
./deploy-lab.sh gitops
Reconstrucción desde cero
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh bootstrap
El bootstrap intenta obtener primero la última versión de main. Cuando
Gitea no responde y ya existe un clon válido, utiliza el commit cacheado y
permite que Argo CD vuelva a reconciliarse cuando Gitea regrese.
El watchdog no hace git fetch cada cinco minutos por defecto. Solo usa el
clon para restaurar root-apps-registry si desaparece. Argo CD sigue siendo
el encargado de observar Git y desplegar cambios durante la operación normal.
Para actualizar el clon también durante ensure:
APP_REGISTRY_REFRESH_ON_ENSURE=true \
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
No se recomienda en el valor predeterminado porque haría una consulta a Gitea en cada ejecución del watchdog.
Corrección v4.2: descubrimiento dinámico y monitoring
Applications esperadas
La lista ya no está codificada manualmente. El script descubre el
metadata.name real de cada Application dentro de:
/DATA/AppData/k3d-lab/git/apps-registry/apps/
Esto evita falsos errores cuando el nombre real es, por ejemplo,
monitoring-governance y no monitoring-governance-app.
También se puede seguir forzando una lista explícita:
EXPECTED_GITOPS_APPS="ecommerce-app,monitoring-app" ./deploy-lab.sh gitops
Diagnóstico de kube-prometheus-stack
./deploy-lab.sh monitoring-diagnose
El comando muestra:
- versión del chart;
- estado Sync/Health;
- operación de Argo CD;
- mensaje del hook;
- Jobs y Pods admission-create/admission-patch;
- eventos recientes;
- condiciones de la Application.
Para chart 58.2.1 o 58.2.2, usa como mínimo 58.3.3 y agrega:
helm:
parameters:
- name: prometheusOperator.admissionWebhooks.patch.ttlSecondsAfterFinished
value: "60"
Multi-repo y governance v4.3
La versión v4.3 incorpora el segundo repositorio GitOps:
https://gitea.cruzcloud.net/devops/platform-infra.git
El repositorio contiene los overlays Kustomize utilizados por las
Applications *-governance.
Cachés persistentes
/DATA/AppData/k3d-lab/git/apps-registry
/DATA/AppData/k3d-lab/git/platform-infra
Validación Kustomize
El script descubre en apps-registry/apps/*.yaml todas las Applications
cuyo repoURL apunta a platform-infra, valida que sus rutas existan y
ejecuta:
kubectl kustomize <ruta>
Los snapshots renderizados quedan en:
/DATA/AppData/k3d-lab/rendered/platform-infra/
Durante un bootstrap se aplican antes de crear las Applications de workload. Esto garantiza que Namespace, ResourceQuota, LimitRange y NetworkPolicy estén presentes antes de instalar Helm charts.
Contrato obligatorio de monitoring
monitoring-governance debe contener un LimitRange, porque su
ResourceQuota exige requests y limits de CPU y memoria.
Se incluyen archivos de referencia en:
repo-patches/platform-infra/monitoring-governance/
├── limitrange.yaml
└── kustomization.yaml
Copia limitrange.yaml al repositorio platform-infra y agrega el recurso
a su kustomization.yaml.
Sincronizar ambos repositorios
./deploy-lab.sh repo-sync
Reconciliar GitOps completo
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh gitops
Reinstalar watchdog
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Recuperación de operación obsoleta de Grafana
Grafana puede funcionar mientras Argo CD mantiene una operación antigua,
por ejemplo target 58.3.3 con operación activa 58.2.1.
./deploy-lab.sh monitoring-diagnose
./deploy-lab.sh monitoring-recover
La recuperación valida el LimitRange, termina la operación anterior,
elimina únicamente los Jobs admission temporales, ejecuta hard refresh,
sincroniza la revisión actual y espera Synced/Healthy.
Cuando argocd no está instalado en ZimaOS se usa temporalmente la imagen
oficial de Argo CD con --core y /DATA/.kube/config.
El manifiesto funcional está en:
repo-patches/apps-registry/apps/monitoring-app.yaml
Corrección v4.4.1: compatibilidad CLI Argo CD 3.2
argocd app terminate-op y argocd app sync no aceptan
--app-namespace en la versión usada por el laboratorio.
La función argocd_core_cli ahora ejecuta:
argocd app terminate-op monitoring-app --core
argocd app sync monitoring-app --core --async --prune --assumeYes
Las Applications del laboratorio residen en argocd, el namespace de
control de Argo CD.
Corrección v4.4.2
Se corrigen dos comportamientos:
-
Cuando
monitoring-appya tiene:- revisión de operación igual al target;
- fase
Succeeded; - estado
Synced/Healthy;
el script termina correctamente y no inicia otra sincronización.
-
En modo
argocd --core, el kubeconfig temporal fija el namespace actual enargocd. Esto evita:
configmap "argocd-cm" not found
El flujo intenta primero hard refresh y auto-sync. Solo llama
argocd app sync --core si Argo CD no inicia la reconciliación.
Corrección v4.4.3: Gitea runner y restart storm
El watchdog se ejecuta como devops; no puede pedir una contraseña de
sudo. La v4.4.3:
- crea las rutas persistentes del Gitea runner durante
install-autostart; - elimina
sudodel flujoensure -> ensure_gitea_runner; - cambia el servicio
oneshotaRestart=no; - deja los reintentos exclusivamente al timer de cinco minutos;
- limita fallos con
StartLimitIntervalSec=15minyStartLimitBurst=3; - agrega
Persistent=trueal timer.
Esto evita ciclos como:
restart counter is at 645
sudo: a terminal is required
Failed to allocate directory watch: Too many open files
Instalación:
sudo systemctl stop k3d-lab-ensure.timer
sudo systemctl stop k3d-lab-ensure.service || true
sudo systemctl reset-failed k3d-lab-ensure.service
sudo LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh install-autostart
Corrección v4.4.4: credenciales TLS para Gitea Actions
El error:
x509: certificate is valid for ... 192.168.68.61 ... not <otro valor>
indica que el secret K8S_TLS_SERVER_NAME no coincide con el host usado en
K8S_SERVER.
La versión agrega:
./deploy-lab.sh postdeploy-secrets
Este modo:
- recrea el ServiceAccount y RBAC de lectura;
- regenera token, CA, server y nombre TLS;
- guarda un kubeconfig persistente de validación;
- comprueba acceso real a Deployments en
ecommerce.
Valores esperados para este laboratorio:
K8S_SERVER=https://192.168.68.61:46421
K8S_TLS_SERVER_NAME=192.168.68.61
También incluye un build.yaml robustecido en:
repo-patches/apps-registry/.gitea/workflows/build.yaml
El workflow normaliza el secret TLS y detiene la ejecución con un mensaje
claro cuando no coincide con el host de K8S_SERVER.
Corrección v4.4.5: kubeconfig atómico para Gitea Actions
El workflow usa primero el secret único:
KUBE_CONFIG_DATA
Este secret contiene conjuntamente servidor, CA, tls-server-name y token.
Así se evita mezclar una CA de un clúster anterior con el servidor o token
actuales. Los cuatro secrets K8S_* se conservan únicamente como fallback.
Regeneración:
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh postdeploy-secrets
Luego actualiza en Gitea KUBE_CONFIG_DATA con el contenido de:
/DATA/AppData/k3d-lab/gitea-actions-secrets/KUBE_CONFIG_DATA.txt
El workflow muestra únicamente la huella SHA256 pública de la CA, nunca el certificado, token ni contenido del secret.
Corrección v4.4.6: token persistente para Gitea Actions
El modo postdeploy-secrets ya no utiliza kubectl create token, porque
ese mecanismo entrega tokens con duración limitada y el API Server puede
acortar la duración solicitada.
La versión crea y administra:
ServiceAccount/ecommerce/gitea-postdeploy-validator
Secret/ecommerce/gitea-postdeploy-validator-token
El Secret es de tipo:
kubernetes.io/service-account-token
El controlador de Kubernetes genera dentro del Secret:
token;ca.crt;namespace.
El RBAC continúa siendo de solo lectura sobre Deployments, ReplicaSets y
Pods del namespace ecommerce.
Regeneración:
LAB_HOST_IP=192.168.68.61 ./deploy-lab.sh postdeploy-secrets
Después actualiza solamente el secret de Gitea:
KUBE_CONFIG_DATA
usando:
/DATA/AppData/k3d-lab/gitea-actions-secrets/KUBE_CONFIG_DATA.txt
El workflow valida que el kubeconfig contenga token y muestra únicamente metadatos seguros: ServiceAccount y expiración, sin imprimir el token.
Corrección v4.4.6.1: indentación de build.yaml
El bloque Python usado para leer metadatos del token había quedado en la columna 1 del YAML. Eso cerraba prematuramente el bloque:
run: |
La corrección mantiene todo el código Python con la indentación YAML del
script. Al procesarse run: |, Bash recibe nuevamente el código Python
desde la columna 1.
Archivo corregido:
repo-patches/apps-registry/.gitea/workflows/build.yaml
También se incluye una copia en la raíz del paquete:
build.yaml