====== 7. Despliegue en el proyecto ====== Objetivo del tema: ''/git-flow publish'' = producción actualizada, **sin entrar nunca al servidor**. Y en cada push, GitHub compila y pasa todos los tests. ''develop'' no se despliega en ningún sitio: es local. Lo que ya sabéis: qué es GitHub Actions y un primer workflow con VPN y SSH, en [[clase:daw:daw:1eval:tema05#github_actions|Despliegue 5]]; cómo funciona nginx-proxy con ''VIRTUAL_HOST'', en [[clase:daw:daw:1eval:tema04.c#proxy_con_nginx|Despliegue 4.c]]. Lo nuevo aquí es sustituir la VPN y el SSH por un **runner dentro del servidor**. ===== El servidor ===== Un VPS compartido por toda la clase, con tres cosas montadas por el profesor: * **Docker.** * **nginx-proxy** en la red ''nginx-proxy-network'', escuchando en el 80. Cada aplicación se publica sola por su ''VIRTUAL_HOST'': ''.proyectosdaw.cip.fpmislata.com'', que ya está en ''compose.yaml'' desde el tema 4. * **Un runner autoalojado de GitHub por alumno**, registrado contra su repositorio. Un runner es un proceso que espera trabajos de GitHub Actions y los ejecuta **en la máquina donde está instalado**. Como está en el servidor, un workflow que se ejecute en él puede hacer ''docker compose'' directamente: ni SSH, ni VPN, ni registro de imágenes. El profesor lo registra con el token que da GitHub en //Settings → Actions → Runners → New self-hosted runner//; es la única vez que alguien entra al servidor. Todos los runners comparten el Docker del servidor, así que con ''docker ps'' cada uno ve los contenedores de los demás. Es un servidor de clase y se asume. Lo que separa a cada alumno es el nombre de proyecto de Compose (el del repositorio) y su subdominio. ===== Los mismos scripts ===== En el servidor no hay nada nuevo que aprender: la aplicación se opera con el **mismo script** del tema 4, ''app.sh'', con sus cuatro subcomandos. Lo único que cambia es una variable de entorno que fija el workflow: COMPOSE_FILE=compose.yaml * Con ''COMPOSE_FILE=compose.yaml'' Compose carga solo producción: el ''compose.override.yaml'' de desarrollo no se aplica, así que ni ''ng serve'', ni puertos publicados, ni datos de prueba. * ''app.sh start'' hace ''up -d --build --wait'': construye la imagen con el código del commit, sustituye los contenedores que han cambiado y espera a los ''healthcheck''. Nunca hace ''down -v'': el volumen ''db-data'' se queda y un despliegue **nunca borra la base de datos**; Flyway aplica las migraciones nuevas al arrancar. * Los workflows solo llaman a ''app.sh'', así que lo mismo que hace GitHub se puede lanzar a mano desde el servidor si un día hace falta. ===== Secretos ===== La contraseña de la base de datos de producción no está en ningún fichero: está en //Settings → Secrets and variables → Actions// del repositorio como ''DB_PASSWORD''. El workflow la pasa como variable de entorno a ''app.sh'' y Compose la lee en ''${DB_PASSWORD}''. En desarrollo esa misma variable viene del ''.env'' del worktree. ===== Los workflows ===== Un solo fichero para CI y CD: name: CI/CD on: push: branches: [develop, main] workflow_dispatch: jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: { distribution: temurin, java-version: 25 } - uses: actions/setup-node@v4 with: { node-version: 22 } - name: Levantar la aplicación env: { DB_PASSWORD: ci } run: | docker network create nginx-proxy-network docker compose up -d --build --wait - name: Tests del backend (unitarios y ArchUnit) working-directory: backend run: mvn -B test - name: Tests de API y E2E working-directory: frontend run: | npm ci npx playwright install --with-deps chromium npm run test:api npm run test:e2e deploy: needs: ci if: github.ref == 'refs/heads/main' runs-on: self-hosted steps: - uses: actions/checkout@v4 - name: Desplegar env: COMPOSE_FILE: compose.yaml DB_PASSWORD: ${{ secrets.DB_PASSWORD }} run: ./app.sh start - name: Smoke test run: curl -fsS http://.proyectosdaw.cip.fpmislata.com/api/clientes > /dev/null Lo que hay que entender: * **''ci'' corre en un runner de GitHub** (''ubuntu-latest''), en cada push a ''develop'' y a ''main''. Levanta la aplicación con el mismo ''compose.yaml'' + ''compose.override.yaml'' del portátil (sin ''.env'': valen los puertos por defecto y el perfil ''datos-prueba''), y pasa los tests con los mismos comandos que en el worktree: ''mvn test'', ''npm run test:api'' y ''npm run test:e2e''. ''--wait'' espera a los ''healthcheck''. * **''deploy'' corre en el runner del servidor** (''self-hosted''), solo en ''main'' y solo si ''ci'' ha pasado (''needs''). Hace ''checkout'' del commit y ''./app.sh start'' con ''COMPOSE_FILE=compose.yaml'': la imagen se construye en el servidor con ese código. * **El smoke test** es el último paso: una petición a la URL de producción que tiene que responder 200. Si el despliegue ha dejado la aplicación caída, el workflow sale en rojo y se ve en el acto. * ''workflow_dispatch'' permite relanzarlo a mano desde la pestaña //Actions// (siempre sobre ''main''). Y dos workflows pequeños de operación, ''status.yml'' y ''logs.yml'', solo con ''workflow_dispatch'' y un paso ''./app.sh status'' o ''./app.sh logs'' (con el mismo ''COMPOSE_FILE'') en ''self-hosted''. Son la forma de ver qué pasa en producción sin acceso al servidor: se lanzan desde //Actions// y la salida queda en el log del workflow. ===== La IA y los workflows ===== El YAML lo escribe el agente a partir de lo de arriba. Y no hay skill para cuando CI sale en rojo, porque **no debería salir en rojo**: pasa los mismos comandos que ''/git-flow finish'' acaba de pasar en el worktree. Si sale, es que algo distingue el runner del portátil (un fichero que no está en git, una variable, un puerto) o que ha fallado el despliegue o el smoke test. Un workflow rojo tiene un log largo y la causa está en tres líneas: se abre el paso fallido en //Actions//, se copia su log y se le pregunta al agente en una conversación nueva. Desde la terminal, ''gh run view --log-failed'' da solo los pasos que han fallado, que es lo que hay que pegarle (tema 5: no pegarle logs enteros). ===== Ejercicios ===== ==== Ejercicio 1 ==== * Crea un issue «CI» e inícialo. * Comprueba que ''app.sh'' funciona como lo va a ejecutar el servidor, sin ''.env'' y solo con producción: ''COMPOSE_FILE=compose.yaml ./app.sh start'' desde el repositorio principal, ''./app.sh status'' y luego ''./app.sh stop''. * Añade el secreto ''DB_PASSWORD'' en //Settings → Secrets and variables → Actions//. * Escribe ''.github/workflows/ci-cd.yml'' solo con el job ''ci''. Finaliza el issue y mira en //Actions// cómo se ejecuta en el push a ''develop''. Si sale en rojo, arréglalo hasta que esté en verde. ==== Ejercicio 2 ==== * Pide al profesor que registre tu runner en el servidor. Comprueba en //Settings → Actions → Runners// que aparece como //Idle//. * Crea un issue «Despliegue» e inícialo. Añade el job ''deploy'' al workflow con el ''needs: ci'', el ''if'' sobre ''main'' y el smoke test contra tu URL. * Finaliza el issue y ''/git-flow publish''. Sigue la ejecución en //Actions// y comprueba que la aplicación responde en ''%%http://.proyectosdaw.cip.fpmislata.com%%''. ==== Ejercicio 3 ==== * Añade ''status.yml'' y ''logs.yml'' con ''workflow_dispatch'' sobre ''self-hosted''. * Lánzalos desde //Actions// y lee la salida de ''app.sh status'' en el log. ==== Ejercicio 4 ==== * Cambia a propósito la URL del smoke test por una que no existe, finaliza el issue y publica. CI pasa y ''deploy'' sale en rojo. Copia el log del paso fallido, pregúntaselo al agente y comprueba que señala la URL. Arréglala y vuelve a publicar. * Haz un cambio visible en la aplicación, finaliza y publica. Comprueba en producción que el cambio está y que los datos que había siguen estando. Fuera de este tema, por ser de un sistema real y no de un proyecto de clase: entornos de ''dev'' y ''pre'', copias de seguridad, copiar datos de producción, tareas programadas y HTTPS (el proxy sirve HTTP).