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

246 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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):
```sh
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
```sh
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.com``http://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:
```sh
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:
```ini
# /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:
```sh
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).
### Ejecutar un job desde consola
```sh
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`](https://github.com/mikefarah/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:
```sh
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
```sh
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:
```sh
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
```sh
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:
```sh
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:
```sh
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`.