Files
jenkins-server/README.md
T
aagusfernandez02 8ad6f9e126 docs: update readme
2026-07-06 11:48:04 -03:00

12 KiB
Raw Blame History

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 Docker proxy.
  • Nginx Proxy Manager (NPM): reverse proxy interno que resuelve el hostname jenkins.laoficina1782.com y hace forward hacia jenkins-server:8080. Corre en el mismo servidor, red proxy.
  • Cloudflare Tunnel (cloudflared): es el único punto de entrada desde internet. El contenedor cloudflared abre una conexión saliente hacia el edge de Cloudflare (autenticada con TUNNEL_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 hacia http://npm:80 dentro de la red proxy.
  • Agentes (SCOs): cada terminal SCO conecta al master vía WebSocket (no JNLP), outbound hacia wss://jenkins.laoficina1782.com/. El proceso del agente corre como root.
  • 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, 443 y 81 directamente al host (ver docker-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 sco02sco07
ralph san-lorenzo sco03sco06
ralph rio-grande sco01sco06

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

  1. En el dashboard de Cloudflare (Zero Trust → Networks → Tunnels), crear un tunnel y copiar su token.
  2. Configurar la ruta pública (Public Hostname) del tunnel: jenkins.laoficina1782.comhttp://npm:80.
  3. Setear el token en .env como CLOUDFLARE_TUNNEL_TOKEN (ver arriba).
  4. Levantar el stack: el contenedor cloudflared se conecta solo, sin necesidad de abrir puertos en el servidor.

Conectar un agente SCO

  1. Descargar el agente desde Jenkins:

    curl -sO http://<server-ip>:8080/jnlpJars/agent.jar
    mv agent.jar /opt/jenkins-agent/agent.jar
    
  2. Obtener el secret del nodo: en Jenkins → Manage Jenkins → Nodes → (nombre del nodo).

  3. 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
    
  4. Habilitar y arrancar:

    systemctl daemon-reload
    systemctl enable --now jenkins-agent
    

Ejecutar un deploy

  1. En Jenkins, abrir el job deploy-ralph (o deploy-lab para el entorno de laboratorio).
  2. Click en Build with Parameters.
  3. Seleccionar la tienda destino en el dropdown (yabucoa / san-lorenzo / rio-grande para deploy-ralph; oficina para deploy-lab).
  4. 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
  • -s espera a que termine el build; -v muestra 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):

  1. Genera config de rclone apuntando a Minio a través del hostname público https://minio-api.laoficina1782.com (antes apuntaba directo a la IP del servidor por el puerto 9000; ahora pasa por el mismo túnel/proxy que Jenkins, con force_path_style = true porque el endpoint ya no es un bucket-subdominio).
  2. Sincroniza el bucket $CUSTOMER desde Minio hacia /opt/sco/ en el SCO (excluyendo datos persistentes del backend), con --s3-sign-accept-encoding=false --multi-thread-streams=0 para evitar problemas de firma/concurrencia a través del proxy.

Los pasos de instalación local (permisos, servicios systemd, autostart, crontabs, paquetes Python, imágenes Docker, inicialización de bases de datos del cpi-server, engine.properties de SymmetricDS) ya no están en este script — quedaron fuera del alcance actual de install.sh, que hoy solo se ocupa de traer el software del cliente a /opt/sco/.

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>, numero normalizado a 3 dígitos) y el label (sco <cliente> <tienda>) siguiendo la convención existente, y agrega el nodo al principio de .jenkins.nodes en jenkins.yaml.
  • cliente y tienda deben 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 de TIENDA. Si falla cualquiera de las dos, corta sin tocar jenkins.yaml.
  • Detecta nodos duplicados (mismo nombre final) y corta sin duplicar.
  • --skip-checks salta las validaciones de cliente/tienda existentes — lo usa create-client.sh internamente 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> ...]
  • --tienda es repetible: declara cada tienda que va a aparecer en el dropdown del Jenkinsfile (se deduplica automáticamente si se repite).
  • --nodo es repetible: uno por cada SCO a crear, en formato <tienda>:<numero>. Cada tienda referenciada en un --nodo tiene 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 job deploy-<cliente> en jenkins.yaml).
  • Si falla la creación de algún nodo a mitad del loop (ej. --nodo repetido), 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 que Jenkinsfile.ralph, con el parámetro NODO opcional), agrega el job deploy-<cliente> a jenkins.yaml y el volumen correspondiente a docker-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 el yq de Python (kislyuk/yq), que usa sintaxis de jq y 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 denied al 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 --numero con 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:

  1. Agregar los nodos del cliente en data/jenkins/jenkins.yaml con el label sco <cliente> <tienda>.
  2. Crear data/jenkins/pipelines/Jenkinsfile.<cliente> con el dropdown de tiendas.
  3. Agregar el job en la sección jobs de jenkins.yaml.
  4. Montar el nuevo Jenkinsfile en docker-compose.yml.
  5. Recrear el contenedor: docker compose up -d --build.