11 KiB
Jenkins SCO — Deploy Server
Jenkins centralizado para el despliegue de software en terminales SCO (Self-Checkout) en producción.
Estructura del repositorio
.
├── docker-compose.yml
└── data/
└── jenkins/
├── Dockerfile # Imagen Jenkins con plugins
├── jenkins.yaml # Configuración JCasC (nodos, seguridad, jobs)
├── deploy/
│ └── install.sh # Script desplegado en cada SCO
└── pipelines/
├── Jenkinsfile.ralph # Pipeline del cliente Ralph
└── Jenkinsfile.lab # Pipeline del entorno de laboratorio
Arquitectura
- Jenkins master: corre en Docker en un servidor (
172.19.13.204). No expone puertos al host; solo es alcanzable dentro de la red interna Dockerproxy. - Nginx Proxy Manager (NPM): reverse proxy interno que resuelve el hostname
jenkins.laoficina1782.comy hace forward haciajenkins-server:8080. Corre en el mismo servidor, redproxy. - Cloudflare Tunnel (
cloudflared): es el único punto de entrada desde internet. El contenedorcloudflaredabre una conexión saliente hacia el edge de Cloudflare (autenticada conTUNNEL_TOKEN), por lo que no hace falta abrir puertos entrantes en el firewall/router del servidor. El tunnel está configurado del lado de Cloudflare para enrutar el hostname público haciahttp://npm:80dentro de la redproxy. - Agentes (SCOs): cada terminal SCO conecta al master vía WebSocket (no JNLP), outbound hacia
wss://jenkins.laoficina1782.com/. El proceso del agente corre comoroot. - Storage: Minio almacena el software de cada cliente en buckets separados (ej: bucket
ralph).
Flujo de tráfico
Internet
│
▼
Cloudflare Edge (DNS + proxy)
│ túnel saliente autenticado (TUNNEL_TOKEN)
▼
cloudflared (contenedor, red "proxy", sin puertos publicados)
│ http://npm:80
▼
NPM (nginx-proxy-manager) — vhost jenkins.laoficina1782.com
│ http://jenkins-server:8080
▼
jenkins-server (sin puertos publicados al host)
Jenkins no expone puertos al host: NPM y cloudflared se comunican con él por nombre de contenedor dentro de la red Docker proxy. Como no hay puertos entrantes abiertos en el servidor, toda la superficie pública depende de Cloudflare (WAF, DNS, TLS terminado en el edge).
⚠️ NPM publica además
80,443y81directamente al host (verdocker-compose.yml). Si el firewall del servidor permite esas conexiones, Jenkins/NPM también serían alcanzables sin pasar por Cloudflare. Para que el tunnel sea realmente el único punto de entrada, esos puertos deberían quedar bloqueados a nivel de firewall/red salvo acceso local.
Variables de entorno
El tunnel necesita el token generado en el dashboard de Cloudflare (Zero Trust → Networks → Tunnels):
cp .env.example .env
# editar .env y setear CLOUDFLARE_TUNNEL_TOKEN=<token>
Labels de agentes
Formato: sco <cliente> <tienda>
| Cliente | Tienda | Agentes |
|---|---|---|
| ralph | yabucoa | sco02–sco07 |
| ralph | san-lorenzo | sco03–sco06 |
| ralph | rio-grande | sco01–sco06 |
Levantar el servidor
docker compose up -d --build
Jenkins queda disponible en https://jenkins.laoficina1782.com una vez configurados el proxy y el tunnel.
Nginx Proxy Manager
Panel de administración: http://172.19.13.204:81 (o localmente en el servidor).
Configuración del proxy host para Jenkins:
| Campo | Valor |
|---|---|
| Domain Names | jenkins.laoficina1782.com |
| Scheme | http |
| Forward Hostname | jenkins-server |
| Forward Port | 8080 |
| Websockets Support | ✅ activado |
Cloudflare Tunnel
- En el dashboard de Cloudflare (Zero Trust → Networks → Tunnels), crear un tunnel y copiar su token.
- Configurar la ruta pública (Public Hostname) del tunnel:
jenkins.laoficina1782.com→http://npm:80. - Setear el token en
.envcomoCLOUDFLARE_TUNNEL_TOKEN(ver arriba). - Levantar el stack: el contenedor
cloudflaredse conecta solo, sin necesidad de abrir puertos en el servidor.
Conectar un agente SCO
-
Descargar el agente desde Jenkins:
curl -sO http://<server-ip>:8080/jnlpJars/agent.jar mv agent.jar /opt/jenkins-agent/agent.jar -
Obtener el secret del nodo: en Jenkins → Manage Jenkins → Nodes → (nombre del nodo).
-
Crear el servicio systemd en el SCO:
# /etc/systemd/system/jenkins-agent.service [Unit] Description=Jenkins Agent After=network.target [Service] User=root ExecStart=/usr/bin/java -jar /opt/jenkins-agent/agent.jar \ -url https://jenkins.laoficina1782.com/ \ -webSocket \ -secret <SECRET> \ -name "<nombre-del-nodo>" \ -workDir "/opt/jenkins-agent" Restart=always RestartSec=10 [Install] WantedBy=multi-user.target -
Habilitar y arrancar:
systemctl daemon-reload systemctl enable --now jenkins-agent
Ejecutar un deploy
- En Jenkins, abrir el job
deploy-ralph(odeploy-labpara el entorno de laboratorio). - Click en Build with Parameters.
- Seleccionar la tienda destino en el dropdown (yabucoa / san-lorenzo / rio-grande para
deploy-ralph; oficina paradeploy-lab). - Opcionalmente, completar NODO con el nombre exacto de un SCO (ej.
ralph-yabucoa-sco05) para deployar solo en ese nodo. Si se deja vacío, despliega en paralelo en todos los SCOs online de la tienda seleccionada.
También se puede disparar desde consola con el Jenkins CLI o curl, ver Ejecutar un job desde consola.
Ejecutar un job desde consola
curl -O https://jenkins.laoficina1782.com/jnlpJars/jenkins-cli.jar
java -jar jenkins-cli.jar -s https://jenkins.laoficina1782.com/ \
-auth TU_USUARIO:TU_API_TOKEN \
build deploy-lab -p TIENDA=oficina -s -v
# Deploy a un solo nodo de la tienda
java -jar jenkins-cli.jar -s https://jenkins.laoficina1782.com/ \
-auth TU_USUARIO:TU_API_TOKEN \
build deploy-lab -p TIENDA=oficina -p NODO=lab-oficina-sco-77 -s -v
-sespera a que termine el build;-vmuestra el log en consola.- El API Token se genera en el usuario de Jenkins → Configure → API Token.
- Alternativa sin el CLI:
curl -X POST https://jenkins.laoficina1782.com/job/deploy-lab/buildWithParameters --user TU_USUARIO:TU_API_TOKEN --data-urlencode "TIENDA=oficina".
Qué hace install.sh
Recibe el nombre del cliente como argumento ($1):
- Genera config de rclone apuntando al Minio local.
- Sincroniza el bucket
$CUSTOMERdesde Minio hacia/opt/sco/en el SCO (excluyendo datos persistentes del backend). - Ajusta permisos en
/opt/sco/. - Instala servicios systemd desde
/opt/sco/resources/services/. - Configura autostart del usuario
sco. - Instala crontabs de
rootysco. - Instala paquetes Python offline.
- Carga imágenes Docker.
- Inicializa carpetas y archivos de base de datos del cpi-server.
- Genera
engine.propertiespara SymmetricDS (solo en primera instalación).
Scripts de alta (create-node.sh / create-client.sh)
data/jenkins/scripts/ tiene dos scripts para no tener que editar jenkins.yaml, los Jenkinsfiles y docker-compose.yml a mano.
Prerequisito: yq (la versión de Go, no la de Python — son incompatibles, ver más abajo) y que los scripts tengan permiso de ejecución:
chmod +x data/jenkins/scripts/*.sh
Ambos solo editan archivos — no hacen docker compose up -d --build ni commits. Después de correrlos, revisá con git diff y aplicá el rebuild vos mismo.
create-node.sh — agregar un nodo a un cliente ya existente
data/jenkins/scripts/create-node.sh --cliente <cliente> --tienda <tienda> --numero <numero>
- Arma el nombre (
<cliente>-<tienda>-sco<numero>,numeronormalizado a 3 dígitos) y el label (sco <cliente> <tienda>) siguiendo la convención existente, y agrega el nodo al principio de.jenkins.nodesenjenkins.yaml. clienteytiendadeben ser minúsculas, números y guiones (^[a-z0-9-]+$) — nada de mayúsculas ni espacios.- Valida que el cliente ya esté dado de alta (que exista
Jenkinsfile.<cliente>) y que la tienda esté en su dropdown deTIENDA. Si falla cualquiera de las dos, corta sin tocarjenkins.yaml. - Detecta nodos duplicados (mismo nombre final) y corta sin duplicar.
--skip-checkssalta las validaciones de cliente/tienda existentes — lo usacreate-client.shinternamente al dar de alta un cliente nuevo (en ese momento el Jenkinsfile todavía no existe).
Ejemplo:
data/jenkins/scripts/create-node.sh --cliente ralph --tienda yabucoa --numero 8
# -> nodo "ralph-yabucoa-sco008", label "sco ralph yabucoa"
create-client.sh — dar de alta un cliente nuevo completo
data/jenkins/scripts/create-client.sh --cliente <cliente> \
--tienda <nombre> [--tienda <nombre> ...] \
--nodo <tienda>:<numero> [--nodo <tienda>:<numero> ...]
--tiendaes repetible: declara cada tienda que va a aparecer en el dropdown del Jenkinsfile (se deduplica automáticamente si se repite).--nodoes repetible: uno por cada SCO a crear, en formato<tienda>:<numero>. Cadatiendareferenciada en un--nodotiene que haber sido declarada con algún--tienda.- Corta antes de tocar nada si el cliente ya existe (ya hay
Jenkinsfile.<cliente>o ya hay un jobdeploy-<cliente>enjenkins.yaml). - Si falla la creación de algún nodo a mitad del loop (ej.
--nodorepetido), revierte automáticamente los nodos ya creados en esa misma corrida antes de cortar — no deja nodos huérfanos. - Al terminar, genera
data/jenkins/pipelines/Jenkinsfile.<cliente>(mismo esqueleto queJenkinsfile.ralph, con el parámetroNODOopcional), agrega el jobdeploy-<cliente>ajenkins.yamly el volumen correspondiente adocker-compose.yml.
Ejemplo:
data/jenkins/scripts/create-client.sh --cliente econo-mbs \
--tienda yabucoa --tienda san-lorenzo \
--nodo yabucoa:1 --nodo yabucoa:2 --nodo yabucoa:3 \
--nodo san-lorenzo:3 --nodo san-lorenzo:4
Troubleshooting
usage: yq [-h] [--yaml-output] .../yq: error: argument files: can't open '.jenkins...': tenés instalado elyqde Python (kislyuk/yq), que usa sintaxis dejqy es incompatible. Desinstalalo (pip uninstall yq) e instalá el de Go:wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq chmod +x /usr/local/bin/yq hash -r # si ya habías corrido "yq" antes en esa misma terminal, bash puede tener cacheada la ruta vieja yq --version # debe decir "yq (https://github.com/mikefarah/yq/) version v4..."Permission deniedal correr un script: falta el bit de ejecución —chmod +x data/jenkins/scripts/*.sh.printf: NNN: invalid octal number: no debería pasar (ya está resuelto), pero si aparece es porque un--numerocon cero a la izquierda se está interpretando como octal en vez de decimal.
Agregar un nuevo cliente
Con los scripts (recomendado, ver arriba), o a mano siguiendo estos pasos:
- Agregar los nodos del cliente en
data/jenkins/jenkins.yamlcon el labelsco <cliente> <tienda>. - Crear
data/jenkins/pipelines/Jenkinsfile.<cliente>con el dropdown de tiendas. - Agregar el job en la sección
jobsdejenkins.yaml. - Montar el nuevo Jenkinsfile en
docker-compose.yml. - Recrear el contenedor:
docker compose up -d --build.