Zaroz
Despliegue desde CI/CD

Despliega desde cualquier shell

zaroz-deploy.sh es todo lo que llaman las guías específicas de cada CI. Necesita curl, jq y una shell POSIX, así que funciona en Jenkins, Drone, Woodpecker, Buildkite, un Makefile o un trabajo cron.

TerminalCode
IMAGE=ghcr.io/acme/api:3f2a1c9d8e7b \ ZAROZ_SERVICE=2d1a0c1e-0000-0000-0000-000000000000 \ ZAROZ_TOKEN=zaroz_... \ sh deploy/zaroz-deploy.sh
VariableObligatoriaSignificado
IMAGEsíhost/repositorio:etiqueta que se ejecutará
ZAROZ_SERVICEsíel UUID de la instancia de servicio
ZAROZ_TOKENsíel token de despliegue
REGISTRY_USERNAME, REGISTRY_PASSWORDnocredenciales de solo lectura para un registro privado
ROLLBACKno1 (por defecto) vuelve a poner la imagen anterior si el despliegue falla; 0 lo desactiva
DEPLOY_TIMEOUT_SECONDSnocuánto esperar, 600 por defecto
ZAROZ_APInopor defecto https://kernel.zaroz.cloud/api/v2

El código de salida 0 significa que la imagen nueva está sirviendo. Cualquier otro significa que no; si se ejecutó la reversión, la imagen anterior vuelve a servir.

El script

TerminalCode
#!/usr/bin/env sh # Deploy an image to a Zaroz Workload and fail the job if the rollout fails. # # IMAGE=ghcr.io/acme/api:3f2a1c ZAROZ_SERVICE=<uuid> ZAROZ_TOKEN=zaroz_... ./zaroz-deploy.sh # # Steps: remember what runs now, point the Workload's source at IMAGE, start a # deployment, wait for it, check the result, and (ROLLBACK=1, the default) put # the previous image back when the new one did not roll out. # # Optional: REGISTRY_USERNAME / REGISTRY_PASSWORD when the image is in a private # registry (stored by Zaroz, never returned by the API), DEPLOY_TIMEOUT_SECONDS, # ROLLBACK=0 to disable the automatic rollback, ZAROZ_API. # # Needs: curl, jq. POSIX sh. No other dependency. set -eu : "${ZAROZ_TOKEN:?set ZAROZ_TOKEN to the deploy token}" : "${ZAROZ_SERVICE:?set ZAROZ_SERVICE to the service instance uuid}" : "${IMAGE:?set IMAGE to host/repository:tag}" API="${ZAROZ_API:-https://kernel.zaroz.cloud/api/v2}" TIMEOUT="${DEPLOY_TIMEOUT_SECONDS:-600}" ROLLBACK="${ROLLBACK:-1}" BASE="$API/service-instances/$ZAROZ_SERVICE" # Secrets never go in argv (visible to `ps` for every user on a shared runner): # the bearer header travels in curl's config on stdin, the body in a 0600 temp file. # The registry login is read by jq from the environment, not from its arguments. umask 077 TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT export REGISTRY_USERNAME="${REGISTRY_USERNAME:-}" REGISTRY_PASSWORD="${REGISTRY_PASSWORD:-}" call() { # method path [json body]; prints the body, fails on a non-2xx _out="$TMP/out"; : > "$_out" if [ -n "${3:-}" ]; then printf '%s' "$3" > "$TMP/body"; set -- "$1" "$2" --data "@$TMP/body"; fi _method="$1"; _path="$2"; shift 2 _code="$(printf 'header = "Authorization: Bearer %s"\n' "$ZAROZ_TOKEN" \ | curl -sS -K - -o "$_out" -w '%{http_code}' -X "$_method" "$BASE$_path" \ -H 'Content-Type: application/json' "$@")" rm -f "$TMP/body" case "$_code" in 2??) cat "$_out" ;; *) echo "zaroz: $_method $_path answered $_code: $(cat "$_out")" >&2; return 1 ;; esac } # host/repository:tag -> three parts (a tag after the last colon of the last path segment) split_image() { _host="${1%%/*}"; _rest="${1#*/}" case "$_rest" in *:*) _repo="${_rest%:*}"; _tag="${_rest##*:}" ;; *) _repo="$_rest"; _tag=latest ;; esac printf '%s %s %s' "$_host" "$_repo" "$_tag" } set_source() { # image set -- $(split_image "$1") # Registry login is only sent when given; absent leaves the stored login alone. call PUT /source "$(jq -n --arg h "$1" --arg r "$2" --arg t "$3" \ '{kind:"registry",host:$h,repository:$r,tag:$t} + (if (env.REGISTRY_USERNAME // "") != "" then {credentials:{username:env.REGISTRY_USERNAME,password:env.REGISTRY_PASSWORD}} else {} end)')" >/dev/null } deploy_and_wait() { # prints the deployment json; exit 0 when it rolled out _dep="$(call POST /deployments)" _id="$(printf '%s' "$_dep" | jq -r .id)" echo "zaroz: deployment #$(printf '%s' "$_dep" | jq -r .number) started" >&2 _deadline=$(( $(date +%s) + TIMEOUT )) while :; do _list="$(call GET '/deployments?page_size=5')" _active="$(printf '%s' "$_list" | jq -r '.active_deployment_id // empty')" if [ -z "$_active" ]; then break; fi [ "$(date +%s)" -lt "$_deadline" ] || { echo "zaroz: timed out after ${TIMEOUT}s" >&2; return 1; } sleep 5 done _outcome="$(printf '%s' "$_list" | jq -r --arg id "$_id" '[.data[]|select(.id==$id)][0].outcome // "unknown"')" _reason="$(printf '%s' "$_list" | jq -r --arg id "$_id" '[.data[]|select(.id==$id)][0].failure_reason // ""')" # "succeeded" only says the rollout was applied; the workloads say whether it is serving. _bad="$(printf '%s' "$_list" | jq -r '[.workloads[]|select(.status!="running")|.role+"="+.status]|join(",")')" if [ "$_outcome" != succeeded ]; then echo "zaroz: rollout $_outcome ${_reason:+($_reason)}" >&2; return 1; fi if [ -n "$_bad" ]; then echo "zaroz: not serving: $_bad" >&2; return 1; fi } PREVIOUS="$(call GET /source | jq -r '.running_image // empty')" echo "zaroz: running now: ${PREVIOUS:-nothing}; deploying $IMAGE" >&2 set_source "$IMAGE" if deploy_and_wait; then echo "zaroz: $IMAGE is serving" >&2 exit 0 fi if [ "$ROLLBACK" = 1 ] && [ -n "$PREVIOUS" ] && [ "$PREVIOUS" != "$IMAGE" ]; then echo "zaroz: rolling back to $PREVIOUS" >&2 set_source "$PREVIOUS" deploy_and_wait && echo "zaroz: rolled back to $PREVIOUS" >&2 || echo "zaroz: rollback failed too" >&2 fi exit 1

Por qué el token nunca va en la línea de comandos

Los argumentos de un proceso los puede leer cualquier usuario de la máquina (ps, /proc/<pid>/cmdline) y los runners de CI suelen compartirse. Por eso el script nunca escribe el token ni la contraseña del registro en un argumento: la cabecera Authorization llega a curl -K - como configuración por la entrada estándar (printf es un comando interno de la shell, así que no aparece en ninguna lista de procesos), el cuerpo de la petición pasa por un archivo temporal privado y jq lee las credenciales del registro desde el entorno. Copia el mismo hábito en tus scripts: curl -H "Authorization: Bearer $TOKEN" es el patrón que debes evitar.

Qué hace cada petición

El script son cinco peticiones. Puedes ejecutarlas tú mismo:

TerminalCode
B="$ZAROZ_API/service-instances/$ZAROZ_SERVICE" zcurl() { printf 'header = "Authorization: Bearer %s"\n' "$ZAROZ_TOKEN" | curl -sS -K - "$@"; } zcurl "$B/source" | jq '{running_image, pending}' # qué corre ahora y si hay un cambio pendiente zcurl -X PUT "$B/source" -H 'Content-Type: application/json' \ -d '{"kind":"registry","host":"ghcr.io","repository":"acme/api","tag":"3f2a1c9d8e7b"}' zcurl -X POST "$B/deployments" # despliega lo guardado; sin cuerpo zcurl "$B/deployments?page_size=5" \ | jq '{active_deployment_id, newest: .data[0] | {number, status, outcome, failure_reason}, workloads}'

failure_reason es uno de not_configured, product_not_deployable, subscription_not_active, hostname_taken, invalid_hostname, network_unavailable, cluster_rejected, unknown. Zaroz nunca repite mensajes en bruto del clúster, porque citan nombres de host internos; recibes el vocabulario cerrado.

Probar el script sin un Workload

El repositorio unit-examples incluye _shared/test/run.sh, que ejecuta este script contra una pequeña simulación de esas rutas y comprueba el camino de éxito y el de reversión.

Last modified on