Siempre he utilizado Wodpress como una especie de bloc de notas menos un breve tiempo que me moví a Evernote. Tiempo después, quise recuperar el blog. Por entonces ya tenía curiosidad de probar algo en el cloud y el blog era una forma útil de hacerlo. Utilizaba Linode por entonces.
Si estáis interesados en saber como podéis desplegar un WordPress en AWS quizá este post os ayude en algo.
En este post hablaré sobre todo de la infraestructura, más que del propio WordPress en si mismo.
Antes de AWS
Como he dicho, utilizaba Linode. Linode es un cloud bastante económico. Tener un servidor virtual son pocos euros al mes. Yo tenía allí alojado mi código de WordPress. La configuración era toda manual. Intenté hacer algo con Ansible pero la falta de tiempo para implementarlo junto con que era algo personal, me hizo desistir. Creo recordar que la base de datos corría en el mismo servidor. Algo típico vaya. Las actualizaciones de WordPress obviamente no estaban para nada versionadas ni remotamente. No tenía tampoco monitorización salvo el UptimeRobot.
La necesidad apremia y dado que quería o necesitaba, adquirir conocimientos en el cloud más importante (por encima de Azure o Google Cloud) tomé la decisión de moverlo todo a AWS.
Pila de herramientas
Antes de mencionar las utilidades me gustaría recalcar que con esta implementación yo asumo que
- no busco ni pretendo alta disponibilidad
- intento aplicar buenas prácticas siempre que sea posible
- no es una solución cerrada, está sujeta a cambios posteriores
Ahora, las utilidades:
- Terraform (o CloudFormation o similares)
- Github o Bitbucket o CodeCommit
- IntelliJ IDEA o similares
- Monitorización Externa (UptimeRobot)
- AWS Cloud
- ElasticBeanstalk LAMP (Linux Apache MySQL Php)
- Codepipeline
- S3
- CloudFront
- Route53
- CloudWatch
- Lambda
A grandes rasgos estas serían las herramientas que vamos a necesitar.
Versionado del código
Vamos a necesitar algún sitio donde subir los cambios el código y la infraestructura. En mi caso utilicé GitHub, que cuenta con integración con CodePipeline.
He montado dos repositorios, una para el código del propio WordPress y otro para la infraestructura. Realmente no me gusta que esté separado y pienso combinarlo en solo uno cuando pueda.
Utilizo master flow. Pero como no tengo sistema de Jira para mi mismo (¬¬) no creo ramas por feature. Directamente hago push a la rama “develop” y de ahí se activa la pipeline. Lo explicaré más a fondo más tarde.
Terraform
Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.
Configuration files describe to Terraform the components needed to run a single application or your entire datacenter. Terraform generates an execution plan describing what it will do to reach the desired state, and then executes it to build the described infrastructure. As the configuration changes, Terraform is able to determine what changed and create incremental execution plans which can be applied.
Terraform es una gran herramienta para poder tu infraestructura como código (IaaC). Tendremos toda nuestra infraestructura (o casi) versionada en un repositorio. Via Terraform podremos añadir, eliminar o modificar nuestra infraestructura añadiendo el código y aplicando “terraform apply”.
No voy a entrar mucho en detalle sobre como funciona Terraform porque hay muchos y mejores posts sobre esto, pero aquí un pequeño ejemplo.
# main.tf
terraform {
backend "s3" {
encrypt = true
bucket = "my-bucket"
region = "eu-west-3"
key = "terraform/prod/terraform.tfstate"
}
}
provider "aws" {
region = "${var.aws_region}"
shared_credentials_file = "${var.aws_credentials}"
profile = "${var.aws_profile}"
}
module "vpc" {
source = "../../modules/vpc"
version = "1.37.0"
name = "${var.vpc_name}"
cidr = "${var.vpc_cidr}"
azs = "${var.vpc_azs}"
private_subnets = "${var.vpc_private_subnets}"
public_subnets = "${var.vpc_public_subnets}"
enable_nat_gateway = "${var.vpc_nat_gateway}"
single_nat_gateway = "${var.vpc_single_nat_gateway}"
enable_vpn_gateway = "${var.vpc_enable_vpn_gateway}"
tags = {
Terraform = "${var.general.["tf"]}"
Environment = "${var.general.["env"]}"
Project = "${var.vpc_name}"
}
vpc_tags = {
Name = "${var.vpc_name}"
}
}
En este pequeño código estaremos declarando nuestro “provider” o lugar donde vamos a subir nuestra infraestructura. En este caso es AWS. Luego está el módulo de VPC. VPC son las siglas de Virtual Private Cloud y es una especie de datacenter virtual donde configuras tu red en AWS. El módulo no es mío por cierto. No tenía tiempo ni ganas de picarme un módulo de VPC :S
Luego, como veis hay variables que tendremos en nuestro “variables.tf” donde configuraremos todo. Ya digo, no quiero entrar mucho en Terraform.
Los elementos que despliego con Terraform son:
- VPC
- Subnets
- Publicas
- Privadas
- InternetGateways
- Subnets
- RDS
- SecurityGroups
- S3 Buckets
- S3 Endpoints
- Lambdas
- IAM
- Roles
- Policies
- CloudWatch
- Alertas
- Filtros de métricas
- AWS Beanstalk Entornos
AWS
El servicio que he utilizado para desplegar mi WordPress es AWS Beanstalk. ¿Qué es AWS Beanstalk?
Con Elastic Beanstalk, puede implementar y administrar rápidamente aplicaciones en la nube de AWS sin tener que aprender a utilizar la infraestructura en la que se ejecutan. Elastic Beanstalk reduce la complejidad de administración sin restringir la liberad de elección ni el control. Solo tiene que cargar la aplicación y Elastic Beanstalk gestionará de manera automática los detalles de aprovisionamiento de capacidad, equilibrio de carga, escalado y monitorización del estado de la aplicación.
Elastic Beanstalk es compatible con aplicaciones desarrolladas en Go, Java, .NET, Node.js, PHP, Python y Ruby. Cuando implementa su aplicación, Elastic Beanstalk crea la versión de la plataforma seleccionada compatible y aprovisiona uno o varios recursos de AWS, como instancias de Amazon EC2, para ejecutar la aplicación.
https://docs.aws.amazon.com/es_es/elasticbeanstalk/latest/dg/Welcome.html
Ejemplo de Aplicación en AWS Beanstalk
Como veis, solo debemos subir un archivo .zip con nuestra aplicación y AWS Beanstalk se encarga de todo. En mi caso, cuando empecé a trabajar en esto me pregunté si quería versionarlo también con Terraform y en decidí que no. Son muchas las opciones y en aquel momento no recuerdo si había algún problema. Hace poco hice un “terraform import” de un entorno de Beanstalk y la verdad es que, es coser y cantar. Lo único que no haremos con AWS Beanstalk es acoplar la base de datos porque en caso de destrucción del entorno, por voluntad o error perderíamos la RDS y no es algo recomendable.
En esta solución no hay integración continua (CI) ya que, WordPress es un código que no necesita de software adicional y no hay necesidad de tests, etc. Si la hubiera podríamos utilizar AWS CodeBuild. En mi caso, no lo necesito.
RDS para MySQL
El motor de base de datos es MySQL 5.7 y utilizo RDS. Importante mencionar de nuevo que, con AWS Beanstalk tu puedes asociar una RDS que elijas pero, en el momento en que elimines el entorno, eliminarás la RDS. La buena práctica sugiere separar la aplicación del motor de base de datos.
¿Por qué RDS, además de separarla de Beanstalk? Bueno, no quería liarme a montar mi propia EC2 con una instalación custom de MySQL porque debería haber buscado entonces alguna herramienta adicional para provisionar AMIs y no tenía tiempo.
Pero obviamente tiene su impacto en el coste mensual. Por comparar:
Tipo | Tipo | Total mes | Total hora |
EC2 t3.micro 5 GB EBS | OnDemand | 8,87$ | 0,011$ |
RDS t3.micro 5 GB EBS | OnDemand | 13,78$ | 0,018$ |
Aquí faltaría añadir no obstante que, el espacio de RDS es gratuito. AWS RDS provisiona un espacio de backup igual al tamaño de nuestro disco de almacenamiento y los backups automáticos están habilitados por defecto. Claro, a nivel empresa una diferencia de 4,91$ NO ES NADA pero cuando somos nosotros quien lo pagamos, la cosa cambia, porque al año serán 60$ más a pagar. En todo caso en su momento me decidí por RDS y así se ha mantenido.
Lógicamente por tema de coste tampoco está en MultiAZ, ya que, no busco alta disponibilidad en ninguna capa.
Orquestación en AWS: CodePipeline
Vale, de momento tenemos que AWS Beanstalk va a montar los grupos de autoescalado, se encargará de la AMI necesaria, lanzar la EC2, los grupos de seguridad, variables de entorno, etc etc.
¿Pero cómo sabrá AWS Beanstalk cuando y qué hacer? Esto lo conseguimos con AWS CodePipeline.
AWS CodePipeline es un servicio de entrega continua completamente administrado que permite automatizar canalizaciones de lanzamiento para lograr actualizaciones de infraestructura y aplicaciones rápidas y fiables. CodePipeline automatiza las fases de compilación, prueba e implementación del proceso de lanzamiento cada vez que se realiza una modificación en el código, en función del modelo de lanzamiento que defina
https://aws.amazon.com/es/codepipeline/
Volvemos a lo mismo, en mi caso, no he escriptado la pipeline. ¿Pero no lo hacías todo con IaaC? Bueno, sí y no. El tiempo era el gran enemigo aquí y no tuve tiempo de escritpar todo. Pero ojo, poder se puede y no es complicado. Voy a explicar mi sencilla pipeline.
El digrama de flujo de esta pipeline sería algo así:
Obviamente hay mil maneras de hacerlo. Tener los entornos completamente separados y desplegar cambio solo en el entorno de beta y no mergear automáticamente a master y hacerlo por separado en otra pipeline, etc. Como este caso es muy sencillo, para mi tenerlo en una sola pipeline es más que suficiente. La única cosa rara es que, cuando se despliega en beta y todo va bien, espera un aprobado manual (cosa que permite CodePipeline) y si es correcto, se invoca una Lambda que hacer un PR hacia GitHub y hace el merge a master. Y es ahí entonces cuando sea activa el último paso hacia el AWS Beanstalk de “producción”.
Diagrama
Como veis no es nada del otro mundo.
(He actualizado el diagrama, mirar notas abajo).
- Autoescalados en subnets públicas con solo 1 EC2
- Security groups limitan el acceso al puerto 22 solo para X IPS
- SG abren puerto 443 y 80 en el autoescalado de producción
- SG limitan el 443 y el 80 en el autoescalado de beta
- RDS desplegada en subnet privada sin acceso a NAT Gateway
- Route53 como servidor DNS
- Envía el tráfico hacia la ElasticIP del autoescalado de prod
- Envía el tráfico de estáticos hacia la distribución de CloudFront asociada a un bucket de S3
- AWS Beanstalk se encarga del deploy de las EC2 y sus respectivos autoescalados
- El autoescalado de beta utiliza instancias Spot
- El autoescalado de prod, on-demand
- CodePipeline orquesta los despliegues
- Los cambios necesarios serán introducidos manualmente via Terraform sin pipeline (la IaaC no está orquestada, pendiente)
Costes
La solución es bastante más cara que en Linode pero hay otras muchas ventajas a tener en cuenta.
La facturación ahora mismo está sobre los 40$ al mes. Donde la mayoría del gasto es computación EC2. Posibles soluciones para bajar el coste serían deshacerse de la RDS pero entonces habría que provisionar una EC2, configurar la AMI, etc. Que no es dinero pero es tiempo.
Una cosa que hacemos para bajar los costes es dejar parado el entorno de beta, mientras no está en uso. Mediante una Lambda, que se invoca en una hora determinada, se revisa el autoescalado del grupo de beta y se decide parar las EC2 para levantarlas luego más tarde.
Ahora hace poco he abilitado la Spot en el autoescalado de beta y tengo pendiente aún mirar como evolucionan los costes.
Conclusiones
El objetivo inicial era mover el blog a AWS. Ha sido entretenido pero el resultado me parece razonable. Mi idea era poder aprender mediante lidiaba con cloud de AWS y eso se puede decir que se ha conseguido, aún a costa de aumentar la factura.
En el próximo post, comentaré que plugins uso en combinación con el cloud de AWS.
Actualización
- Diagrama actualizado con la implementación de CloudFront + WAF