Como criar aplicativos da Web escalonáveis e resilientes no Google Cloud Platform

A criação de aplicativos resilientes e escalonáveis é essencial em qualquer arquitetura de aplicativo. Um aplicativo bem projetado deve ter escalabilidade perfeita à medida que a demanda aumenta e diminui. Além disso, precisa ser resiliente o bastante para suportar a perda de um ou mais recursos de computação.

Este documento destina-se a profissionais em operações de sistemas familiarizados com o Compute Engine. Neste documento, você aprenderá a usar o Google Cloud Platform para criar arquiteturas de aplicativos escalonáveis e resilientes usando padrões e práticas amplamente aplicáveis a qualquer aplicativo da Web. Você verá como esses princípios são aplicados a cenários reais com um exemplo de implantação da conhecida ferramenta de gerenciamento de projetos de código aberto Redmine, um aplicativo baseado em Ruby on Rails. Posteriormente, na seção Implantar a solução de exemplo, você terá a chance de implantar o aplicativo e fazer o download de todo o código-fonte para referência.

Com o GCP, você cria e executa aplicativos da Web escalonáveis e resilientes de maneira fácil e econômica. Serviços como o Compute Engine e o Autoescalador facilitam o ajuste dos recursos do aplicativo conforme a demanda. E com o modelo de preços do Compute Engine, você paga por segundo e recebe automaticamente o melhor preço com Descontos por uso prolongado sem qualquer planejamento de capacidade ou reserva complicado.

Para ter uma visão geral das opções de hospedagem na Web do GCP, veja Como disponibilizar sites.

Antes de começar

Definir escalabilidade e resiliência

Antes de descrever um exemplo de arquitetura de aplicativo, é importante definir os termos escalabilidade e resiliência.

Escalonabilidade: ajustar a capacidade para atender à demanda

Um aplicativo da Web escalonável é aquele que funciona bem com 1 ou 1.000.000 de usuários e lida com picos e quedas de tráfego automaticamente. Ao adicionar e remover máquinas virtuais apenas quando necessário, os aplicativos escalonáveis consomem apenas os recursos necessários para atender à demanda.

O diagrama abaixo mostra como um aplicativo escalonável responde a aumentos e diminuições da demanda.

Um gráfico que mostra como os recursos podem ser escalonados para atender à demanda.

Observe que a capacidade se ajusta dinamicamente para dar conta das variações de demanda. Essa configuração, às vezes chamada de "elasticidade no design", ajuda a garantir que o pagamento seja feito apenas pelos recursos de computação necessários ao aplicativo em um determinado momento.

Resiliência: aplicativos projetados para resistir ao inesperado

Um aplicativo da Web altamente disponível ou resiliente é aquele que continua a funcionar, mesmo com as falhas esperadas ou inesperadas dos componentes do sistema. Em caso de falha em uma única instância ou problema em uma zona inteira, um aplicativo resiliente permanecerá tolerante a falhas, continuando a funcionar e reparando-se automaticamente, quando necessário. Como as informações com estado não são armazenadas em nenhuma instância, o desempenho do aplicativo não será afetado com a perda de uma instância ou mesmo de uma zona inteira

Um aplicativo de fato resiliente requer planejamento em nível de desenvolvimento de software e em nível de arquitetura de aplicativo. O foco deste documento é principalmente o nível de arquitetura do aplicativo.

Projetar a arquitetura de um aplicativo resiliente costuma envolver:

  • balanceadores de carga para monitorar servidores e distribuir tráfego para servidores que lidem melhor com as solicitações;
  • hospedagem de máquinas virtuais em diversas regiões;
  • configuração de uma solução de armazenamento robusta.

Google Cloud Platform: flexível e econômico

As arquiteturas tradicionais compatíveis com a escalabilidade e a resiliência muitas vezes exigem grandes investimentos em recursos. Com soluções locais, escalabilidade geralmente significa decidir entre o excesso de gastos com capacidade do servidor para lidar com picos máximos de uso e comprar apenas de acordo com a necessidade média, arriscando-se a ter desempenho do aplicativo ou experiência do usuário insuficientes em picos de tráfego. No entanto, a resiliência vai além da capacidade do servidor: o local também é importante. Para reduzir o impacto de eventos físicos, como tempestades extremas ou terremotos, é preciso considerar servidores operacionais em diferentes locais físicos, e isso tem um custo significativo.

O GCP oferece uma alternativa: um conjunto de serviços em nuvem que fornece um modo flexível de adicionar escalabilidade e resiliência à arquitetura. Além disso, esses serviços são oferecidos pelo GCP por meio de uma estrutura de preços simples de controlar.

Criar arquiteturas resilientes e escalonáveis com o Google Cloud Platform

A tabela abaixo mostra como os diferentes serviços do GCP são mapeados para os principais requisitos necessários para tornar os aplicativos escalonáveis e resilientes.

Requisito de arquitetura Serviço do Google Cloud Platform
Balanceamento de carga Balanceamento de carga HTTP
Hospedagem de servidor Compute Engine, Regiões e zonas
Gerenciamento de servidor Modelos de instância, grupos de instâncias gerenciadas, autoescalador
Armazenamento de dados Cloud SQL, Cloud Storage

Veja no diagrama abaixo o funcionamento desses componentes do GCP na criação de um aplicativo da Web escalonável e resiliente. O papel desempenhado por cada componente será descrito em detalhes na próxima seção.

Um diagrama que mostra um aplicativo da Web escalável e resiliente.

Visão geral dos componentes

Cada componente do exemplo de arquitetura do aplicativo desempenha um papel para garantir que o aplicativo seja escalonável e resiliente. Nesta seção, faremos uma breve descrição de cada um desses serviços. As seções posteriores mostrarão o funcionamento desses serviços em conjunto.

Balanceador de carga HTTP

O balanceador de carga HTTP expõe um único endereço IP público usado pelos clientes para acessar o aplicativo. Esse IP está associado a um registro de DNS A, como "example.com" ou CNAME, como "www.example.com". As solicitações recebidas são distribuídas pelos grupos de instâncias em cada zona de acordo com a capacidade de cada grupo. Dentro da zona, as solicitações são distribuídas uniformemente pelas instâncias dentro do grupo. Com o balanceador de carga HTTP, você equilibra o tráfego em diversas, No entanto, ele é usado pela solução de exemplo em uma única região com diversas zonas.

Zona

Uma zona é um local isolado dentro de uma região. As zonas têm conexões de rede de alta largura de banda e baixa latência com outras zonas da mesma região. O Google recomenda a implantação de aplicativos em várias zonas de uma região.

Instance

Uma instância é uma máquina virtual hospedada na infraestrutura do Google. É possível instalar e configurar essas instâncias, como é feito com servidores físicos. No exemplo de implementação, use scripts de inicialização e "Chef" para configurar instâncias com o servidor de aplicativos e o código para o aplicativo da Web.

Modelo de instância

Um modelo de instância define um tipo de máquina, imagem, zona, rótulos e outras propriedades dela. Ele é usado para criar um grupo de instâncias gerenciadas.

Grupo de instâncias gerenciadas

Um grupo de instâncias gerenciadas é uma coleção de instâncias homogêneas com base em um modelo de instância. Esse grupo é direcionado por um balanceador de carga HTTP para distribuir o trabalho entre as instâncias do grupo. Ele tem um recurso de gerenciador de grupos de instâncias correspondente, responsável pela adição e remoção de instâncias do grupo.

Autoescalador

Com o Autoescalador do Compute Engine você adiciona ou remove instâncias do Compute Engine em um grupo de instâncias gerenciadas, fazendo interface com o gerenciador do grupo em resposta ao tráfego, à utilização da CPU ou a outros sinais. Na solução de exemplo, o autoescalador responde à métrica de Solicitação por segundo (RPS, na sigla em inglês) do balanceador de carga HTTP. É necessário um Autoescalador para cada grupo de instâncias gerenciadas que você quer escalonar automaticamente.

Cloud SQL

O Cloud SQL é um serviço de banco de dados totalmente gerenciado, compatível com o MySQL e com o PostgreSQL. Replicação, criptografia, patches e backups são gerenciados pelo Google. Uma instância do Cloud SQL é implantada em uma única zona e os dados são replicados automaticamente para outras zonas. O aplicativo Redmine, usado neste exemplo, é compatível com o MySQL e funciona perfeitamente com o Cloud SQL.

Cloud Storage

Com o Google Cloud Storage é possível armazenar e recuperar objetos, geralmente arquivos, com uma interface simples e escalonável. Nessa solução, um intervalo do Cloud Storage é usado na distribuição de chaves SSL particulares para as instâncias do Compute Engine escalonáveis, bem como para armazenar todos os arquivos enviados ao aplicativo Redmine. Isto significa que nenhuma informação com estado é armazenada nos discos de qualquer instância.

Resiliência

Para que este exemplo de arquitetura seja resiliente, ele precisa ser capaz de substituir automaticamente as instâncias com falha ou indisponíveis. Quando uma nova instância fica on-line, ela precisa:

  • compreender o próprio papel no sistema;
  • configurar-se automaticamente;
  • descobrir qualquer dependência própria;
  • começar a lidar com as solicitações automaticamente.

Para substituir automaticamente uma instância com falha, é possível usar vários componentes do Compute Engine juntos:

Um script de inicialização é executado quando a instância é inicializada ou reiniciada. Use esses scripts para instalar software e atualizações, para garantir que os serviços estejam em execução na máquina virtual ou mesmo instalar uma ferramenta de gerenciamento de configuração, como "Chef", "Puppet", "Ansible" ou "Salt".

Este cenário usa um script de inicialização para instalar o Chef Solo que, por sua vez, configura instâncias para trabalhar com o aplicativo. Para saber como usar os scripts de inicialização e o "Chef Solo" para configurar as instâncias automaticamente, veja o Apêndice: Como adicionar uma nova instância, no final deste tópico.

Além de um script de inicialização, é preciso definir mais alguns itens antes de iniciar uma instância do Compute Engine. Por exemplo, é preciso especificar o tipo de máquina, a imagem do sistema operacional a ser usada e os discos que você quer anexar. Essas opções são definidas usando um modelo de instância.

Juntos, um modelo de instância e um script de inicialização definem como iniciar uma instância do Compute Engine e a configuração do software nessa instância para cumprir um papel específico na arquitetura do aplicativo.

Diagrama que mostra como scripts de inicialização, modelos de instância e instâncias trabalham juntos.

Obviamente, um modelo de instância é exatamente o que o nome diz: um modelo. Para que esse modelo funcione, é preciso aplicar um modelo a novas instâncias do Compute Engine quando elas estiverem on-line. Para isso, crie um grupo de instâncias gerenciadas. Determine o número de instâncias que você quer executar a qualquer momento e o modelo de instância que quer aplicar a essas instâncias. Um gerenciador do grupo de instâncias é responsável por iniciar e configurar essas instâncias conforme necessário.

O diagrama a seguir mostra como esses componentes funcionam juntos:

  • script de inicialização
  • modelo de instância
  • gerenciador de grupos de instâncias
  • grupo de instâncias gerenciadas
Diagrama que mostra como scripts de inicialização, modelos de instâncias, gerenciadores de grupos de instâncias e grupos de instâncias gerenciadas trabalham juntos

Um grupo de instâncias gerenciadas e o respectivo gerenciador são recursos regionais ou específicos da zona. Um modelo de instância é um recurso em nível de projeto, reutilizável em diversos grupos de instâncias gerenciadas em qualquer zona, em qualquer região. No entanto, é possível especificar alguns recursos zonais em um modelo de instância, o que restringe o uso desse modelo à zona em que residem esses recursos zonais.

Com scripts de inicialização, modelos de instâncias e grupos de instâncias gerenciadas, agora você tem um sistema que substitui instâncias não íntegras por novas. Na próxima seção, você aprenderá a definir o que é uma instância não íntegra e como detectá-la.

Verificações de integridade

Nesse ponto, o aplicativo de exemplo tem quase todas as ferramentas necessárias para ser resiliente. No entanto, está faltando uma peça: a forma de identificar as instâncias não íntegras para substituí-las.

Este aplicativo foi projetado para que os usuários se conectem a uma instância apropriada e íntegra usando um balanceador de carga HTTP. Com esta arquitetura, você utiliza dois serviços para identificar as instâncias capazes de atender às solicitações:

  • Verificações de integridade. Uma verificação de integridade HTTP especifica a porta e o caminho para executar a verificação de integridade em cada instância. A verificação de integridade espera uma resposta "200 OK" de uma instância íntegra.
  • Serviços de back-end. Um serviço de back-end define um ou mais grupos de instâncias que receberão o tráfego de um balanceador de carga. O serviço de back-end especifica a porta e o protocolo expostos pelas instâncias, por exemplo, porta HTTP 80, além da verificação de integridade HTTP a ser usada nas instâncias dos grupos de instâncias.

O diagrama a seguir mostra a arquitetura do aplicativo e como um serviço de back-end e uma verificação de integridade HTTP se relacionam com o balanceador de carga e os grupos de instâncias.

Um diagrama que mostra as verificações de integridade e os serviços de back-end.

Resiliência de dados com o Cloud SQL

As três principais áreas de qualquer arquitetura de aplicativo são: rede, computação e armazenamento. A arquitetura de aplicativos descrita aqui tratou dos componentes de rede e computação, mas, para ser completa, precisa abordar também o componente de armazenamento.

Este exemplo de solução usa instâncias do Cloud SQL Primeira geração para fornecer um banco de dados MySQL totalmente gerenciado. Com o Cloud SQL, o Google gerencia automaticamente a replicação, a criptografia, o gerenciamento de patches e os backups.

Um banco de dados do Cloud SQL atende a toda a região, ou seja, os dados são replicados nas zonas de uma região. Isso equivale a fazer um backup de quaisquer atualizações de dados assim que elas ocorrem. Na hipótese remota de falha total de uma zona, os dados serão preservados.

O Cloud SQL permite escolher um entre dois tipos de replicação:

  • Replicação síncrona. Na replicação síncrona, as atualizações são copiadas para diversas zonas antes de retornarem ao cliente. Isso é ótimo em relação à confiabilidade e à disponibilidade em caso de grandes incidentes, mas torna a gravação mais lenta.
  • Replicação assíncrona. A replicação assíncrona aumenta a capacidade de gravação, reconhecendo as gravações quando armazenadas em cache localmente, mas antes de copiar os dados para outros locais. A replicação assíncrona resulta em gravações mais rápidas no banco de dados, já que não é preciso aguardar a conclusão da replicação. No entanto, você está sujeito a perder as atualizações mais recentes no caso improvável de uma falha no sistema do data center alguns segundos após a atualização do banco de dados.

No aplicativo Redmine, usado nesta solução, é executada a replicação síncrona, porque a carga de trabalho não faz muitas gravações. A escolha entre replicação síncrona ou assíncrona depende dos requisitos específicos de desempenho de gravação e durabilidade de dados do aplicativo.

Escalonabilidade

Nas seções anteriores, mostramos como o aplicativo de exemplo usa o GCP para criar um aplicativo resiliente. Mas não basta só resiliência: a escalonabilidade também é importante. O aplicativo funciona bem para 1 ou 1.000.000 de usuários e, para ser econômico, é preciso que os respectivos recursos aumentem ou diminuam junto com esses usuários.

Para que os recursos do aplicativo possam aumentar ou diminuir, é necessário que ele tenha:

  • um meio que permita adicionar ou remover instâncias do serviço. Você também precisa decidir o momento de adicionar ou remover uma instância. Isso é resolvido com o Autoescalador do GCP.
  • um meio de armazenar dados com estado. Devido ao fluxo das instâncias, o armazenamento de dados com estado não é aconselhável. A arquitetura do aplicativo resolve isso para os dados relacionais, armazenando-os em uma instância separada do Cloud SQL, mas também precisa dar conta dos arquivos enviados pelo usuário. O Cloud Storage atende a esse requisito.

Nas próximas seções, mostraremos como usar o autoescalador para escalonar a infraestrutura que executa o aplicativo Redmine e como aproveitar o Cloud Storage para arquivos transferidos por upload.

Escalonar com o autoescalador

Com o fluxo e refluxo no uso do aplicativo, ele precisa ser capaz de ajustar dinamicamente os recursos necessários. Isso é resolvido com o Autoescalador do Compute Engine.

Com o aumento do tráfego ou da carga, o autoescalador adiciona recursos para lidar com a atividade extra e os remove quando o tráfego ou carga diminui. Isso ajuda a reduzir custos. Ele executa essas ações automaticamente, de acordo com as regras de escalonamento definidas e sem intervenção posterior de sua parte.

O impacto do autoescalador é duplo:

  1. Os usuários têm uma ótima experiência com o uso do aplicativo, porque sempre há recursos suficientes para atender à demanda.
  2. Você mantém um melhor controle sobre os custos, já que o autoescalador remove as instâncias quando a demanda cai abaixo de um limite especificado.

Ele faz o escalonamento automático do número de máquinas virtuais com base em uso da CPU, capacidade de veiculação ou uma métrica do Stackdriver Monitoring. A métrica de capacidade de veiculação é usada por essa solução para adicionar ou remover instâncias do Compute Engine, com base no número de solicitações por segundo (RPS) recebidas pelas instâncias a partir do balanceador de carga. Para saber mais, veja Processamento em lote com o autoescalador do Compute Engine.

Solicitações por segundo (RPS)

Nas seções anteriores, descrevemos um único serviço de back-end que identifica os grupos de instâncias que recebem o tráfego do balanceador de carga. Para cada grupo de instâncias associado ao serviço de back-end, este exemplo de solução também configura a balancingMode=RATE. Essa propriedade instrui o balanceador de carga a equilibrar com base no valor de RPS definido na propriedade maxRatePerInstance, que neste exemplo é 100. Essa configuração significa que o balanceador de carga tenta manter cada instância em 100 RPS ou menos. Veja a documentação dos serviços de back-end para saber mais sobre as propriedades de configuração.

Para escalonar em RPS, você precisa criar um autoescalador para cada grupo de instâncias que quer escalonar automaticamente. Neste exemplo, o grupo de instâncias é um recurso por zona. Portanto, para cada uma é preciso criar um autoescalador.

Um diagrama que mostra como um autoescalador se encaixa em uma arquitetura de aplicativo.

Cada autoescalador inclui uma propriedade utilizationTarget que define a fração da capacidade máxima de veiculação do balanceador de carga que ele precisa manter. Este exemplo configura a utilizationTarget de cada autoescalador como 80% da taxa máxima do serviço de back-end de 100 RPS para cada instância. Isso significa que o autoescalador será escalonado quando o RPS exceder 80% da taxa máxima por instância, ou seja, 80 RPS. Ele diminuirá a escala quando o RPS estiver abaixo desse limite.

Um fluxograma que mostra como o autoescalador determina se uma instância deve ser adicionada ou removida.

Cada autoescalador também define os números mínimo e máximo de instâncias que não serão violadas.

Observe que somente os grupos de instâncias gerenciadas oferecem recursos de escalonamento automático. Para saber mais, consulte Grupos de instâncias e Grupos de instâncias de dimensionamento automático.

Gerenciar uploads de arquivos

Parte da funcionalidade do aplicativo Redmine inclui permitir que os usuários façam upload e salvem arquivos quando conectados. O comportamento padrão do Redmine e de muitos outros aplicativos da Web semelhantes é armazenar esses arquivos diretamente no disco local. Essa abordagem serve quando você tem apenas um servidor com um mecanismo de backup bem definido. No entanto, essa não é a melhor abordagem quando se tem diversas instâncias do Compute Engine escalonadas automaticamente por trás de um balanceador de carga. Caso um usuário faça o upload de um arquivo, não haverá garantia de que a solicitação seguinte chegará na máquina em que os arquivos foram armazenados. Também não haverá garantia de que o autoescalador não encerre uma instância desnecessária que contenha arquivos.

A melhor solução é usar o Cloud Storage, que fornece um local centralizado perfeito para armazenar e acessar uploads de arquivos a partir de uma frota de servidores da Web escalonados automaticamente. O Cloud Storage também expõe uma API interoperável com os clientes do Amazon S3, tornando-a compatível com os plug-ins de aplicativos existentes para o S3, incluindo o plug-in Redmine S3, sem nenhuma modificação. Muitos aplicativos de terceiros e de código aberto têm plug-ins para oferecer compatibilidade com armazenamentos de objetos, como o Cloud Storage. Caso esteja criando o próprio aplicativo, use diretamente a API Cloud Storage para que haja compatibilidade com o armazenamento de arquivos.

Veja a seguir o fluxo para upload (setas azuis) e recuperação de arquivos (setas verdes) usando Redmine e Cloud Storage:

Um diagrama que mostra o fluxo de solicitações pelo aplicativo Redmine.

O processo mostrado no diagrama é o seguinte:

  1. O usuário POSTA o arquivo a partir de um navegador da Web.
  2. O balanceador de carga escolhe uma instância para lidar com a solicitação.
  3. O arquivo é armazenado pela instância no Cloud Storage.
  4. Os metadados de arquivo, como nome, proprietário e localização correspondente no Cloud Storage, são armazenados pela instância no banco de dados do Cloud SQL.
  5. Quando um arquivo é solicitado pelo usuário, é feito o streaming desse arquivo do Cloud Storage para uma instância.
  6. As instâncias enviam o stream pelo balanceador de carga.
  7. O arquivo é enviado ao usuário.

Storage capacity

Além de remover os uploads de arquivos com estado das instâncias do Compute Engine e permitir que sejam escalonados dinamicamente, o Cloud Storage oferece armazenamento durável e redundante para um número praticamente infinito de uploads de arquivos. Esta solução de armazenamento é resiliente, escalonável e econômica. Você paga apenas pelo armazenamento utilizado sem se preocupar com o planejamento da capacidade, e os dados são armazenados em diversas zonas de modo automático e redundante.

Custo

Até agora, a arquitetura de aplicativos descrita neste documento mostra como criar um aplicativo resiliente e escalonável usando o GCP. No entanto, não basta criar um aplicativo, ele precisa ser feito com o melhor custo-benefício possível.

Nesta seção, demonstramos como essa arquitetura não é apenas resiliente e escalonável, mas também muito econômica. Ela começa com algumas premissas gerais sobre intensidade e frequência de uso do aplicativo, transformando-as em uma estimativa básica de custos. Lembre-se de que essas premissas não passam disso: premissas. Fique à vontade para ajustar esses números conforme necessário para criar uma estimativa de custo que corresponda melhor ao uso antecipado dos aplicativos.

Computação

Uma grande preocupação em qualquer arquitetura de aplicativo é o custo para manter os servidores em execução. Esta análise de custos usa as seguintes premissas:

Métrica Valor
Média de visualizações de página por mês 20.000.000
Média de solicitações HTTP por mês 120.000.000
Horas de pico (90% ou mais) de uso Das 7h às 18h, de segunda a sexta
Transferência de dados por visualização de página 100 KB
Horas de pico por mês 220
Taxa de solicitação durante as horas de pico 127 solicitações/segundo (RPS)
Taxa de solicitação fora das horas de pico 6 solicitações/segundo (RPS)

Com base nessas premissas, descubra o número de visualizações de página recebidas pelo aplicativo nas horas de pico entre 7h às 18h, de segunda a sexta de cada mês:

20.000.000 visualizações/mês * 6 solicitações/visualizações * 90% de ocorrências em horários de pico = 108.000.000

Em média, há 22 dias úteis por mês. Em um dia útil com 11 horas de pico, é preciso fornecer recursos de computação suficientes para lidar com 242 horas de pico por mês.

Em seguida, você precisa descobrir que tipo de instância do Compute Engine é capaz de lidar com esse tipo de tráfego. Esta arquitetura de aplicativo foi testada usando gatling.io em testes básicos de carga. Os resultados desses testes determinaram que 4 instâncias do Compute Engine do tipo n1-standard-1 são suficientes.

Fora dos horários de pico, esta solução tem no mínimo 2 instâncias de n1-standard-1 em execução.

Para saber o custo de execução dessas instâncias, confira as estimativas de preço mais recentes na calculadora de preços do Google Cloud Platform. Durante essa conferência, observe que, em ambos os casos, essas instâncias se qualificam automaticamente para descontos por uso prolongado.

Balanceamento de carga e transferência de dados

Este aplicativo provisionou um balanceador de carga com uma única Regra de encaminhamento, que é o endereço IP público a que os usuários se conectam. Essa regra de encaminhamento é faturada por hora.

Para estimativas de transferência de dados, pense primeiro no que seria o pior dos casos. O balanceador de carga cobra tanto pelos dados de entrada quanto pelo tráfego de saída processados por ele. Supõe-se que 99,5% dos 120.000.000 de solicitações HTTP são de usuários carregando uma página de projeto do Redmine. O carregamento de 1 página conta como uma solicitação HTTP GET, o que faz com que 5 outras solicitações HTTP GET carreguem outros recursos, como CSS, imagens e jQuery. Carregar uma página inteira envolve 6 solicitações HTTP. Isso resulta em:

  • aproximadamente 20.000.000 páginas completamente carregadas por mês;
  • cerca de 10 KB de dados de entrada processados e 450 KB de transferência de dados por página;
  • um total aproximado de 214 GB de dados processados pelo balanceador de carga por mês e 9.091 GB de tráfego de saída.

Os outros 0,5% dos 20.000.000 de solicitações HTTP são solicitações HTTP POST para fazer upload de um arquivo de tamanho médio (cerca de 0,5 MB), para mais 500 GB de dados processados mensalmente.

Essa estimativa da Calculadora de preços do Google Cloud Platform mostra uma previsão de custo para 714 GB de transferência de dados gerados pelo balanceador de carga, além de um tráfego de saída de 9.091 GB para esse cenário.

Esse foi o pior cenário para uma estimativa de transferência de dados, por estar veiculando todo o conteúdo, inclusive os recursos estáticos, em cada solicitação de uma instância do Compute Engine e por meio de um balanceador de carga, sem o benefício de armazenamento em cache ou CDN. Do payload estimado em 450 KB para cada carregamento de página, lembrando que esta solução baseia-se em carregamentos acima de 20 milhões por mês, são necessários 333 KB para carregar o jQuery. Para reduzir os custos de transferência de dados em 73%, basta atualizar uma linha do aplicativo para carregar o jQuery a partir das bibliotecas hospedadas pelo Google.

Com essa estimativa de preços atualizada, você visualiza a economia na transferência de dados conseguida com a troca para as bibliotecas hospedadas do Google.

Storage

Esta solução usa o Cloud Storage para todos os arquivos transferidos por upload pelo aplicativo Redmine. Conforme descrito na seção anterior, cerca de 0,5% desse uso refere-se ao upload de arquivos, sendo que o tamanho em média de cada arquivo é de aproximadamente 0,5 MB. Isso significa que é esperado 1.000.000 de novos uploads de arquivos por mês, resultando em 500 GB de armazenamento novo mensal. Esta solução também estima 1.000.000 de operações HTTP PUT por mês para armazenamento de novos arquivos, cobradas como operação de classe A.

Com essa estimativa de preços da Calculadora de preços do Google Cloud Platform, você visualiza o custo previsto para o uso do Cloud Storage.

Esta arquitetura usa o Cloud SQL para armazenar todos os dados relacionais do aplicativo. Com base nas métricas de exemplo descritas anteriormente, o tipo de banco de dados D2 com 1.024 MB de RAM oferece capacidade suficiente para a carga de trabalho do aplicativo e será executado 24 horas por dia, 7 dias por semana. Como esse banco de dados provavelmente terá uso intenso, escolha a opção "Heavy" para as operações de E/S na calculadora. Foi realizado um teste para esta arquitetura de exemplo com a inserção de 100.000 documentos. Os resultados determinaram que um disco de 50 GB aceita mais de 100.000.000 de documentos, permitindo que o banco de dados aceite mais de 8 anos de uso na taxa descrita.

Veja a seguir uma estimativa feita pela Calculadora de preços do Google Cloud Platform, mostrando o custo previsto dessa parte dos custos de arquitetura.

Implantar a solução de exemplo

Para implantar o aplicativo de exemplo descrito nesta solução, visite o repositório do GitHub Aplicativo da Web escalonável e resiliente no Google Cloud Platform.

Apêndice: adicionar uma nova instância

Como parte dos esforços na criação de uma arquitetura de aplicativo resiliente e escalonável, é preciso decidir sobre o método de adição de novas instâncias. Determine de forma específica, como configurar automaticamente novas instâncias à medida que elas ficam on-line.

Nesta seção, você verá algumas opções disponíveis.

Bootstrap da instalação de software

Para veicular a solicitação da Web de um usuário, cada instância precisa de outro software instalado no topo do sistema operacional básico, junto com os dados de configuração, que incluem dados como informações de conexão do banco de dados, o nome do intervalo do Cloud Storage em que serão armazenados os arquivos e assim por diante. Ao imaginar esses componentes como camadas, é possível visualizar toda a pilha a ser executada em cada instância:

Um diagrama que mostra como é o empilhamento de software em uma instância.

Esta solução usa um modelo de instância, que especifica a imagem do Compute Engine usada pelas instâncias usam quando são iniciadas. Especificamente, esta solução usa a imagem do Ubuntu 14.10, desenvolvida e mantida pela Canonical. Por ser a imagem do sistema operacional de base, ela não inclui configurações ou softwares específicos exigidos pelo aplicativo.

Para que o restante da pilha seja instalado automaticamente quando uma nova instância for iniciada, use uma combinação de scripts de inicialização do Compute Engine e Chef Solo para executar o bootstrap no momento da inicialização. Especifique um script de inicialização adicionando um item de atributo de metadados startup-script a um modelo de instância. Um script de inicialização é executado quando a instância é inicializada.

Esse script de inicialização:

  1. instala o cliente do Chef;
  2. faz o download de um arquivo especial do "Chef" denominado node.json. Esse arquivo informa ao "Chef" a configuração específica a ser executada para esta instância;
  3. executa o "Chef" e o deixa cuidar da configuração detalhada.

Veja aqui o script de inicialização completo:

#! /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/...

Observação: esta solução não aborda o funcionamento do Chef.Todas as receitas e os manuais são de código aberto e estão disponíveis no repositório Scalable and Resilient Web Application on GCP.

Fornecer a configuração do aplicativo

Após uma nova instância ser inicializada e configurada usando o script de inicialização e o "Chef", será preciso receber algumas informações para que comece a atender às solicitações. Neste exemplo, cada instância precisa conhecer as informações de conexão do banco de dados, como o nome do host, nome de usuário e senha, bem como o nome do intervalo do Cloud Storage a ser usado e as credenciais para conexão.

Cada instância do Compute Engine dispõe de atributos de metadados associados a ele a serem definidos por você. Você aprendeu anteriormente sobre a adição do atributo especial de metadados startup-script, mas também é possível adicionar pares chave=valor arbitrários. Aqui, é possível especificar atributos no modelo de instância para incluir os dados de configuração necessárias para que as instâncias se conectem ao banco de dados e ao intervalo do Cloud Storage.

Veja como são os metadados de um modelo de instância do Console do GCP:

Captura de tela do Console do Google Cloud Platform mostrando as informações personalizadas de metadados referentes a um modelo de instância.

Uma ferramenta chamada Ohai é usada pelo "Chef" para analisar esses bits de informações de configuração a partir dos metadados da instância, preenchendo modelos para criar arquivos de configuração necessários para o aplicativo. Veja a seguir o modelo que cria o arquivo database.yaml, contendo as informações de conexão do banco de dados e acessando automaticamente os itens de metadados apropriados:

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'] %>
...

Além disso, é possível acessar manualmente os valores de metadados de dentro de uma instância usando o serviço de metadados local. Aqui, use curl para recuperar a senha do banco de dados:

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

Considerações sobre desempenho e dependência

A abordagem de bootstrapping usada nesta solução envolve o uso inicial de uma imagem padrão do sistema operacional, a instalação de todos os softwares no momento da inicialização com o "Chef" e a utilização de metadados da instância para fornecer dados de configuração do aplicativo.

Um diagrama que mostra como é o empilhamento de software em uma instância. Esta pilha mostra como alguns softwares são empacotados com a imagem, uns são instalados na inicialização e outros são fornecidos após a inicialização.

Nessa abordagem, a configuração do sistema é especificada em um manual do "Chef" é uma vantagem. É possível controlar a versão deste manual, compartilhá-lo e usá-lo para provisionar localmente as máquinas virtuais para teste, usando Vagrant ou Docker. Além disso, é possível configurar servidores no data center ou com diferentes provedores da nuvem. O gerenciamento de imagens também é simplificado. No caso deste aplicativo de exemplo, basta rastrear a imagem do SO de base usada pelo aplicativo.

Entre as desvantagens, estão os tempos de inicialização potencialmente lentos devido ao download e à instalação de todos os softwares. Em alguns casos, ainda é necessário realizar a compilação. Também é importante levar em consideração as dependências introduzidas por esse método. Neste exemplo, o "Chef" instalou alguns pacotes apt, do Rubygems e do GitHub. Caso algum desses repositórios esteja indisponível no momento da inicialização de uma nova instância, a configuração dela falhará.

Imagens personalizadas e bootstrapping

Como é possível criar suas próprias imagens personalizadas com o Compute Engine, a instalação de tudo no momento da inicialização não é a única abordagem para o bootstrap. Por exemplo, é possível:

  1. iniciar uma imagem de base do Ubuntu 14.10;
  2. instalar tudo, exceto o app Redmine (Ruby, nginx etc.);
  3. criar uma imagem a partir do resultado;
  4. usar essa imagem no modelo de instância.

Agora, quando uma nova instância é iniciada, ela só precisa instalar o Redmine. O tempo de inicialização é melhorado e o número de dependências de pacotes externos é reduzido.

Um diagrama que mostra uma pilha de instâncias com tudo instalado na imagem, exceto o Redmine.

É possível ampliar a abordagem da imagem personalizada e colocar absolutamente tudo em uma imagem, incluindo todas as dependências, o código-fonte do aplicativo e a configuração. As vantagens são o tempo de inicialização mais rápido e a eliminação das dependências externas. No entanto, para fazer qualquer alteração no aplicativo, será preciso criar uma nova imagem e atualizar o modelo de instância.

Um diagrama que mostra uma pilha de instâncias com todos os softwares empacotados com a imagem.

Pense nas abordagens de bootstraping de uma instância como uma progressão. Quanto maior o número de configurações no momento da inicialização, mais lenta ela será. Porém, você terá menos imagens para gerenciar. Quanto maior for o número de configurações inseridas na imagem personalizada, mais rápida será a inicialização. Além disso, o número de dependências diminuirá, mas você terá potencialmente mais imagens para gerenciar. Para a maioria dos clientes, a abordagem certa é o meio termo. Escolha aquela que fizer sentido para você e para o aplicativo.

Um diagrama que mostra progressivamente as maneiras de instalar o software em uma instância. O intervalo varia entre ter tudo instalado após a inicialização e ter tudo empacotado com a imagem.
Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…