gcm1001 / TFG-CeniehAriadne

CENIEH & Ariadne+ project.
GNU General Public License v3.0
3 stars 2 forks source link

Continuar con el análisis e implementación de la Integración Continua #40

Closed gcm1001 closed 4 years ago

gcm1001 commented 4 years ago
gcm1001 commented 4 years ago

Propuesta 01

Dado que existen varias formas de implementar la integración continua (CI) y el despliegue continuo (CD), voy a proponer el siguiente método.

Descripción

Aprovechando que estamos utilizando Docker para el despliegue de la aplicación, creo que es una buena idea implementar el CD haciendo uso de Web-Hooks.

¿Qué son los Web-Hooks?

Un WebHook es un método que permite, a través de llamadas HTTP, comunicar en tiempo real dos aplicaciones o servicios web. Para que esta comunicación se efectúe, debe acontecer un evento que desencadene la acción.

¿Cómo funcionan?

Su funcionamiento es bastante sencillo, por una parte tendríamos al proveedor de webhook, que es la aplicación o servicio web cuyo cometido es enviar una señal cuando acontece un evento determinado. Por otra parte tenemos al listener, que es quien se encarga de recibir los webhooks y ejecutar las acciones asociadas a cada uno de ellos.

webHooks

Implementación a través de Github Actions

Requisitos

Para llevar a cabo este método de despliegue continuo hay que cumplir con una serie de requisitos:

Paso 01: Puesta a punto del servidor.

Como hemos comentado anteriormente, necesitaremos, para el despliegue, tener instalado Docker en nuestro servidor.

sudo apt-get update
sudo apt-get install docker
sudo systemctl enable docker
sudo systemctl start docker

A continuación, debemos instalar la herramienta webhook para poder establecer la comunicación entre Github Actions y nuestro servidor.

sudo apt-get install webhook

Una vez instalado, necesitamos configurar el webhook que se encargará de atender la llamada asociada al despliegue. Para ello, en cualquier directorio del servidor, necesitamos crear un archivo al que llamaremos hooks.json.

[
  {
    "id": "accioncd",
    "execute-command": "/directorio/scripts/accioncd.sh",
    "command-working-directory": "/directorio/de/trabajo",
    "response-message": "Despliegue continuo."
  }
]

Los elementos configurados han sido:

Como podemos observar, en el campo execute-command he indicado un fichero denominado accioncd.sh. Por ello, la siguiente tarea será crear, en el directorio indicado, dicho fichero. Este debe contener todos los comandos relativos al despliegue de la aplicación.

#!/bin/bash
docker stack deploy -c docker-compose.yml omeka_cenieh
docker system prune -f

La secuencia de comandos es la siguiente:

  1. Actualizamos los contenedores de nuestra aplicación con todos los cambios realizados.
  2. Limpiamos el servidor eliminando todos los archivos "basura" que genera Docker en cada compilación.

Para que esta secuencia funcione perfectamente deberíamos tener configurados previamente los secrets indicados en el fichero docker-compose.yml. Además, como es lógico, tanto el fichero docker-compose.yml como el fichero secreto db.ini deben estar almacenados en el directorio donde se ejecutará la acción, es decir, la indicada en el campo command-working-directory del fichero hooks.json.

cd /directorio/de/trabajo
wget https://raw.githubusercontent.com/gcm1001/TFG-CeniehAriadne/master/docker-compose.yml
wget https://raw.githubusercontent.com/gcm1001/TFG-CeniehAriadne/master/db.ini.modificar
cp db.ini.modificar db.ini

Por último, activamos el endpoint.

webhook -hooks /directorio/del/hook/hooks.json -verbose

El siguiente paso es actualizar el workflow utilizado por Github Actions. Este proceso se explicará en el próximo comentario.

gcm1001 commented 4 years ago

Propuesta 01

Paso 02: Configuración del workflow.

Actualmente, nuestro workflow crea la imagen Docker asociada a la aplicación, la publica en DockerHub y "despliega" la infraestructura sobre el contenedor de Github Actions. Esta última acción es inútil ya que no se puede acceder a ninguno de los contenedores desplegados. Por ello, la primera modificación será eliminar esta parte.

  name: Docker CI/CD

  on:
    push:
      branches:
        - master
      paths-ignore:
        - 'docs/**'

  jobs:
      docker:
        name: Publicación - DockerHub
        runs-on: ubuntu-latest
        env:
          REPO: ${{ secrets.DOCKER_REPO }}      
        steps:
          - uses: actions/checkout@v1
          - name: Login
            run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
          - name: Build
            run: docker build -t omeka_cenieh .
          - name: Tags
            run: |
                docker tag omeka_cenieh $REPO:${{ github.sha }}
                docker tag omeka_cenieh $REPO:latest
          - name: Push
            run: |
                docker push $REPO:${{ github.sha }}
                docker push $REPO:latest

Como se puede observar, no solo he eliminado la parte de despliegue, también he añadido una variable de entorno, REPO, que almacenará el nombre del repositorio de nuestra imagen, oculto mediante un nuevo secret.

Para añadir la parte de despliegue, configuraré un nuevo job al que llamaré deploy. En su interior haré uso del WebHook Action, acción que nos permite llevar a cabo la comunicación con nuestro servidor.

...

    deploy:
      name: Despliegue - Llamada WebHook
      runs-on: ubuntu-latest
      needs: [docker]
      steps:
        - name: Despliegue Continuo
          uses: joelwmale/webhook-action@master
          env:
            WEBHOOK_URL: ${{ secrets.DEPLOY_WEBHOOK_URL  }}
            data: "Deploying from github actions!"

Los campos de configuración relevantes son:

Con este último paso, ya tendríamos configurado el workflow. A partir de este momento, cada cambio cometido sobre la rama master provocará la publicación de la imagen sobre DockerHub y el posterior despliegue de la aplicación, utilizando esta nueva imagen, sobre el servidor del CENIEH.

secuenciaDeEventos

Tareas relacionadas

En este momento lo ideal sería crear una nueva rama de desarrollo, develop, sobre la que pudiera realizar modificaciones sin que estas sean inmediatamente cometidas sobre el servidor.

A su vez, podría crear un nuevo workflow destinado a desplegar la infraestructura cada vez que cometiera una modificación sobre la rama de desarrollo, pero esta vez sobre el servidor de desarrollo, excluyendo por tanto al servidor en producción.

gcm1001 commented 4 years ago

Propuesta 02

Tras comentar la primera propuesta al administrador de sistemas del CENIEH, este me confirmó que no era viable ya que no iba a poder administrarme una IP para el servidor de desarrollo y, por lo tanto, no existía la posibilidad de realizar conexiones entrantes al servidor, imposibilitando cualquier tipo de conexión del servidor con el exterior.

Como solución a este problema, se me ha ocurrido una segunda propuesta, que consistirá en montar mi propio servidor de desarrollo en la nube, donde podré llevar a cabo los procesos propios de la integración continua y, además, será accesible al público.

Etapa 01: Montar el servidor en la nube

Para montar el servidor en la nube he utilizado la plataforma Google Kubernetes Engine (GKE) de Google Cloud.

Pasos ejecutados

  1. Crear un nuevo proyecto en Google Cloud.
  2. Habilitar los siguientes servicios: Container Registry y Kubernetes Engine APIs.Haz clic aquí para activarlos automáticamente
  3. Crear un nuevo clúster en Google Cloud. Haz clic aquí para activarlos automáticamente
  4. Crear una nueva cuenta de servicio. Tutorial seguido.
  5. Añadir, a la cuenta recién creada, los siguientes roles. Tutorial seguido
    • Kubernetes Engine Developer - nos permitirá desplegar aplicaciones en la plataforma GKE
    • Storage Admin - nos permitirá publicar contenedores Docker en la plataforma Container Registry
  6. Crear una clave para la cuenta creada en el paso 4. Tutorial seguido

Etapa 02: Configuración del workflow

Como hemos venido haciendo anteriormente, para implementar las técnicas CI/CD a través de Github Actions hay que configurar un workflow. En este caso, me he basado en la configuración por defecto que Github te propone al comenzar el proceso de configuración.

name: Build and Deploy to GKE

on:
  push:
    branches:
    - master
    paths-ignore:
    - 'docs/**'

env:
  PROJECT_ID: ${{ secrets.GKE_PROJECT }}
  GKE_CLUSTER: cluster-cenieh-project   
  GKE_ZONE: europe-west2-a
  IMAGE: gke-omeka

jobs:
  setup-build-publish-deploy:
    name: Setup, Build, Publish, and Deploy
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    # Setup gcloud CLI
    - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
      with:
        version: '290.0.1'
        service_account_key: ${{ secrets.GKE_SA_KEY }}
        project_id: ${{ secrets.GKE_PROJECT }}

    # Configure Docker to use the gcloud command-line tool as a credential
    # helper for authentication
    - run: |-
        gcloud --quiet auth configure-docker
    # Get the GKE credentials so we can deploy to the cluster
    - run: |-
        gcloud container clusters get-credentials "$GKE_CLUSTER" --zone "$GKE_ZONE"
    # Build the Docker image
    - name: Build
      run: |-
        docker build \
          --tag "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" \
          --build-arg GITHUB_SHA="$GITHUB_SHA" \
          --build-arg GITHUB_REF="$GITHUB_REF" \
          .
        docker build \
          --tag "gcr.io/$PROJECT_ID/$IMAGE:latest" \
          .
    # Push the Docker image to Google Container Registry
    - name: Publish
      run: |-
        docker push "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA"
        docker push "gcr.io/$PROJECT_ID/$IMAGE:latest"
    # Set up kustomize
    - name: Set up Kustomize
      run: |-
        curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64
        chmod u+x ./kustomize
    # Deploy the Docker images to the GKE cluster
    - name: Deploy
      run: |-
        ./kustomize build . | kubectl apply -f -
        kubectl rollout status deployment/$IMAGE
        kubectl get services -o wide

Los procesos que se llevan a cabo son los siguientes:

  1. Checkout: recogemos el contenido del repositorio.
  2. setup-gcloud: preparamos el entorno para tener acceso a todas las herramientas existentes en la plataforma Google Cloud.
  3. Configuramos el entorno de Docker.
  4. Obtenemos los credenciales necesarios para publicar la imagen Docker en nuestro repositorio privado de Google Cloud.
  5. Compilamos la imagen Docker.
  6. Publicamos la imagen Docker.
  7. Obtenemos la herramienta Kustomize, necesaria para administrar los ficheros .yaml.
  8. Compilamos los ficheros .yaml, actualizamos el servidor, y comprobamos que se han creado todos los servicios correspondientes.

Etapa 03: Configurar ficheros .yaml para Kustomize

Esta etapa ha sido la más complicada de todas ya que la sintaxis utilizada en este tipo de ficheros es distinta a la utilizada en los ficheros docker-compose.yml. Dado que nuestra infraestructura utiliza dos contenedores, uno para la base de datos y otro para la aplicación web, he tenido que crear los ficheros correspondientes a estas dos bases, MySQL y Omeka, junto al fichero kustomization.yaml para "unir" ambas bases. Además, para montar el volumen e indicar las variables de entorno de la base omeka, he creado un fichero adicional denominiado patch.yml.

Omeka

vars:

Los secretos utilizados por los ficheros yaml de la etapa anterior tienen que estar presentes en el servidor. Para crearlos, hay que ejecutar los siguientes comandos en el servidor:

Etapa final

La última etapa consistiría en ejecutar un commit sobre la rama master (siempre que el directorio afectado no sea /docs), comprobando así que se activa correctamente la acción recién creada y que finaliza de forma exitosa.

gcm1001 commented 4 years ago

En aproximadamente 48h ya se podrá acceder a la aplicación a través de este enlace.