Taberna WordPress Ansible Playbook

Continuamos explicando la creación de esta web, si te perdiste la primera parte de Creando Taberna WordPress desde cero: servidor deberías echarle un vistazo antes de continuar con esta lectura.

Aunque aún no he llegado a la parte de personalización del Tema (se nota, ¿no?), ni de plugins, para esta entrada he creído oportuno instalar un plugin para que muestre cómodamente el código fuente y mirando entre las posible opciones había algunos que destacaban más que otros, pero me he preguntado, ¿elijo por las puntuaciones y descargas?, no, la mejor opción, he ido a la mejor web de WordPress en español, la de mi amigo Fernando Tellado (Ayuda WordPress) y he mirado el que usaba, que es Crayon Syntax Highlighter, que además era una de las opciones principales que valoraba, así que dicho y hecho, plugin instalado (gracias Fernando)… hay alguno más instalado como Disqus Comment System pero ya los iremos viendo en su momento.

A lo que íbamos, que me distraigo y me voy por las ramas, para él aprovisionamiento del servidor he usado un Playbook de Ansible que utiliza algunos Roles que también he creado para este y otros usos, veamos el código del playbook que está disponible en Github:

Vamos a empezar por el final, si un poco raro, pero va a a ser más cómodo.

En la línea 109 en las tareas previas (pre_tasks) vemos que se instala python-minimal necesario para ejecutar las tareas, una de las características de las tareas de Ansible es que son  idempotentes (con algunas excepciones), es decir, que podemos ejecutar nuestro Playbook cien veces y solos ejecutará determinadas tareas si hay cambios, vamos que aunque la tarea sea instalar WordPress, esta tarea sólo se realizará si hay modificaciones, por lo que podemos realizar cambios a nuestro Playbook y solo se ejecutarán en el servidor dichos cambios. Uno de los comandos que precisamente no es idempotente es el de ejecutar comandos en crudo ( raw ), por lo que en nuestra pre_task registramos el resultado somo salida ( register: salida ) y sólo ejecutamos dicha tarea si su salida es distinta de vacío ( changed_when: salida.stdout != "" ), en cristiano, que si ya está instalado no hará nada y si no está pues lo instalará (comando en raw).

Continuamos con la linea 115, en donde se definen una serie de roles que vienen a ser un conjunto de tareas a realizar en el servidor remoto ( tasks ), manejadores ( handlers ) que se ejecutan en determinadas situaciones que le indicamos (si cambia el archivo de configuración de Nginx comprueba su sintaxis y si está correcta reinicia Nginx), plantillas ( templates ) que son archivos que copiaremos al servidor con una serie de variables a cambiar y condiciones según lo que definamos en nuestro playbook (usa jinja2) y unos valores de la variables asignados por defecto ( defaults ). Hay más opciones en los roles, pero con estas nos llegan, además vamos a sumarle unos archivos encriptados con clave (Vault), que aunque los subamos a nuestro repositorio con nuestros secretos no serán visibles a los usuarios, por ejemplo los parámetros de la Base de Datos, por ejemplo en este Playbook https://github.com/CarlosLongarela/ansible-playbook-wp-tabernawp/blob/master/host_vars/tabernawp/vault. Para encriptar, modificar y ver archivos Vault tenemos el comando ansible-vault  con los parámetros create, edit, view, encrypt, decrypt  y rekey , y en el archivo encriptado para no complicarnos recreamos la misma variable con vault_  quedando por ejemplo nuestro usuario de BD en el archivo encriptado como vault_mariadb_users_1_name: "mi-usuario"  y en nuestro archivo de variables que puede “ver todo el mundo” en el repositorio quedaría mariadb_users.user1.name: "{{ vault_mariadb_users_1_name }}" 

Si no tenéis experiencia con Ansible al principio puede ser un poco lío, pero con práctica es muy cómodo y bastante “human readable”, eso si, los experimentos con gaseosa, es decir en un servidor en local con VirtualBox o similar, que os podéis quedar si acceso al server o cargaros todo…

Seguimos, ya vemos que hay una serie de roles definidos, pero no están ahí (¿?), eso es debido a que trato cada rol como un repositorio independiente, pudiendo mejorar cierto aspecto del Playbook sin tocar al resto, son grupos de tareas que podemos usar en varios Playbooks y para varias circunstancias, y además Ansible tiene un repositorio público de roles, que en realidad solo guarda una serie de definiciones y después el código estará en Github, BitBucket, etc. Dicho repositorio es Ansible Galaxy y ahí es donde tengo los roles, bueno, su definición, después cada uno esta en su repositorio de Github. Por ejemplo el rol para instalar y configurar WordPress está en https://galaxy.ansible.com/CarlosLongarela/wordpress/ y el repositorio (enlazado desde Galaxy) está en https://github.com/CarlosLongarela/ansible-role-wordpress y si no lo tenemos en nuestro ordenador con poner en el rol - CarlosLongarela.wordpress  el propio Playbook se ocupa de descargarlo y dejárnoslo disponible para su uso,

  • ¿complicado?
  • No, queremos, más, mucho más.
  • Ok.

Bueno, pues entonces continuamos. Después de los siete roles definidos, también está el de phpmyadmin que creé para otro proyecto pero está comentado ya que no lo uso aquí, me conecto a la BD mediante túnel SSH, lo dicho, después de los roles que ya nos habrán configurado completamente nuestro servidor tenemos las tareas a realizar antes de finalizar totalmente nuestro aprovisionamiento ( post_tasks ) y que en esta ocasión sólo se trata de actualizar paquetes del sistema. Esta tarea ya es idempotente por lo que solo se ejecuta si es necesario actualizar algo, pero os habréis fijado que tiene una condición para ejecutarse when: update_packages  y eso le indica que si cambiamos el valor de la variable update_packages: true  a false  no realizará la actualización de paquetes del sistema.

Y con esto ya hemos visto una variable, concretamente la de la línea 107 y como íbamos hacia arriba, hasta la línea 9 son todo variables para sobreescribir los valores por defecto que teníamos en los roles, ¿os acordáis de defaults ?. La variables en Ansible, que usa YAML para las definiciones son diccionarios y listas, ¿qué es eso?, pues diccionarios y listas… palabra definición (diccionario), una serie de palabras (listas), que no me maten los puristas que es un churro de definición pero no quiero liar mucho la cosa… eso si, no va a ser todo tan fácil, podemos anidar listas y diccionarios y listas y… podéis mirar un poco las variables definidas aquí, así como las defaults de los roles para ir comprendiendo un poco lo que son y para profundizar un poco más buscar en un sitio serio ¿aún seguís aquí?

Seguimos subiendo y para lo poco que falta nos vamos ya al inicio, total por poco más de cuatro líneas. La primera línea ---  indica que comienza el archivo YAML, aunque la podemos omitir, en la siguiente un ejemplo de como ejecutarlo, todo lo que comience por #  son comentarios en YAML ya sea al inicio de una línea o en medio. En la linea 3 - hosts: tabernawp  tenemos la primera lista (las listas comienzan con guión)… con un diccionario, al elemento de la lista hosts le definimos un significado de tabernawp, es decir, le indicamos al Playbook que estas tareas se ejecutarán sobre tabernawp (archivo que está en el raíz del Playbook), que puede definir un servidor como en este caso o 10.000, si, desde Ansible podemos preparar 10.000 servidores o más y con diferentes configuraciones dependiendo de en que grupo estén, etc., pero eso queda para una clase del curso que viene. Continuamos, también en el raíz tenemos host_vars  donde están las variables de los hosts, para eso lo dice su nombre, ¿no?, dentro de host_vars  tenemos los diferentes hosts, en este caso la carpeta tabernawp que contiene las variables del host vars, pero para no ponerlas visibles sus valores están encriptados en el archivo vault.

Líneas 5 y 6 gather_facts: no  y become: true  lo primero es que no recoja información de la máquina como nombre, memoria, procesadores, sistema operativo, etc, con lo que se puede hacer maravillas como diferentes valores según la capacidad del servidor, diferentes comandos según si es Ubuntu, CentOS, etc, pero yo he realizado este PlayBook muy específico, ara un Ubuntu Xenial, pero además no podemos recoger los facts mientras no tengamos python (¿recordáis las pre_tasks ?), hay diversas soluciones para esto, pero como aquí no uso estas variables del server, no las recojo. La linea 6 es decirle que ejecute las tareas con privilegios (sudo).

¿Cansados?, bueno, quedaría mucho que contar aún, configuraciones de Ansible en ansible.cfg con comandos muy interesantes como el vault_password_file  y el ansible_managed  que usamos en las plantillas para decirles a los usuarios, “eh, ahí no toques, que esto lo maneja Ansible y te a a pisar lo que modifiques”, pero os dejo que le echéis un vistazo al playbook y a los roles si os interesan y ante las dudas preguntad que para eso puse el Disqus y en breve integraré el Gitter como comentaba en la entrada anterior y también sois libres de preguntar ahí, ponerme verde porque no funciona, está mal hecho, peor explicado o lo que queráis.

¿qué aún podéis aguantar un poco más de rollo?, bueno, roles, qué hace cada uno esquemáticamente, si queréis saber más Github, Gitter y comentarios:

Apt

Instala todos los programas que necesitemos en nuestro servidor para la administración y que definimos en la lista aptget_install , en nuestro caso están definidos en las líneas 12 a 20 y son:

  • aptitude
  • mc
  • nano
  • sysv-rc-conf
  • python-pip
  • python-dev
  • libmysqlclient-dev
  • lynx
  • curl

Ntp

Fija la zona horaria correcta en /usr/share/zoneinfo/  y /etc/timezone 

Se asegura que NTP está instalado y se comprueba que se esté ejecutando y activado (mediante un handler) y copiamos la plantilla con nuestros valores con la configuración de ntp a /etc/ntp.conf 

MariaDB

Añadimos el repositorio y su key para MariaDBB 10.1.22, instalamos el módulo Python MySQLB y MariaDB.

Iniciamos y activamos el servicio, se copia ~/.my.cnf  para root, se instala el archivo de configuración de red y se actualiza la clave de root.

Eliminamos la Base de datos test y usuarios anónimos. Creamos las Bases de Datos de la lista mariadb_databases  y los usuarios del diccionario mariadb_users .

Se crea el directorio de utilidades, se instala MySQLTuner, un script de optimización de BD y otro de backups de BD.

Creamos el directorio de Backups de la BD y añadimos la tarea cron del BackUp de BD y la tarea cron de Optimización de BD.

Todo esto según las variables fijadas en el Playbook (y notificamos a la BD para que se reinicie), por ejemplo podemos no instalar los scripts de optimización con mariadb_utiles_bd: false .

Si tenemos algún problema de conexión con la Base de Datos podemos aplicar la solución de http://askubuntu.com/questions/705458/ubuntu-15-10-mysql-error-1524-unix-socket es decir:

PHP7

Añadir repositorio de PHP 7.1, instalamos PHP 7.1-fpm (y cli) y requisitos junto con los módulos indicados en la lista php_modules .

Copiamos la plantilla de configuración de php.ini y la PHP-fpm. Borrado del pool www por defecto de PHP-fpm.

Copiamos la plantilla de configuración de pool de PHP-fpm y nos aseguramos que PHP-fpm está iniciado y activado al reiniciar.

Si tenemos problemas con la instalación de PHP del tipo “dpkg: error: dpkg status database is locked by another process” aplicamos la solución de https://askubuntu.com/questions/219545/dpkg-error-dpkg-status-database-is-locked-by-another-process

Certbot (certificados de Let’s Encrypt)

Nos aseguramos que Open SSL está instalado y creamos dhparam.pem  para Nginx SSL ya que queremos tener un certificado SSL A+ (iremos mejorando el certificado poco a poco).

Instalamos repositorio oficial de Certbot y a continuación Certbot (Let’s Encrypt) y añadimos la tarea cron de comprobación de certificados.

Nginx

Añadimos el repositorio oficial de Nginx ppa:nginx/stable  para poder usar la versión con fastcgi_cache_purge .

Instalamos Nginx (versión fastcgi_cache_purge ), eliminamos los snippets por defecto, desactivamos el sitio web por defecto, nos aseguramos que Nginx está iniciado y activado al reiniciar.

Instalamos el archivo de configuración general de Nginx y el de configuración de seguridad de Nginx. Creamos los Virtual Hosts definidos, los enlaces simbólicos para Virtual Hosts activados.

Comprobamos que exista el raíz del sitio web y el directorio .well-known  necesario para Let’s Encrypt. Comprobar que exista el directorio de logs.

Comprobamos la sintaxis de Nginx y si es correcta reiniciamos.

Esta configuración nos permitirá realizar caché de archivos PHP desde Nginx en RAM, algo que no nos permite ningún plugin de WordPress ya que deben ejecutar PHP y en este caso si existe copia del archivo desde Nginx este lo sirve sin realizar petición alguna a PHP, me apunto otro artículo pendiente sobre esto. Para poder borra la caché cada vez que creamos un artículo, lo modificamos, etc., así como algunas otras opciones que si el tiempo lo permite ya comentaremos, usamos Nginx Helper, otro de los por ahora pocos plugins instalados en esta web.

WordPress

Instalamos WP-CLI y  los plugins de WP-CLI definidos en wp_cli_packages , comprobamos si existe el archivo de WordPress readme.html y lo registramos en wp_readme  para uso posterior.

Descargamos WordPress con WP-CLI en nuestro idioma y a la ruta indicadas. Lo configuramos con la base de datos, usuario, clave, host y prefijo de tablas que le hayamos indicado en las variables.

Instalamos WordPress con la url, título, usuario admin, clave, password y email que le indiquemos en las variables  wp_cli_wp_*  pero solo si existe el archivo readme.txt que registramos anteriormente.

Fijamos los permisos de las carpetas (755) y archivos (644) con el usuario y grupo bajo el que se ejecuta Nginx.

Eliminamos los archivos licencia.txtlicense.txtreadme.html, este último es el que decidirá si ya está instalado WordPress si no está en el raíz del sitio web.

Nota sobre este Playbook y Vault

Los que tengáis experiencia con Ansible os sobrará esto y seguramente también el resto de esta pesada entrada, para los que os suene un poco, podéis ejecutar este Playbook para crear vuestro propio servidor, ya os lo dije antes, pruebas en Virtual Box o un server destinado a… pruebas. Si lo ejecutáis tal cual os fallará porque diversos valores están en el archivo Vault el cual está encriptado y la clave está en un archivo .vault_pass  que no se sube al repo al estar en el .gitignore  por lo que vosotros o ponéis directamente los valores en las variables o elimináis Vault y creáis el vuestro con los valores que necesitéis.

Para editar los archivos encriptados de ansible-vault con nuestro editor favorito, deberemos editar el archivo de configuración ~/.bashrc  y poner el editor en la variable $EDITOR :

y volver a cargar el archivo .bashrc:

y vemos si ya está asignado el editor con:

También debemos hacerlo para root /root/.bashrc  para cuando usamos sudo.

Si usamos archivos encriptados con ansible-vault  y controlamos nuestro repositorio con Git en Windows, es posible que debido a la conversión de final de línea CRLF en vez de LF, Git realice la conversión y nuestro archivo encriptado no se pueda leer dándonos un error como “Odd-length string” o similar.

Para solucionarlo tenemos que decirle a Git que nuestro archivos tienen un final de línea LF y podemos hacerlo creando en el raíz de nuestro repositorio un archivo .gitattributes (como el que figura en este repositorio) en el que ponemos:

y después para volver a ejecutar la conversión ejecutamos:

Bueno, queda mucho por contar, pero también falta mucho tiempo para escribir, así que aquí queda por esta entrada. Si queréis profundizar en Ansible os recomiendo el libro Ansible for DevOps uno de los mejores que he leído, y por cierto, el enlace del libro en Amazon es un enlace de afiliados y como no voy a inventar la rueda ni reescribir lo que ya puso Fernando y suscribo al 100%, aquí os dejo para que lo leáis: https://ayudawp.com/los-enlaces-afiliado-la-madre-los-pario/

Ansible for DevOps

Seguramente la siguiente entrada no sea tanto rollo como esta y quizás se hable más de WordPress, quizás la escriba mañana o en un par de semanas, quien sabe, lo que el tiempo permita, The answer my friend is blowing in the wind.