====== 7. GitHub Actions ======
===== Introducción =====
El despliegue continuo es desplegar las aplicaciones web lo más pronto posible: cada cambio que se sube al repositorio acaba en el servidor sin que nadie tenga que hacerlo a mano.
Para eso se usa un **servidor de automatización**: un programa que, cuando ocurre algo (un ''push'' a una rama, que alguien pulse un botón, una hora concreta), ejecuta solo una lista de pasos escrita de antemano. Los pasos son los que haríamos nosotros a mano:
- Descargar el código del repositorio.
- Compilarlo y pasar los tests.
- Construir la imagen Docker.
- Desplegar la nueva versión
Si un paso falla, se para ahí y avisa: lo que no compila o no pasa los tests no llega al servidor.
Existen muchas formasde hacerlo, antiguamente se hacía con una herramienta de software libre llamada [[https://www.jenkins.io/|Jenkins]] pero actualmente se usan las acciones de GitHub o GitLab , etc.
GitHub Actions es el sistema de automatización integrado en GitHub. Cada fichero YAML dentro de ''.github/workflows/'' es un **workflow**, y en él se define:
* **Cuándo se ejecuta:** al hacer un push, cuando tú decidas o a una hora programada.
* **Qué se ejecuta:** las tareas (**jobs**), como compilar, pasar los tests o desplegar. Cada job se compone de pasos (**steps**), que pueden ser órdenes de shell o //actions// reutilizables hechas por otros.
* **Dónde se ejecuta:** en un **runner**, el programa que ejecuta los jobs. Puede estar en una máquina virtual de GitHub o en una máquina tuya ([[#runners|Runners]]).
Mas información:
* [[https://blog.develat.io/pipelines-ci-cd/|Integración continua y despliegue continuo]]
* [[https://devopsti.wordpress.com/2014/09/26/integracion-continua-ci-entrega-continua-cd-y-despliegue-continuo-cd/|Integración continua (CI), Entrega continua (CD) y Despliegue Continuo (CD)]]
* [[https://geeks.ms/jorge/2019/02/25/integracion-entrega-y-despliegue-continuo-diferencias-y-similitudes/|Integración, entrega y despliegue continuo. Diferencias y similitudes]]
===== Workflows =====
Un **workflow** es un fichero YAML dentro de la carpeta ''.github/workflows/'' del repositorio. GitHub lee esa carpeta en cada ''push'': no hay que darlo de alta en ningún sitio. El fichero tiene cuatro partes:
* ''on'': cuándo se ejecuta. ''push'' con ''branches'' cuando se sube algo a esa rama; ''workflow_dispatch'' añade un botón //Run workflow// en la pestaña //Actions// para lanzarlo a mano.
* ''jobs'': los trabajos que hay que hacer.
* ''runs-on'': el **runner** en el que se ejecuta cada trabajo. Con ''ubuntu-24.04'', un Ubuntu que GitHub crea para la ocasión y tira al acabar; con ''self-hosted'', un runner propio ([[#runners|Runners]]). Siempre con la versión puesta, nunca ''ubuntu-latest'': cuando GitHub cambia la versión a la que apunta, los scripts que funcionaban pueden dejar de funcionar sin haber tocado nada.
* ''steps'': los pasos de cada trabajo, en orden. Cada paso tiene un ''name'' y hace una de dos cosas:
* ''run'': órdenes de shell, como en una terminal. Con ''|'' se ponen varias líneas.
* ''uses'': una acción ya hecha por otros, como ''actions/checkout@v7'', que descarga el código del repositorio en el runner.
Lo que no debe estar en el repositorio (contraseñas, claves SSH, IPs) se guarda como **secreto** en //Settings → Secrets and variables → Actions// y se usa en el workflow con ''%%${{ secrets.NOMBRE }}%%''. GitHub lo oculta en los logs.
El runner arranca **vacío**: no tiene nuestro código hasta que se hace ''actions/checkout'', y no tiene nada instalado aparte de lo que trae Ubuntu, así que lo que haga falta se instala en un paso.
Para compilar el proyecto Java y pasar sus tests cada vez que se sube algo a ''master'', se añade al repositorio:
name: Compilar
on:
workflow_dispatch:
push:
branches:
- master
jobs:
compilar:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- name: Instalar Java 21
uses: actions/setup-java@v6
with:
distribution: temurin
java-version: 21
- name: Compilar y pasar los tests
run: mvn -B package
* ''actions/setup-java'': el Ubuntu del runner no trae la versión de Java del proyecto; esta acción la instala.
* ''mvn -B package'': compila y pasa los tests. Si falla un test, el workflow sale en rojo. ''-B'' es el modo no interactivo, sin colores ni barras de progreso en el log.
Cada ejecución sale en la pestaña //Actions// del repositorio, con cada paso en verde o en rojo y su log. Si un paso falla, los siguientes no se ejecutan.
===== Runners =====
Un **runner** es el programa que ejecuta los jobs de un workflow: GitHub le pasa los pasos y él los va ejecutando y le devuelve el log. Dónde está el runner lo elige ''runs-on'', y hay dos tipos:
* **Runner de GitHub** (''runs-on: ubuntu-24.04''): una máquina virtual que GitHub crea para cada job y tira al acabar. Es el del workflow de arriba.
* **Runner propio** (''runs-on: self-hosted''): el mismo programa, pero instalado por nosotros en un servidor nuestro. Se queda siempre en marcha esperando trabajo.
^ ^ Runner de GitHub ^ Runner propio ^
| Dónde se ejecutan los pasos | En una máquina de GitHub | En nuestro servidor |
| Hay que instalarlo y mantenerlo | | ✓ |
| Empieza vacío en cada ejecución | ✓ | |
| Ve la red del servidor | | ✓ |
| Para desplegar necesita VPN, SSH y sus secretos | ✓ | |
Para compilar y pasar los tests, el runner de GitHub. **Para desplegar en un servidor nuestro, un runner propio en ese servidor**: los pasos se ejecutan allí mismo, así que el workflow se queda en lanzar ''deploy.sh''.
==== Con el runner de GitHub ====
El runner está en Internet y el servidor en el instituto, así que para desplegar tendría que entrar por VPN y SSH, con las claves guardadas como secretos:
{{:clase:daw:daw:runner-github.png?800|Con el runner de GitHub: crea el runner y entra en el servidor por VPN y SSH}}
La clave privada SSH para entrar en el servidor ([[clase:daw:proyecto:1eval:tema01#conexion_con_claves_publica_privada|Proyecto 1]]) se guarda como secreto ''SSH_KEY'', y el usuario y el servidor como ''SSH_USER'' y ''SSH_HOST''. El workflow entra por SSH y ejecuta ''deploy.sh'', que está en el //home// del usuario en el servidor:
name: Desplegar
on:
workflow_dispatch:
push:
branches:
- master
jobs:
desplegar:
runs-on: ubuntu-24.04
steps:
- name: Desplegar
run: |
echo "${{ secrets.SSH_KEY }}" > clave
chmod 600 clave
ssh -i clave -o StrictHostKeyChecking=accept-new ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/deploy.sh"
* ''echo'' y ''chmod'': ''ssh'' lee la clave de un fichero, así que se escribe el secreto en uno.
* ''StrictHostKeyChecking=accept-new'': el runner es nuevo y no conoce el servidor. Sin esto, ''ssh'' pregunta si se fía de él y el workflow se queda parado esperando la respuesta.
* ''"~/deploy.sh"'': la orden que se le pasa a ''ssh'' detrás del servidor se ejecuta en el servidor, no en el runner. Va entre comillas para que ''~'' sea el //home// del servidor y no el del runner.
Solo funciona si el servidor acepta SSH desde Internet. Si está detrás del router del instituto o de la empresa, antes hay que conectar la VPN en otro paso, con más secretos. O quizás ni se pueda si no hay una VPN
==== Con un runner propio ====
El runner está dentro del servidor. Es él quien **pregunta a GitHub** si hay trabajo, por HTTPS, como un navegador: no hace falta abrir ningún puerto ni VPN, y funciona aunque el servidor esté detrás del router del instituto.
{{:clase:daw:daw:runner-propio.png?800|Con un runner propio: el runner pregunta a GitHub por HTTPS y ejecuta el job en local}}
==== Instalar un runner propio en Linux ====
Un runner se registra en **un** repositorio y solo ejecuta sus workflows. Se instala en el servidor con el usuario que va a desplegar, no con ''root'':
- En el repositorio, //Settings → Actions → Runners → New self-hosted runner//, con //Linux// y //x64//. GitHub muestra las órdenes de los pasos siguientes: se copian de esa página, porque la de ''config.sh'' lleva el token.
- Descargarlo:
mkdir actions-runner && cd actions-runner
curl -O -L https://github.com/actions/runner/releases/download/v2.338.2/actions-runner-linux-x64-2.338.2.tar.gz
tar xzf ./actions-runner-linux-x64-2.338.2.tar.gz
- Registrarlo en el repositorio. ''--url'' es el repositorio y ''--token'' un **token de registro** que GitHub genera al abrir la página de //New self-hosted runner//: está en la orden ''config.sh'' del apartado //Configure// de esa página. Pregunta el grupo, el nombre y las etiquetas: con //Enter// se queda lo que propone.
./config.sh --url https://github.com/lgonzalezmislata/Facturas --token AXQKZ7MBV3LN2RCYEUHJ4PDTD6Y2W
- Dejarlo como servicio, para que siga en marcha al cerrar la sesión SSH y arranque solo al reiniciar el servidor:
sudo ./svc.sh install
sudo ./svc.sh start
sudo ./svc.sh status
- En //Settings → Actions → Runners// tiene que salir como //Idle//.
El token de registro **no es un token personal** de GitHub ni hay que crearlo: lo da esa página, solo sirve para registrar el runner una vez y **caduca en una hora**. Si ''config.sh'' dice que el token no vale, se recarga la página y se copia el nuevo.
==== Configurar un workflow para usar un runner ====
El workflow es el mismo que con el [[#con_el_runner_de_github|runner de GitHub]], pero sin SSH: el runner ya está en el servidor y ejecuta ''deploy.sh'' allí mismo.
name: Desplegar
on:
workflow_dispatch:
push:
branches:
- master
jobs:
desplegar:
runs-on: self-hosted
steps:
- name: Desplegar
run: ~/deploy.sh
Lo que hace que el job se ejecute en nuestro servidor es ''runs-on: self-hosted''. Los pasos se ejecutan **con el usuario que instaló el runner**
Más información:
* [[https://docs.github.com/en/actions/concepts/runners/self-hosted-runners|About self-hosted runners]]: qué son y requisitos.
* [[https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/configure-the-application|Configure the self-hosted runner application]]: ''svc.sh'' en Linux.
===== Ejercicios =====
==== Ejercicio 1 ====
Añade a tu repositorio de Java el workflow ''compilar.yml''. Comprueba en //Actions// que sale en verde; luego haz fallar un test, súbelo y comprueba que sale en rojo y en qué paso.
==== Ejercicio 2 ====
* Instala un runner propio para tu repositorio de Java en tu servidor como en [[#instalar_un_runner_propio_en_linux|Instalar un runner propio en Linux]], como servicio, y comprueba que sale //Idle//.
* Añade el workflow ''desplegar.yml'' adaptado a tu proyecto. Se ejecutará manualmente y cuando haya un cambio en ''master''. Sube un cambio y comprueba que llega desplegado al servidor.