CI/CD | Automatiza tus despliegues con GitLab y Ansible

Gerardo Ocampos
12 min readJul 11, 2020

--

En el siguiente apartado te enseñaré a como configurar una tubería de despliegue simple para mantener tus cambios seguros y automatizados en todo momentos. Utilizaremos el servidor de integración continua GitLab CI/CD para orquestar la tubería de despliegues; un sistema de control de versiones Git para controlar los cambios en nuestro repositorio y finalmente mantener la infraestructura y configuraciones de nuestros despliegues con Ansible.

La estrategia se basa de la siguiente manera: Una tubería de despliegue automatizado para mantener el Front-end de un servidor web, el código fuente se encuentra publicado en el Git, cada cambio realizado en las diferentes ramas -Dev/QA/Prod- desatara la tubería configurada en el gitlab-ci.yml, el cual se encargara de orquestar el cambio y llamar al Playbook de Ansible para aplicarlo dependiendo del ambiente. Ansible por su parte se encargará de realizar el Rolling Upgrade en los servidores, de tal manera a que el cambio efectuado sea controlado y simplificado en cada servidor.

El ambiente de trabajo estará conformado de la siguiente manera:

CI/CD Workstation:
* Proyecto en GIT: https://github.com/AndrewOcamps/cicd-webservice-example
* Servidor de Integración: gitlab.example.lab (192.168.33.20)
* Servidor Ansible (gitlab-runner): runner01.example.lab
Servicio Web:
* DEV: devweb.example.lab (192.168.33.21)
* QA: qaweb.example.lab (192.168.33.22)
* PROD: web1.example.lab (192.168.33.23)

Nota: Los servidores utilizados poseen Sistema Operativo Centos 7.5; en mi caso utilizaré vagrant+virtualbox para desplegar el entorno de trabajo, pueden utilizar la plantilla personalizada de Vagrantfile para este laboratorio, el cual se encuentra publicado en mi cuenta de GitHub en el apartado “infrastructure > Vagrantfile”.

Paso 1: Crear y configurar las ramas en el proyecto en GitLab

Dentro de la interfaz web de gitlab, crearemos un proyecto llamado “CICD WebService”

En un servidor con el cliente git instalado, descargaremos el proyecto y crearemos nuestro primer commit a la rama master, con el comentario de “DevOps”

Nota: En mi caso utilizare un usuario llamado devops, si utiliza el usuario administrador “root” de GitLab, cambiar la url a http://gitlab.example.lab/root/cicd-webservice.git

$ git clone http://gitlab.example.lab/devops/cicd-webservice.git
Cloning into 'cicd-webservice'...

Username for 'http://gitlab.example.lab': devops
Password for 'http://devops@gitlab.example.lab': *******
warning: You appear to have cloned an empty repository.## Ingresamos dentro del repositorio descargado
$ cd cicd-webservice/
## Agregamos un archivo para nuestro primer commit
$ echo "CI CD WebService" > init.txt
$ git add init.txt
## Realizamos el commit con el comentario "DevOps"
$ git commit -m "DevOps"
## Subimos nuestro cambio a la rama master
$ git push origin master
Username for 'http://gitlab.example.lab': devops
Password for 'http://devops@gitlab.example.lab': ******
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 232 bytes | 232.00 KiB/s, done.Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To http://gitlab.example.lab/devops/cicd-webservice.git
* [new branch] master -> master

Volvemos a nuestra interfaz de proyecto y veremos reflejado el archivo agregado.

Ahora debemos de crear las ramas a utilizar para nuestra tubería de despliegue. Mediante la interfaz web de GitLab, agregamos una nueva rama dando clic al icono “+”, e indicaremos que el nombre de la rama será “dev”.

Seguido, procederemos a crear la rama “qa”, partiendo también de la rama master.

Finalmente tendremos de la siguiente forma; un proyecto con 3 ramas principales:

* dev
* qa
* master (Utilizaremos esta rama como producción)

Paso 2: Configuración del gitlab-runner como instancia de Ansible

Lo siguiente a realizar, será la instalación del gitlab-runner, utilizaremos “shell” como gitlab-executer para poder lanzar comandos de ansible dentro de la terminal

  • Ingresamos al proyecto “CICD WebService”
  • Nos dirigimos al apartado de “Settings > CI / CD > Runners”
  • Desplazamos el curso hasta la sección de “Set up a specific Runner manyally” para obtener la url y el token del proyecto

Dentro del servidor runner01.example.lab registraremos el runner con la url y el token que nos proporciona el proyecto.

gitlab-runner register \
--non-interactive \
--url "http://gitlab.example.lab/" \
--registration-token "VmkSNiNvyymxH-3ccWhx" \
--executor "shell" \
--tag-list "ansible"

Donde:

  • register: es el argumento para registrar el runner
  • - -non-interactive: indicamos que estaremos pasando las opciones y los argumentos en una simple linea
  • - -url: indicamos la url que obtuvimos al inicio
  • - -registration-token: indicamos el token correspondiente al proyecto
  • - -executor: establecemos el tipo de ejecución, en nuestro caso shell para lanzar comandos en la terminal
  • - -tag-list: la lista de tag asociadas al runner, mediante la cual llamaremos al runner de las tareas

Nota: Para utilizar el comando mencionado, se debe de instalar el siguiente repositorio de la página oficial de GitLab

$ curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash$ yum -y install gitlab-runner

Volviendo al repositorio en la sección de runner, encontraremos nuestro servidor registrado.

Pase 3: Configuración de código fuente

Para nuestro ejemplo, desplegaremos una página simple que está compuesta por un conjunto de librerías y un index.html el cual será visualizado en el navegador, el mismo estará desplegada en los 3 ambientes mediante ansible, quien se encargara de realizar el setup de toda la infraestructura. El código fuente lo pueden descargar del siguiente repositorio.

Nota: en la sección code, puede encontrar la estructura de archivos configuradas a continuación

  • Iniciamos con la configuración de la página, nos dirigimos al proyecto, y mediante la herramienta Web IDE de GitLab creamos el archivo index.html en la rama dev con el siguiente contenido:
### index.html:<!doctype html>
<html>
<head>
<title>This is the title of the webpage!</title>
</head>
<body>
<p> CI CD DevOps </p>
</body>
</html>
La estación de trabajo actual es la rama dev

Nota: Tener en cuenta siempre la rama en donde se está realizado el cambio. Este cambio debe ser realizado en dev para iniciar el CICD hasta llegar a master

  • Lo siguiente que configuraremos será el archivo “ansible.cfg” en donde le indicaremos las configuraciones para este proyecto
### ansible.cfg:[defaults]
inventory = hosts
host_key_checking = False
remote_user = vagrant
private_key_file = vagrant.key

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false

Donde:

* "inventory = hosts" Nombre de  nuestro inventario* "host_cheking_false" Apartado para evitar que * "remote_user = vagrant" Usuario con permiso sudo all para realizar cambios en diferentes servidores* "private_key_file = vagrant.key" Llave privada para conexiones sin contraseña con el usuario indicado* "become = True" Indicamos a ansible que todo lo realice con sudo* "become_method = sudo" Modo de conexión para escalar privilegios* "become_user = root" Usuario administrador para utilizar sudo* "become_ask_pass = false" Como tenemos configurado el usuario vagrant como 'NOPASSWD' en las políticas de sudoers (/etc/sudoers.d/sudo_runner), no necesitamos introducir contraseñas para utilizar sudo
  • Seguido del archivo hosts donde estará la lista de los servidores clasificados por entorno
### hosts[dev]
192.168.33.21
[qa]
192.168.33.22
[prod]
192.168.33.23
  • Luego declararemos el playbook donde se indicaran las tareas a realizar en cada servidor
### playbook.yml- name: Configuración de un servidor web
hosts: all
tasks:
- name: Instalación de dependencías
yum:
name: ['httpd', 'firewalld']
state: latest

- name: copiar index.html
copy:
src: index.html
dest: /var/www/html/
mode: 0755

- name: Iniciar servicio de firewalld
service:
name: firewalld
state: started
enabled: yes
when:
- inventory_hostname in groups['qa'] or inventory_hostname in groups['prod']

- name: Configurar servicio http en el firewall
firewalld:
service: http
permanent: yes
state: enabled
immediate: yes
when:
- inventory_hostname in groups['qa'] or inventory_hostname in groups['prod']

- name: Iniciar servicio de apache
service:
name: httpd
state: started
enabled: yes

Donde:

1- “Instalación de dependencias”: Indicamos a ansible que debe de instalar la última versión de los componentes listados

2- “copiar index.html”: Indicamos que copie el archivo principal al document root de cada servidor

3- “Iniciar servicio de firewalld”: Procederá a iniciar el servicio de firewalld solo cuando el servidor indicado sea de qa o de prod. Se omite para servidores en el entorno dev

4- “Configurar servicio http en el firewall”: Como activamos el firewalld, debemos indicar una regla para las peticiones al puerto 80 o servicio de apache. Esto solo afectara a servidores de qa o prod. Se omite para servidores en el entorno dev

5- “Iniciar servicio de apache”: AL finalizar, asegurarse que el servicio httpd se encuentre corriendo en los servidores indicados

  • Por último subiremos nuestra llave privada (vagrant.key) al repositorio, el cual se utilizara para que el usuario que ejecuta los runner pueda conectarse a los demás servidores y realizar el escalado de privilegios (sudo) con el usuario vagrant

Nota: la llave privada mencionada lo pueden encontrar en el directorio code del proyecto publicado

Debería quedar de la siguiente forma:

  • Finalizamos la configuración con el commit a la rama dev, como aún no tenemos el archivo gitlab-ci.yml el “continuos delivery” no iniciara.
  • Tener en cuenta que “Start a new merge request” no debe estar seleccionado

Paso 4: Configuración de gitlab-ci.yml y despliegue continuo en las diferentes ramas

El archivo gitlab-ci.yml es un archivo de configuración YAML que debes crear en la raíz de tu proyecto. Si este archivo está presente se activará la integración continua en Gitlab. Es decir, cada vez que subas un commit al repositorio, Gitlab se encargará de revisar que este archivo se encuentre y, de ser así, ejecutará en Gitlab Runner el pipeline (grupos de trabajos) definidos en él.

Ref: https://docs.gitlab.com/ee/ci/yaml/

Por nuestra parte, utilizando el asistente de GitLab Web IDE, y agregaremos un nuevo archivo con el nombre de .gitlab-ci.yml. establecemos la siguiente configuración.

### .gitlab-ci.yml:variables:
GIT_STRATEGY: clone

stages:
- configure
- check
- deploy

webservice-configure:
stage: configure
tags:
- ansible
script:
- "whoami"
- "sudo yum install -y epel-release"
- "sudo yum install ansible -y"

webservice-check:
stage: check
tags:
- ansible
script:
- "whoami"
- "ansible --version"
- "sudo chmod 600 vagrant.key"
- "ansible -m ping all"

webservice-deploy-dev:
stage: deploy
tags:
- ansible
script:
- "sudo chmod 600 vagrant.key"
- "ansible-playbook playbook.yml --limit=dev"
only:
- dev

webservice-deploy-qa:
stage: deploy
tags:
- ansible
script:
- "sudo chmod 600 vagrant.key"
- "ansible-playbook playbook.yml --limit=qa"
only:
- qa

webservice-deploy-prod:
stage: deploy
tags:
- ansible
script:
- "sudo chmod 600 vagrant.key"
- "ansible-playbook playbook.yml --limit=prod"
only:
- master

Quedando de la siguiente forma:

1- stages: Definimos las etapas que cumplira nuestro ci/cd

  • configure: Instalaremos ansible en el nodo de gitlab-runner
  • check: Validaremos todas las dependencias y las conexiones con los servidores a interactuar
  • deploy: etapa final donde lanzaremos el playbook de ansible dependiendo del entorno

2- tags: indicaremos el nodo de gitlab-runner a utilizar, el cual configuramos en la sección anterior

3- script: Como nuestro runner lo configuramos como un executor tipo shell, podemos lanzar comandos de bash internos que nos ayudara a realizar las configuraciones necesarias

4- only: Lo utilizaremos para indicar que cada tarea solo se realizara en la rama mencionada (dev,qa,master)

Nota: Utilice la variable “GIT_STRATEGY: clone” para indicar que cada vez que interactua con nuestro proyecto, realice un git clone para tener actualizado el repositorio.

Una vez finalizado la creación y la configuración del archivo mencionado, procedemos a realizar un nuevo commit a la rama dev, indicamos un comentario del cambio, desmarcamos la casilla “Start a new merge request” y damos clic en commit

Al realizar esta acción, gitlab automáticamente inicia la tubería de despliegue basándose en la configuración realizada en el archivo gitlab-ci.yml

Esto se puede visualizar en la interfaz de Pipelines

Como se observa iniciará cada úna de las etapas que corresponda a la rama dev, y omitirá en este caso las configuraciones realizadas para las ramas “qa” o “master”

Finalizado el Pipeline, podemos acceder a la consola y mira los resultados de cada una de las etapas

Pipeline en la rama dev finalizado
Job: configure “webservice-configure” finalizado con exito
Job: check“webservice-check” finalizado con exito
Job: deploy “webservice-deploy-dev” finalizado con exito
Se observa la página desplegada en el ambiente dev http://192.168.33.21

Nuestro siguiente paso será realizar un merge request a la rama “qa”, para que todo el cambio funcional que configuramos en la rama “dev”, se pase a este nuevo entorno mencionado

  • Dentro del proyecto, encontrarás la opción “Create merge request” damos clic y continuaremos a la siguiente interfaz
  • Una vez adentro, debemos indicar que realizaremos un merge de la rama dev a qa. Gitlab nos permite realiza un comentario y asignar un aprobado de esta solicitud
Tener en cuenta que debe estar indicado la rama correspondiente en la sección “From <dev> into <qa>”.

1- GitLab te informará que el último Pipeline realizado en la rama dev fue exitoso y te habilitará el botón de merge.

2- Dando clic al botón mencionado, iniciará un nuevo Pipeline en la rama qa con el último commit realizado en la rama dev

Una vez finalizado, podemos observar que todos los cambios fueron aplicados y desplegados en el nuevo entorno

Se observa la página desplegada en el ambiente de qa http://192.168.33.22

Cada cambio realizado en la rama “qa” desatará un nuevo llamada a la tubería de despliegues, pasando por todas las etapas y asegurando que todo este como debe ser en el ambiente seleccionado.

Ansible nos permite asegurar que la infraestructura permanecerá con las indicaciones mencionadas en el playbook, utilizando este componente para manejar las configuraciones de despliegue nos evita programar scripts de bash complejos en las etapas de gitlab-ci.

Terminado el despliegue de dev y qa, solo nos queda finalizar con un merge request en la rama principal, para tener nuestro producto listo en producción.

Volvemos a la interfaz principal de nuestro proyecto y solicitamos un nuevo merge. De la misma forma como lo realizamos en la rama de qa.

Indicamos en este caso que se realizara un merge a la rama master

GitLab volverá a indicarte todos las tuberías realizadas en la rama anterior y te habilitará el botón de merge para continuar. Esto desatará la tubería final en la rama master.

En la interfaz de Pipelines podrás observar todo el camino y los cambios realizados en cada rama.

En la sección de “Graph”, podrás observar el estado de tus ramas y los cambios que se realizó hasta llegar a producción (master)

Se observa la página desplegada en el ambiente de producción http://192.168.33.23

Conclusión

Desarrollar usando integración continua y despliegue continuo en nuestros proyectos es realmente beneficioso, no solo para los desarrolladores, sino también para los sysadmin, donde pueden aprovechar y mantener su infraestructura como código en todo momento, la ventaja principal es que no necesitarías dedicarle mucho tiempo a probar el código cada vez que integras una nueva funcionalidad. Este tiempo se lo puedes dedicar a escribir más pruebas y cambios en funcionabilidades y Gitlab se encargaría de ejecutarlas. Además es bastante útil cuando se trabaja en equipos agiles donde sus integrantes pueden no estar al tanto de todos los cambios y características del proyecto, pero las pruebas sí.

--

--

Gerardo Ocampos
Gerardo Ocampos

Written by Gerardo Ocampos

Red Hat Certified Engineer — Red Hat Certified System Administrator — Red Hat Certified Specialist in Containers and Kubernetes. Asunción, Paraguay

Responses (2)