Cómo compilar aplicaciones web escalables y resilientes en Google Cloud Platform

Crear aplicaciones resilientes y escalables es una parte esencial de la arquitectura de cualquier aplicación. Una aplicación bien diseñada debe ser capaz de escalar sin contratiempos a medida que la demanda aumenta o disminuye, y debe ser lo suficientemente resiliente como para soportar la pérdida de uno o más recursos de procesamiento.

Este documento está dirigido a profesionales de operaciones de sistemas familiarizados con Compute Engine. En este documento, aprenderás cómo usar Google Cloud Platform para compilar arquitecturas de aplicaciones resilientes y escalables con patrones y recomendaciones que, generalmente, se pueden usar en cualquier aplicación web. Verás cómo se aplican estos principios en situaciones de la vida real con un ejemplo de implementación de la popular herramienta de administración de proyectos de código abierto Redmine, una aplicación basada en Ruby on Rails. Más adelante, en la sección Cómo implementar la solución de ejemplo, tendrás la oportunidad de implementar la aplicación y descargar toda la fuente a modo de referencia.

GCP te permite compilar y ejecutar de manera simple y rentable aplicaciones web que son escalables y resilientes. Los servicios como Compute Engine y el Escalador automático facilitan el ajuste de los recursos de la aplicación según la demanda. Así como con el modelo de precios de Compute Engine, el pago es por segundo, y automáticamente recibes el mejor precio con Descuentos por uso continuo, sin necesidad de ninguna planificación complicada de capacidad o reserva.

Para obtener una descripción general de tus opciones de hosting web en GCP, consulta Cómo entregar sitios web.

Antes de comenzar

Cómo definir la escalabilidad y la resiliencia

Antes de describir un ejemplo de arquitectura de aplicación, será útil definir los términos escalabilidad y resiliencia.

Escalabilidad: ajuste de la capacidad para alcanzar la demanda

Una aplicación web escalable es aquella que funciona bien con 1 usuario o con 1,000,000 de usuarios, y que es capaz de manejar sin problemas picos y caídas de tráfico automáticamente. Cuando se agregan y quitan máquinas virtuales solo en los casos en que es necesario, las aplicaciones escalables solo consumen los recursos necesarios para cumplir con la demanda.

En el siguiente diagrama, se muestra cómo una aplicación escalable responde ante aumentos y caídas de la demanda.

Gráfico que muestra cómo escalar recursos para cumplir con la demanda.

Ten en cuenta que la capacidad se ajusta de manera dinámica para justificar los cambios en la demanda. Esta configuración, a veces conocida como la elasticidad del diseño, ayuda a garantizar que solo pagues por los recursos de procesamiento que necesita la aplicación en un momento determinado.

Resiliencia: diseñada para soportar lo inesperado

Una aplicación web altamente disponible, o resiliente, es aquella que continúa funcionando a pesar de las fallas esperadas o inesperadas de los componentes del sistema. Si falla una instancia o una zona completa presenta un problema, una aplicación resiliente no se ve afectada por estas fallas, sino que continúa funcionando y se autorrepara de manera automática si fuera necesario. Debido a que la información con estado no se almacena en una única instancia, la pérdida de una instancia (o incluso de una zona completa) no debería afectar el rendimiento de la aplicación.

Una aplicación verdaderamente resiliente requiere la planificación tanto a nivel de desarrollo de software como a nivel de arquitectura de aplicación. Este documento se enfoca en el nivel de arquitectura de aplicación.

El diseño de una arquitectura de aplicación para una aplicación resiliente, por lo general, consta de las siguientes características:

  • Balanceadores de cargas para supervisar los servidores y distribuir el tráfico en los servidores que puedan manejar mejor la solicitud
  • Alojamiento de máquinas virtuales en varias regiones
  • Configuración de una solución de almacenamiento robusta

Google Cloud Platform: flexible y rentable

Las arquitecturas tradicionales que son compatibles con la escalabilidad y la resiliencia, por lo general, implican una inversión significativa en recursos. Con las soluciones locales, la escalabilidad, a menudo, implica tomar una decisión entre incurrir en gastos adicionales en capacidad del servidor para manejar los picos de uso, o adquirir solo lo necesario según el promedio y arriesgarse a experimentar un rendimiento de la aplicación o una experiencia del usuario deficientes en los momentos de tráfico máximo. La resiliencia implica mucho más que solo la capacidad del servidor; la ubicación también es importante. Para reducir el impacto de eventos físicos, como tormentas graves o terremotos, debes considerar la operación de servidores en diferentes ubicaciones físicas, lo que significa incurrir en un gasto considerable.

GCP ofrece una alternativa: un conjunto de servicios en la nube que te proporcionan una manera flexible de agregar escalabilidad y resiliencia a la arquitectura. Además, GCP ofrece estos servicios con una estructura de precios fácil de controlar.

Cómo compilar arquitecturas resilientes y escalables con Google Cloud Platform

En la siguiente tabla, se muestra cómo diferentes servicios de GCP se asignan a los requisitos clave necesarios para que las aplicaciones sean escalables y resilientes.

Requisito de la arquitectura Servicio de Google Cloud Platform
Balanceo de cargas Balanceo de cargas de HTTP
Hosting del servidor Compute Engine, Regiones y zonas
Administración del servidor Plantillas de instancias, Grupos de instancias administrados, Escalador automático
Almacenamiento de datos Cloud SQL, Cloud Storage

En el siguiente diagrama, se muestra cómo trabajan estos componentes de GCP en conjunto para compilar una aplicación web escalable y resiliente. La función que cumple cada componente se describe con más detalle en la próxima sección.

Diagrama que muestra una aplicación web escalable y resiliente.

Descripción general de los componentes

Cada componente en el ejemplo de arquitectura de la aplicación cumple una función para garantizar que la aplicación sea tanto escalable como resiliente. En esta sección, se describe brevemente cada uno de estos componentes. En las próximas secciones, se muestra cómo cada uno de estos servicios trabaja en conjunto.

Balanceador de cargas de HTTP

El balanceador de cargas de HTTP expone una única dirección IP pública que utilizan los clientes para acceder a la aplicación. La dirección IP puede estar asociada a un registro A DNS (p. ej., example.com) o CNAME (p. ej., www.example.com). Las solicitudes entrantes se distribuyen en los grupos de instancias en cada zona según la capacidad de cada grupo. Dentro de la zona, las solicitudes se propagan de manera uniforme en las instancias del grupo. Si bien el balanceador de cargas de HTTP puede balancear el tráfico en varias regiones, esta solución de ejemplo lo usa en una sola región con varias zonas.

Zona

Una zona es una ubicación aislada dentro de la región. Las zonas tienen ancho de banda alto y conexiones de red de latencia baja con otras zonas en la misma región. Google recomienda implementar aplicaciones a través de múltiples zonas en una región.

Instancia

Una instancia es una máquina virtual alojada en la infraestructura de Google. Puedes instalar y configurar estas instancias como lo haces con servidores físicos. En el ejemplo de implementación, utilizas secuencias de comandos de inicio Chef para configurar instancias con el servidor de la aplicación y el código de la aplicación web.

Plantilla de instancias

Una plantilla de instancias define el tipo de máquina, la imagen, la zona, las etiquetas y otras propiedades de la instancia. Se puede utilizar para crear un grupo de instancias administrado.

Grupo de instancias administrado

Un grupo de instancias administrado es una colección de instancias homogéneas basado en una plantilla de instancias. Un balanceador de cargas HTTP puede orientarse a un grupo de instancias administrado para propagar el trabajo por las instancias del grupo. Un grupo de instancias administrado tiene un respectivo recurso de administrador de grupo de instancias, que es responsable de agregar y quitar instancias del grupo.

Escalador automático

El Escalador automático de Compute Engine agrega y quita instancias de Compute Engine a un grupo de instancias administrado mediante la interacción con el administrador del grupo como respuesta al tráfico, al uso de CPU o a otras señales. En la solución de ejemplo, el Escalador automático responde a la métrica de Solicitud por segundo (RPS) del balanceador de cargas de HTTP. Se requiere un Escalador automático para cada grupo de instancias administrado que desees escalar automáticamente.

Cloud SQL

Cloud SQL es un servicio de base de datos completamente administrado compatible tanto con MySQL como con PostgreSQL. Google se encarga de administrar las replicaciones, las encriptaciones, los parches y las copias de seguridad. Una instancia de Cloud SQL se implementa en una zona única, y los datos se replican de manera automática en otras zonas. La aplicación Redmine que se utiliza en este ejemplo es compatible con MySQL y trabaja sin problemas con Cloud SQL.

Cloud Storage

Cloud Storage permite que los objetos (por lo general, archivos) se almacenen y recuperen con una interfaz simple y escalable. En esta solución, se utiliza un depósito de Cloud Storage para distribuir claves de SSL privadas a las instancias escalables de Compute Engine. También se utiliza a fin de almacenar todos los archivos subidos a la aplicación Redmine, lo que significa que no se almacena información con estado en ningún disco de la instancia.

Resiliencia

Para que este ejemplo de arquitectura sea resiliente, debe ser capaz de reemplazar automáticamente instancias que fallaron o dejaron de estar disponibles. Cuando una instancia nueva se encuentra en línea, debería cumplir con las siguientes características:

  • Comprender su función dentro del sistema
  • Configurarse automáticamente
  • Descubrir cualquiera de sus dependencias
  • Comenzar a manejar solicitudes automáticamente

Para reemplazar automáticamente una instancia que falló, puedes utilizar varios componentes de Compute Engine juntos:

Una secuencia de comandos de inicio se ejecuta cuando la instancia se inicia o se reinicia. Puedes utilizar estas secuencias de comandos con el fin de instalar software y actualizaciones, para asegurarte de que los servicios se ejecuten dentro de la máquina virtual, o incluso con el objetivo de instalar una herramienta de administración de configuración, como Chef, Puppet, Ansible o Salt.

En esta situación, se utiliza una secuencia de comandos de inicio para instalar Chef Solo, que, a su vez, también configura instancias con el fin de trabajar con la aplicación. Si deseas obtener información sobre cómo puedes utilizar las secuencias de comandos de inicio y Chef Solo con el fin de configurar instancias de manera automática, consulta Apéndice: Cómo agregar una instancia nueva al final de este tema.

Además de una secuencia de comandos de inicio, debes definir algunos elementos más antes de iniciar una instancia de Compute Engine. Por ejemplo, tienes que especificar el tipo de máquina, la imagen del sistema operativo que se utilizará y cualquier disco que desees adjuntar. Estas opciones las defines con una plantilla de instancias.

En conjunto, una plantilla de instancias y una secuencia de comandos de inicio definen cómo iniciar una instancia de Compute Engine y cómo configurar el software en esa instancia para cumplir una función específica en la arquitectura de tu aplicación.

Diagrama que muestra el trabajo en conjunto de las secuencias de comandos de inicio, las plantillas de instancias y las instancias.

Por supuesto, una plantilla de instancias es solo eso, una plantilla. A fin de ponerla en funcionamiento, necesitas una manera de aplicar esa plantilla en una instancia nueva de Compute Engine en el momento en que se pone en línea. Para lograrlo, debes crear un grupo de instancias administrado. Determinas la cantidad de instancias que deseas que se ejecuten en determinado momento y la plantilla de instancias que deseas aplicar a esas instancias. Luego, un administrador de grupo de instancias es responsable del inicio y la configuración de esas instancias, según sea necesario.

En el siguiente diagrama, se muestra cómo trabajan juntos estos componentes:

  • Secuencia de comandos de inicio
  • Plantilla de instancias
  • Administrador de grupos de instancias
  • Grupo de instancias administrado
Diagrama que muestra el trabajo en conjunto de las secuencias de comandos de inicio, las plantillas de instancias, los administradores de grupo de instancias y los grupos de instancias administrados.

Un grupo de instancias administrado y su administrador de grupo de instancias correspondiente pueden ser recursos específicos de una zona o región. Una plantilla de instancias es un recurso a nivel del proyecto que se puede reutilizar en varios grupos de instancias administrados en cualquier zona y en cualquier región. Sin embargo, puedes especificar algunos recursos zonales en una plantilla de instancias, lo que restringe el uso de esa plantilla a la zona en la cual residen esos recursos zonales.

Gracias a la secuencia de comandos de inicio, las plantillas de instancias y los grupos de instancias administrados, ahora tienes un sistema que puede reemplazar instancias en mal estado por instancias nuevas. En la próxima sección, verás una manera con la que puedes definir qué es una instancia en mal estado y cómo detectarla.

Verificaciones de estado

En este momento, la aplicación de ejemplo tiene casi todas las herramientas que necesita para ser resiliente. Sin embargo, falta un elemento; la aplicación necesita una manera de identificar las instancias en mal estado para poder reemplazarlas.

Esta aplicación está diseñada para que los usuarios se conecten a una instancia adecuada en buen estado con un balanceador de cargas de HTTP. Esta arquitectura te permite utilizar dos servicios para identificar las instancias capaces de realizar solicitudes de entrega:

  • Verificaciones de estado. Una verificación de estado HTTP especifica el puerto y la ruta en la que se ejecutan las verificaciones de estado para cada instancia. Una instancia en buen estado arroja una respuesta esperada de 200 OK para una verificación de estado correcta.
  • Servicios de backend. Un servicio de backend define uno o más grupos de instancias que deben recibir tráfico del balanceador de cargas. El servicio de backend especifica el puerto y el protocolo que expone la instancia (p. ej., puerto HTTP 80), así como la verificación de estado de HTTP que se utilizará con las instancias en los grupos de instancias.

En el siguiente diagrama, se muestra la arquitectura de la aplicación y la manera en que un servicio de backend y una verificación de estado de HTTP se relacionan con el balanceador de cargas y los grupos de instancias.

Diagrama que muestra las verificaciones de estado y los servicios de backend.

Resiliencia de datos con Cloud SQL

Las tres áreas principales de cualquier arquitectura de aplicación son redes, procesamiento y almacenamiento. La arquitectura de aplicación que se describe en este documento abarca los componentes de redes y procesamiento, pero para estar completa, también tiene que abordar el componente de almacenamiento.

Este ejemplo de solución utiliza instancias de Cloud SQL de primera generación a fin de proporcionar una base de datos MySQL completamente administrada. Con Cloud SQL, Google administra de manera automática la replicación, la encriptación, y el control de parches y copias de seguridad.

La base de datos de Cloud SQL es regional, lo que significa que los datos se replican en todas las zonas de una región. Esto equivale a realizar una copia de seguridad de cualquier actualización de los datos en ese momento. En el caso improbable de una falla total de la zona, se preservarán los datos.

Cloud SQL te permite elegir entre dos tipos de replicaciones:

  • Replicación síncrona. Con la replicación síncrona, las actualizaciones se copian en varias zonas antes de regresar al cliente. Esto favorece mucho la confiabilidad y disponibilidad en caso de que sucedan incidentes graves, pero ralentiza las operaciones de escritura.
  • Replicación asíncrona. La replicación asíncrona aumenta la capacidad de procesamiento de escritura mediante el reconocimiento de la operación de escritura una vez que se almacenan en caché local, pero antes de copiar los datos a otra ubicación. La replicación asíncrona da como resultado una operación más rápida de escritura en la base de datos, ya que no tienes que esperar a que termine la replicación. Sin embargo, es posible que pierdas la actualización más reciente en el improbable caso de que ocurra una falla del sistema en el centro de datos usos segundos después de la actualización de la base de datos.

La aplicación Redmine que se usa en esta solución utiliza la replicación síncrona, ya que la carga de trabajo no tiene una operación de escritura tan intensa. Debes elegir entre la replicación síncrona y asíncrona según los requisitos específicos de rendimiento de escritura y durabilidad de datos de tu aplicación específica.

Escalabilidad

En las secciones anteriores, se muestra cómo la aplicación de ejemplo utiliza GCP para crear una aplicación resiliente. Pero la resiliencia no es suficiente; la escalabilidad también es importante. Esta aplicación debería funcionar en 1 usuario o 1,000,000, y sus recursos deberían aumentar o disminuir en función de esos usuarios para ser rentable.

A fin de que los recursos de la aplicación puedan aumentar o disminuir, es necesario que esta tenga lo siguiente:

  • Una manera para agregar o quitar instancias del servicio. También necesitas una manera de decidir cuándo se tiene que agregar una instancia y cuándo se debe quitar. El Escalador automático de GCP soluciona este problema.
  • Una manera de almacenar datos con estado. Ya que las instancias no son estables, no se recomienda almacenar los datos con estado en esas instancias. La arquitectura de la aplicación soluciona este problema para los datos relacionales al almacenarlos en una instancia de Cloud SQL separada, pero también necesita justificar los archivos que sube el usuario. Cloud Storage cumple este requisito.

En las siguientes secciones, se describe cómo usar el Escalador automático con el fin de escalar la infraestructura que ejecuta la aplicación Redmine y cómo aprovechar Cloud Storage para los archivos subidos.

Escalar con el escalador automático

Debido a que el uso de la aplicación es fluctuante, es necesario poder ajustar los recursos que necesita de forma dinámica. Puedes resolver este desafío con el Escalador automático de Compute Engine.

Cuando el tráfico o la carga aumentan, el Escalador automático agrega recursos con el fin de manejar la actividad adicional, y cuando el tráfico o la carga disminuyen, quita los recursos para ayudarte a reducir los gastos. El Escalador automático realiza estas acciones de manera automática según las reglas de escalamiento que definiste y sin necesidad de intervenciones posteriores de tu parte.

El impacto del Escalador automático es doble:

  1. Los usuarios obtienen una experiencia fantástica cuando utilizan tu aplicación, porque siempre hay recursos suficientes para cumplir con la demanda.
  2. Puedes mantener mejor el control de los gastos, porque el Escalador automático quita las instancias cuando la demanda se encuentra por debajo del umbral especificado.

El Escalador automático puede escalar una cantidad de máquinas virtuales según el uso de CPU, la capacidad de entrega o una métrica de Stackdriver Monitoring. Esta solución utiliza la métrica de capacidad de entrega para agregar o quitar instancias de Compute Engine según la cantidad de solicitudes por segundo (RPS) que reciben las instancias del balanceador de cargas. Si deseas obtener más información, consulta Procesamiento por lotes con el Escalador automático de Compute Engine.

Solicitudes por segundo (RPS)

En las secciones anteriores, se describió un servicio de backend único que identifica los grupos de instancias que reciben el tráfico del balanceador de cargas. Para cada uno de los grupos de instancias asociados con el servicio de backend, este ejemplo de solución también configura balancingMode=RATE. Esta propiedad le indica al balanceador de cargas que balancee según las RPS que se definieron en la propiedad maxRatePerInstance, la cual está configurada en 100 para este ejemplo. Esta configuración significa que el balanceador de cargas intentará mantener cada instancia en un rango de 100 RPS o menor. Consulta la documentación de servicios de backend para obtener más información sobre las propiedades de configuración del servicio de backend.

Si deseas escalar en RPS, tienes que crear un Escalador automático para cada grupo de instancias que quieras que se escale automáticamente. En este ejemplo, el grupo de instancias es un recurso por zona, de manera que tienes que crear un Escalador automático en cada zona.

Diagrama que muestra cómo se ajusta un Escalador automático en una arquitectura de aplicación.

Cada Escalador automático incluye una propiedad de utilizationTarget que define la fracción de la capacidad de entrega máxima del balanceador de cargas que debe mantener el Escalador automático. En este ejemplo, se configura cada utilizationTarget del Escalador automático en un 80% de la tarifa máxima del servicio de backend de 100 RPS para cada instancia. Esto significa que el Escalador automático escalará cuando las RPS superen el 80% de la tarifa máxima por instancia, que equivale a 80 RPS. El Escalador automático disminuirá el escalamiento cuando las RPS se encuentren por debajo del umbral.

Diagrama de flujo que muestra cómo el Escalador automático determina si se debe agregar o quitar una instancia.

Cada Escalador automático también define una cantidad máxima y mínima de instancias que este no incumplirá.

Ten en cuenta que solo los grupos de instancias administrados ofrecen las capacidades de ajuste de escala automático. Si deseas obtener más información, consulta Grupos de instancias y Cómo realizar ajustes de escala automáticos de grupos de instancias.

Manejo de subida de archivos

Una parte de la funcionalidad de la aplicación Redmine incluye la posibilidad de que los usuarios suban y guarden archivos cuando acceden a sus cuentas. El comportamiento predeterminado de Redmine y de muchas otras aplicaciones web similares consiste en guardar esos archivos directamente en el disco local. Este enfoque funciona bien si solo tiene un servidor con un mecanismo de copia de seguridad bien definido. Sin embargo, este no es el enfoque óptimo cuando tienes varias instancias de Compute Engine con ajuste de escala automático detrás de un balanceador de cargas. Si un usuario sube un archivo, no se puede garantizar que la próxima solicitud llegará a la máquina en la que se guardaron los archivos. Tampoco se puede garantizar que el Escalador automático no finalice una instancia innecesaria que contenga archivos.

Una mejor solución es utilizar Cloud Storage, que proporciona una ubicación centralizada perfecta para guardar archivos que se subieron desde una flota con ajuste de escala automático de servidores web y acceder a ellos. Cloud Storage también expone una API que es interoperable con clientes Amazon S3, lo cual la hace compatible con los complementos de las aplicaciones existentes para S3, incluido el complemento Redmine S3 sin ninguna modificación. Muchas aplicaciones de código abierto de terceros tienen complementos compatibles con los almacenamientos de objetos como Cloud Storage. Si estás compilando tu propia aplicación, puedes utilizar directamente la API de Cloud Storage para el almacenamiento de archivos.

A continuación, se muestra el flujo de subida (flechas azules) y recuperación (flechas verdes) de archivos con Redmine y Cloud Storage:

Diagrama que muestra el flujo de solicitudes a través de la aplicación Redmine.

El proceso que se muestra en el diagrama consta de los siguientes pasos:

  1. El usuario publica el archivo desde un navegador web.
  2. El balanceador de cargas elige una instancia que se encarga de la solicitud.
  3. La instancia almacena el archivo en Cloud Storage.
  4. La instancia almacena los metadatos del archivo (como el nombre, el propietario y la ubicación en Cloud Storage) en la base de datos de Cloud SQL.
  5. Cuando un usuario solicita un archivo, este se transmite desde Cloud Storage a una instancia.
  6. La instancia envía la transmisión a través del balanceador de cargas.
  7. El archivo se envía al usuario.

Capacidad de almacenamiento

Además de quitar subidas de archivos con estado de las instancias de Compute Engine y permitir que se realice un escalamiento dinámico, Cloud Storage proporciona almacenamiento redundante y duradero para una cantidad prácticamente infinita de archivos. Esta solución de almacenamiento es resiliente, escalable y rentable; solo pagas por el almacenamiento que usas sin tener que preocuparte por la planificación de capacidad, y los datos se almacenan de manera automática y redundante en varias zonas.

Costo

Hasta el momento, la arquitectura de aplicación que se describe en este documento muestra cómo compilar una aplicación escalable y resiliente con GCP. Sin embargo, la capacidad de compilar una aplicación no es suficiente, tienes que ser capaz de compilarla de la manera más rentable posible.

En esta sección, se demuestra cómo la arquitectura de aplicación que se describe en este documento no solo es resiliente y escalable, sino también altamente rentable. Al principio, se exponen suposiciones generales sobre la intensidad y frecuencia con que se utiliza la aplicación y, luego, se convierten esas suposiciones en una estimación de costos básica. Ten en cuenta que estas suposiciones son solo suposiciones. No dudes en ajustar estos números según sea necesario para crear una estimación de costos que se acerque más al uso previsto de tu aplicación.

Procesamiento

Una de las principales preocupaciones de cualquier arquitectura de aplicación es cuánto cuesta mantener los servidores en funcionamiento. Este análisis de costos utiliza las siguientes suposiciones:

Métrica Valor
Visualizaciones de página promedio por mes 20,000,000
Solicitudes HTTP promedio por mes 120,000,000
Horario pico de uso (90% o más) 7:00 a.m. a 6:00 p.m., de lunes a viernes
Transferencia de datos por vista de una página 100 KB
Horas pico por mes 220
Frecuencia de solicitudes durante horarios pico 127 solicitudes por segundo (RPS)
Frecuencia de solicitudes durante horas de menor demanda 6 solicitudes por segundo (RPS)

Según estas suposiciones, puedes calcular la cantidad de visualizaciones de página que recibe la aplicación cada mes durante los horarios pico de 7:00 a.m. a 6:00 p.m., de lunes a viernes:

20,000,000 (visualizaciones por mes) * 6 (solicitudes por visualización) * 90% (en horarios pico) = 108,000,000

En promedio, cada mes tiene 22 días hábiles. Si cada día hábil tiene 11 horas pico, entonces debes proporcionar suficientes recursos de procesamiento para manejar 242 horas pico por mes.

A continuación, tienes que determinar qué tipo de instancia de Compute Engine es capaz de manejar este tipo de tráfico. La arquitectura de aplicación se probó con gatling.io para realizar una prueba de carga básica. Los resultados de estas pruebas determinaron que 4 instancias de Compute Engine del tipo n1-standard-1 serían suficientes.

En los horarios de menor demanda, esta solución ejecuta un mínimo de dos instancias n1-standard-1.

Para comprobar cuánto cuesta ejecutar estas instancias, revisa las estimaciones de precios más recientes en la Calculadora de precios de Google Cloud Platform. Cuando lo hagas, ten en cuenta que, en ambos casos, estas instancias califican automáticamente para los Descuentos por uso continuo.

Balanceo de cargas y transferencia de datos

Esta aplicación aprovisionó a un balanceador de cargas con una Regla de envío única, que es la dirección IP pública a la cual se conectan los usuarios. Esa regla de envío se factura por hora.

Para realizar las estimaciones de transferencia de datos, primero considera la peor situación. El balanceador de cargas cobra por los datos de entrada que procesa, y las tarifas de salida habituales se cobran según el tráfico de salida del balanceador de cargas. Supongamos que el 99.5% de las 120,000,000 de solicitudes HTTP son de usuarios cargando una página de proyecto de Redmine. Cargar una página representa 1 solicitud HTTP GET, que luego genera 5 solicitudes HTTP GET más para cargar otros elementos (CSS, imágenes y jQuery). La carga de una página completa involucra 6 solicitudes HTTP. El resultado es el siguiente:

  • Aproximadamente, 20,000,000 de cargas de páginas completas por mes
  • Alrededor de 10 KB de datos de entrada procesados y 450 KB de transferencia de datos por página
  • Un total aproximado de 214 GB de datos procesados por el balanceador de cargas por mes y 9,091 GB de tráfico de salida

El 0.5% restante de las 20,000,000 de solicitudes HTTP son solicitudes HTTP POST para subir un archivo de tamaño promedio (alrededor de 0.5 MB), lo que agrega 500 GB de datos adicionales procesados por mes.

Esta estimación de la Calculadora de precios de Google Cloud Platform muestra el costo previsto de los 714 GB de transferencia de datos que tiene que manejar el balanceador de cargas, además de los 9,091 GB de tráfico de salida para esta situación.

Esa estimación de transferencia de datos es la peor situación posible, porque entrega todo el contenido (incluidos los elementos estáticos) de cada solicitud de una instancia de Compute Engine y a través de un balanceador de cargas, sin el beneficio de almacenar en caché ni de red de entrega de contenido (CDN). De los casi 450 KB de carga útil por cada carga de página (y teniendo en cuenta que esta solución se basa en más de 20 millones de cargas por mes), 333 KB de eso requieren la carga de jQuery. Con la simple actualización de una línea de la aplicación para cargar jQuery desde Google Hosted Libraries, puedes reducir los gastos de transferencia de datos un 73%.

Esta estimación de precio actualizada muestra los ahorros en transferencia de datos que se logran con el cambio a Google Hosted Libraries.

Almacenamiento

Esta solución utiliza Cloud Storage para todos los archivos que se suben con la aplicación Redmine. Como se describe en la sección anterior, cerca del 0.5% de este uso se destina a la subida de archivos, con un tamaño promedio de archivo de 0.5 MB. Esto significa que puedes esperar una subida de 1,000,000 de archivos nuevos por mes, lo que da como resultado 500 GB de almacenamiento nuevo por mes. Esta solución también supone 1,000,000 de operaciones HTTP PUT por mes para almacenar archivos nuevos, que se facturan como una operación de clase A.

Esta estimación de precios de la Calculadora de precios de Google Cloud Platform muestra el costo previsto del uso de Cloud Storage.

Esta arquitectura utiliza Cloud SQL para almacenar todos los datos relacionales de la aplicación. Si tomamos las métricas de ejemplo descritas antes, el tipo de base de datos D2 con 1,024 MB de RAM debería proporcionar capacidad suficiente para la carga de trabajo de la aplicación, y funcionaría las 24 horas del día, los 7 días de la semana. Como esta base de datos probablemente tenga un uso más intensivo, elige la opción "Intensivo" en la calculadora para las Operaciones de E/S. Se realizó una prueba para esta arquitectura de ejemplo mediante la inserción de 100,000 documentos; los resultados determinaron que un disco de 50 GB podrá respaldar más de 100,000,000 documentos, lo que permitirá que la base de datos respalde 8 años de uso considerando la tasa que se describe.

Esta es una estimación de la Calculadora de precios de Google Cloud Platform que muestra el costo previsto de esta parte de los gastos de la arquitectura.

Cómo implementar la solución de ejemplo

Para implementar el ejemplo de aplicación que se describe en esta solución, visita el repositorio de GitHub Aplicaciones web escalables y resilientes en Google Cloud Platform.

Apéndice: Cómo agregar una instancia nueva

Como parte del esfuerzo por crear una arquitectura de aplicación resiliente y escalable, debes decidir qué deseas hacer para agregar instancias nuevas. Específicamente, debes determinar cómo configurar de manera automática instancias nuevas a medida que se activan en línea.

En esta sección, encontrarás algunas de las opciones disponibles.

Cómo comenzar con la instalación del software

Para entregar una solicitud web del usuario, cada instancia necesita tener instalado un software adicional además del sistema operativo de base, justo con los datos de configuración, que incluyen datos como la información de conexión de la base de datos, el nombre del depósito de Cloud Storage en el cual se almacenarán los archivos, etcétera. Si te imaginas estos componentes como capas, puedes visualizar toda la pila que se ejecutará en la instancia:

Diagrama que muestra cómo se apila el software en una instancia.

En esta solución, se utiliza una plantilla de instancias que especifica la Imagen de Compute Engine que utilizan las instancias en el momento del inicio. Específicamente, esta solución utiliza la imagen Ubuntu 14.10 con desarrollo y respaldo de Canonical. Debido a que esta es la imagen del sistema operativo base, no contiene el software específico ni ninguna de las configuraciones que requiere la aplicación.

A fin de que el resto de la pila se instale de manera automática cuando se activa una instancia nueva, puedes utilizar una combinación de secuencias de comando de inicio de Compute Engine y Chef Solo para realizar el arranque en el momento del inicio. Puedes especificar una secuencia de comandos de inicio mediante la incorporación de un elemento de atributo de metadatos startup-script a una plantilla de instancias. La secuencia de comandos de inicio se ejecuta en el momento del arranque de la instancia.

Esta secuencia de comandos de inicio realiza las siguientes acciones:

  1. Instala el cliente de Chef.
  2. Descarga un archivo especial de Chef llamado node.json. Este archivo permite que Chef identifique qué configuración específica debe ejecutar para esta instancia.
  3. Ejecuta Chef y le permite que se encargue de ejecutar la configuración detallada.

A continuación, se muestra la secuencia de comandos de inicio completa:

#! /bin/bash

# Install Chef
curl -L https://www.opscode.com/chef/install.sh | bash

# Download node.json (runlist)
curl -L https://github.com/googlecloudplatform/... > /tmp/node.json

# Run Chef
chef-solo -j /tmp/node.json -r https://github.com/googlecloudplatform/...

Nota: En esta solución, no se analizará el funcionamiento de Chef. Todas las recetas y guías de soluciones son de código abierto y se encuentran disponibles en Aplicaciones web escalables y resilientes en Google Cloud Platform.

Cómo proporcionar la configuración de la aplicación

Luego de que una instancia nueva arranca y se configura con la secuencia de comandos de inicio y Chef, necesita obtener cierta información antes de comenzar las entregas de solicitudes. En este ejemplo, cada instancia necesita saber la información de conexión de la base de datos (como nombre de host, nombre de usuario y contraseña), como también el nombre del depósito de Cloud Storage que se utilizará y las credenciales de conexión.

Todas las instancias de Compute Engine tienen atributos de metadatos asociados con ellas que puedes definir. En secciones anteriores, aprendiste cómo agregar el atributo de metadatos especial startup-script, pero también puedes agregar pares clave=valor arbitrarios. Aquí puedes especificar los atributos de la plantilla de instancias que se incluirán en los datos de configuración que necesitan las instancias para conectarse a la base de datos y al depósito de Cloud Storage.

A continuación, se muestra un ejemplo de los metadatos de una plantilla de instancias desde GCP Console:

Captura de pantalla de Google Cloud Platform Console que muestra la información de metadatos personalizada para una plantilla de instancias.

Chef utiliza una herramienta llamada Ohai para analizar estos bits de información de configuración de los metadatos de la instancia, mediante la propagación de plantillas para crear los archivos de configuración que necesita la aplicación. A continuación, se muestra la plantilla que crea el archivo database.yaml que contiene la información de conexión a la base de datos, y que tiene acceso automático a los elementos de metadatos adecuados:

production:
    adapter: mysql2
    database: <%= node['gce']['instance']['attributes']['dbname'] %>
    host:     <%= node['gce']['instance']['attributes']['dbhost'] %>
    username: <%= node['gce']['instance']['attributes']['dbuser'] %>
    password: <%= node['gce']['instance']['attributes']['dbpassword'] %>

También puedes acceder de manera manual a los valores de metadatos desde la instancia con el servicio de metadatos local. Aquí puedes utilizar curl para obtener la contraseña de la base de datos:

curl "http:/metadata.google.internal/computeMetadata/v1/instance/attributes/dbpassword" -H "Metadata-Flavor: Google"

Consideraciones de rendimiento y dependencia

El enfoque de arranque que se utiliza en esta solución implica el comienzo con una imagen predeterminada del sistema operativo, la instalación de todo el software en el momento del inicio mediante Chef, y la utilización de los metadatos de la instancia para los datos de configuración de la aplicación.

Diagrama que muestra cómo se apila el software en una instancia.Esta pila muestra cómo se agrupa cierto software con la imagen; algunos elementos se instalan en el momento del inicio y otros se proporcionan luego del inicio.

Una de las ventajas de este enfoque es que la configuración del sistema se especifica en una guía de soluciones de Chef. La guía de soluciones puede tener una versión controlada, y se puede compartir y utilizar con el fin de aprovisionar a las máquinas virtuales de manera local para pruebas mediante Vagrant o Docker; o se puede utilizar con el fin de configurar los servidores en tu centro de datos o con diferentes proveedores de servicios en la nube. La administración de imagen también se simplifica: en este ejemplo de aplicación, solo debes realizar un seguimiento de la imagen del SO base que usa la aplicación.

Algunas de las desventajas que se deben considerar incluyen un tiempo potencial de inicio más lento, ya que todo el software se descarga y se instala, y en algunos casos, hasta se requiere compilación. También es importante considerar las dependencias que ingresa este método. En este ejemplo, Chef instaló una cantidad de paquetes de apt, Rubygems y GitHub. Si alguno de esos repositorios no se encuentra disponible en el momento de inicio de una instancia nueva, fallará la configuración.

Imágenes personalizadas y arranque

Ya que puedes crear tus propias imágenes personalizadas con Compute Engine, la instalación de todos los componentes en el momento del inicio no es el único enfoque de arranque. Por ejemplo, puedes realizar las siguientes acciones:

  1. Iniciar una imagen base de Ubuntu 14.10
  2. Instalar todos los componentes, excepto la aplicación Redmine (Ruby, nginx, etc.)
  3. Crear una imagen del resultado
  4. Utilizar esa imagen en la plantilla de instancias

De esa manera, cuando se inicia una instancia nueva, solo se debe instalar Redmine. Se mejora el tiempo de arranque y se reduce la cantidad de dependencias empaquetadas externas.

Diagrama que muestra una pila de instancias con todos los elementos instalados en la imagen, excepto Redmine.

Hasta puedes mejorar el enfoque de imagen personalizada aún más y armar todo en una imagen, incluidas todas las dependencias, las fuentes de la aplicación y la configuración. Este enfoque tiene la ventaja de un arranque más rápido sin dependencias externas, pero si se produce cualquier cambio en tu aplicación, tienes que crear una imagen nueva y actualizar la plantilla de instancias.

Diagrama que muestra una pila de instancias con todo el software agrupado con la imagen.

Piensa en los enfoques de arranque de una instancia como una continuidad. Cuanta más configuración en el momento del inicio, más tiempo de arranque, pero menos imágenes para administrar. Si se ingresa más configuración en una imagen personalizada, el tiempo de arranque será más rápido y habrá menos dependencias; pero quizá haya más imágenes para administrar. Para la mayoría de los clientes, el enfoque correcto es un equilibrio justo. Elige lo que mejor se adapte a ti y a tu aplicación.

Diagrama que muestra las maneras continuas en que el software se instala en una instancia.El rango abarca desde la instalación de todos los elementos luego del inicio, hasta la agrupación de todos los elementos con la imagen.
¿Te sirvió esta página? Envíanos tu opinión:

Enviar comentarios sobre…