Zaroz
Despliegue desde CI/CD

Despliega desde CI con un token de API

Todas las guías de esta sección hacen las mismas cuatro cosas, en cualquier sistema de CI:

  1. construir una imagen y subirla a un registro, con una etiqueta inmutable por commit;
  2. decirle a Zaroz que ejecute esa imagen (PUT /service-instances/{id}/source);
  3. iniciar un despliegue y esperar a que termine (POST /service-instances/{id}/deployments);
  4. hacer fallar el trabajo si el despliegue falló y volver a poner la imagen anterior.

Los pasos 2 a 4 son un pequeño script, zaroz-deploy.sh. Las guías de GitHub, GitLab y Forgejo solo se diferencian en cómo construyen, dónde viven los secretos y cómo evitan que dos despliegues corran a la vez.

Sistema de CIGuía
GitHub ActionsGitHub Actions
GitLab CIGitLab CI
Forgejo Actions y Gitea ActionsForgejo y Gitea Actions
cualquier otro (Jenkins, Drone, un cron en tu servidor)Shell genérico

Crea un token que solo pueda desplegar

CI nunca debería tener un token que pueda leer tus facturas o borrar tu cuenta. Dale un token limitado a una instancia de servicio.

services.deploy es el permiso de despliegue. Permite exactamente dos escrituras sobre un servicio: PUT /service-instances/{id}/source y POST /service-instances/{id}/deployments. No lee nada, no cambia variables de entorno, copias de seguridad, archivos ni el cortafuegos, no inicia, detiene ni reinicia el servicio (services.power), no abre terminal y no toca la facturación ni los miembros. Añade services.read para que el trabajo pueda leer el source y esperar a que termine el despliegue. Qué token usar depende de lo estrecho que lo necesites:

  • Un token personal (POST /me/api-tokens, abajo) se puede limitar a una instancia de servicio. Actúa como la persona que lo creó y para crearlo hace falta una sesión completa: un token de API no puede crear otro.
  • Un token de organización (POST /organizations/{id}/api-tokens) pertenece a la organización y puede sobrevivir a quien lo creó ("lifetime": "organization_owned"), pero cada permiso se limita a toda la organización, nunca a un solo servicio. GET /organizations/{id}/api-tokens/grantable-capabilities lista lo que puedes poner en él. Úsalo para un pipeline de equipo que despliega varios servicios.

curl

TerminalCode
# 1. what your own account may grant # (the header goes through curl's config on stdin so it never shows in `ps`) zcurl() { printf 'header = "Authorization: Bearer %s"\n' "$ZAROZ_TOKEN" | curl -sS -K - "$@"; } zcurl "$ZAROZ_API/me/api-token-capabilities" # 2. a token that can only manage ONE service instance zcurl -X POST "$ZAROZ_API/me/api-tokens" -H "Content-Type: application/json" -d '{ "name": "ci-deploy-my-api", "expires_at": "2027-01-01T00:00:00Z", "authorization": {"grants": [ {"capability": "services.deploy", "scope": {"kind": "resource", "resource_type": "service_instance", "resource_id": "<service instance uuid>"}}, {"capability": "services.read", "scope": {"kind": "resource", "resource_type": "service_instance", "resource_id": "<service instance uuid>"}} ]} }' # {"token": {...}, "secret": "zaroz_..."} the secret is shown this once

TypeScript

TypeScriptCode
const res = await fetch(`${API}/me/api-tokens`, { method: "POST", headers: { Authorization: `Bearer ${ADMIN_JWT}`, "Content-Type": "application/json" }, body: JSON.stringify({ name: "ci-deploy-my-api", expires_at: "2027-01-01T00:00:00Z", authorization: { grants: ["services.deploy", "services.read"].map((capability) => ({ capability, scope: { kind: "resource", resource_type: "service_instance", resource_id: SERVICE }, })), }, }), }); console.log((await res.json()).secret);

Python

Code
r = requests.post( f"{API}/me/api-tokens", headers={"Authorization": "Bearer " + ADMIN_JWT}, json={ "name": "ci-deploy-my-api", "expires_at": "2027-01-01T00:00:00Z", "authorization": {"grants": [ {"capability": c, "scope": {"kind": "resource", "resource_type": "service_instance", "resource_id": SERVICE}} for c in ("services.deploy", "services.read") ]}, }, timeout=20, ) print(r.json()["secret"])
  • resource_id es el id de la instancia de servicio, el mismo UUID que aparece en la URL del Workload en la consola.
  • Dale al token un expires_at y pon un recordatorio para rotarlo. Un token personal se revoca con DELETE /me/api-tokens/{token_id}.
  • El secreto (zaroz_...) se muestra una sola vez. Guárdalo directamente en el almacén de secretos de tu CI. Nunca lo pongas en el repositorio, en archivos .env que se suban ni en la imagen.
  • Mantén el secreto fuera de los argumentos de la línea de comandos, que cualquier usuario de un runner compartido puede leer con ps. Pásalo como variable de entorno desde el almacén de secretos de CI, como hacen los flujos; el script se lo entrega a curl por la entrada estándar.
  • Crea un token por servicio y por entorno (staging, producción), de modo que revocar uno no detenga a los demás.

Un token personal actúa como la persona que lo creó: si esa persona deja la organización o pierde el acceso al servicio, el token deja de funcionar. Para un equipo, prefiere un token de organización con lifetime: "organization_owned" (sobrevive a quien lo creó, pero cubre todos los servicios de la organización):

TerminalCode
zcurl -X POST "$ZAROZ_API/organizations/<uuid de la organización>/api-tokens" -H "Content-Type: application/json" -d '{ "name": "ci-deploy", "lifetime": "organization_owned", "expires_at": "2027-01-01T00:00:00Z", "capabilities": ["services.deploy", "services.read"] }' # {"token": {...}, "secret": "zaroz_pat_..."} se muestra una sola vez

Para crearlo hace falta ser Owner o Admin. Si omites lifetime, el token se revoca cuando termina tu pertenencia.

Qué hace el token en cada paso

PasoPeticiónPermiso
Leer qué se ejecuta ahoraGET /service-instances/{id}/sourceservices.read
Elegir la imagenPUT /service-instances/{id}/sourceservices.deploy
DesplegarPOST /service-instances/{id}/deploymentsservices.deploy
Esperar y comprobarGET /service-instances/{id}/deploymentsservices.read

PUT /source no despliega: solo guarda lo que bajará el siguiente despliegue. POST /deployments no lleva cuerpo y despliega lo guardado, así que no hay forma de desplegar algo que no se guardó antes.

Despliegue, éxito y reversión

Un despliegue tiene un status (cómo terminó la operación) y un outcome (succeeded o failed, con un failure_reason como cluster_rejected o subscription_not_active). succeeded significa que el despliegue se aplicó; no dice que los pods nuevos estén sanos. Por eso el script también lee workloads[].status de la misma lista y exige que todos estén en running.

Revertir es desplegar la imagen anterior. El script lee running_image antes de cambiar nada y, si el despliegue nuevo falla, vuelve a guardar esa imagen y la despliega. Con ROLLBACK=0 se desactiva. Un despliegue se rechaza con 409 mientras una restauración, un cambio de tamaño u otro despliegue tiene el servicio, y la respuesta dice cuál y desde cuándo.

Zaroz no vigila un registro por si una etiqueta se mueve (watch_tag siempre es false), así que una canalización que sube latest y no despliega nada nunca actualizará. Usa una etiqueta única por commit, como hacen las guías.

Imágenes privadas

Si la imagen es privada, añade un token de registro de solo lectura y pásalo como REGISTRY_USERNAME y REGISTRY_PASSWORD. Zaroz lo guarda, nunca lo devuelve y lo usa para descargar desde dentro del clúster. No debe ser el token que usa tu CI para subir.

Repositorios de ejemplo

Cada ejemplo trae un flujo de despliegue listo para los tres sistemas de CI y se ejecuta de punta a punta contra un proyecto tuyo.

RepositorioQué muestra
example-scaled-apiuna API HTTP con de 1 a 8 unidades autoescaladas detrás de un endpoint
example-queue-workertrabajadores que escalan con una cola
example-game-networkhub, lobbies, duelos y parcelas (Minecraft), fase 2
example-job-runneruna unidad por trabajo, iniciada por clave, resultado por evento

Viven en la organización zaroz-oss, en https://git.zaroz.cloud/zaroz-oss/.

Last modified on