Como criar aplicativos escalonáveis e resilientes

A criação de aplicativos da Web resilientes e escalonáveis é essencial em qualquer arquitetura de aplicativo. Um aplicativo bem projetado precisa ser escalonado sem problemas conforme a demanda aumenta e diminui e ser resiliente o suficiente 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ê aprende a usar o Google Cloud Platform (GCP) para criar arquiteturas de aplicativos escalonáveis e resilientes usando padrões e práticas que se aplicam amplamente a qualquer aplicativo. Você também aprenderá 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 Como 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 que são escalonáveis e resilientes. É possível usar serviços como o Compute Engine e o autoescalador para ajustar os recursos do seu aplicativo conforme a demanda exigir. 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 uma visão geral das opções de hospedagem na Web do GCP, consulte Como exibir sites.

Como definir escalonabilidade 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 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. Com a adição e remoção de 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 altamente disponível ou resiliente é aquele que continua a funcionar mesmo com 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 o tráfego para servidores que processem melhor as solicitações;
  • hospedagem de máquinas virtuais em diversas regiões;
  • configuração de uma solução de armazenamento robusta.

GCP: flexível e econômico

As arquiteturas tradicionais compatíveis com a escalonabilidade e a resiliência muitas vezes exigem grandes investimentos em recursos. Com soluções locais, escalonabilidade 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 escalonabilidade e resiliência à arquitetura. Além disso, o GCP oferece esses serviços usando uma estrutura de preços que pode ser controlada.

Como criar arquiteturas resilientes e escalonáveis com o GCP

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 GCP
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 escalonável e resiliente. O papel desempenhado por cada componente é descrito em detalhes na próxima seção.

Um diagrama que mostra um aplicativo escaloná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 pode estar associado a um registro 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. Na zona, as solicitações são distribuídas uniformemente pelas instâncias no grupo. Com o balanceador de carga HTTP, você equilibra o tráfego em diversas regiões. No entanto, ele é usado pelo exemplo de solução 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 grande 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.

Instância

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 implantação, use scripts de inicialização e o Chef para configurar instâncias com o servidor de aplicativos e o código referente ao aplicativo.

Modelo de instância

Um modelo de instância define um tipo de máquina, imagem, zona, rótulos e outras propriedades dela. Use um modelo de instância 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 por meio da 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. Nesta solução, usamos um intervalo do Cloud Storage na distribuição de chaves SSL privadas para as instâncias do Compute Engine escalonáveis, bem como para armazenar todos os arquivos enviados ao aplicativo Redmine. Isso 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 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 processar as solicitações automaticamente.

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

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

Neste cenário, usamos 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, consulte Apêndice: como adicionar uma nova instância, no fim 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

Neste ponto, o aplicativo de exemplo tem quase todas as ferramentas necessárias para ser resiliente. No entanto, falta 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 em que a verificação de cada instância precisa ser executada. 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 recebem o tráfego de um balanceador de carga. O serviço de back-end especifica a porta e o protocolo expostos pelas instâncias, como porta HTTP 80, além da verificação de integridade HTTP a ser usada nas instâncias dentro dos grupos.

Veja no diagrama a seguir 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 de 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.

Com o Cloud SQL é possível escolher 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 termos de 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 no momento em que são armazenadas em cache localmente, mas antes que os dados sejam copiados para outros locais. Ela gera gravações mais rápidas no banco de dados, já que não é preciso aguardar a conclusão da replicação. No entanto, é possível perder as atualizações mais recentes no caso improvável de falha no sistema do data center alguns segundos após a atualização do banco de dados.

O aplicativo Redmine, adotado nesta solução, usa 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 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 precisa funcionar bem para 1 ou 1.000.000 de usuários e, para ser econômico, é preciso que os recursos dele 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. Também é preciso 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 seções a seguir, veja como usar o autoescalador para escalonar a infraestrutura que executa o aplicativo Redmine e como aproveitar o Cloud Storage para arquivos enviados.

Escalonar com o autoescalador

Com o fluxo e refluxo no uso do aplicativo, ele precisa 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 da 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 controle melhor sobre os custos, já que o autoescalador remove as instâncias quando a demanda cai abaixo de um limite especificado.

O autoescalador pode escalonar o número de máquinas virtuais com base na utilização da CPU, na capacidade de exibição ou em uma métrica do Stackdriver Monitoring. A métrica de capacidade de exibiçã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, na sigla em inglês) recebidas pelas instâncias com o balanceador de carga. Para saber mais, consulte 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 define balancingMode=RATE. Essa propriedade instrui o balanceador de carga a balancear 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 abaixo disso. Para saber mais sobre as propriedades de configuração de um serviço de back-end,
consulte a documentação de serviços de back-end.

Para escalonar em RPS, é preciso criar um autoescalador para cada grupo de instâncias que você 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 exibição do balanceador de carga que ele mantém. Este exemplo define a utilizationTarget de cada autoescalador em 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 ultrapassar 80% da taxa máxima por instância, ou seja, 80 RPS. O autoescalador diminuirá a escala quando o RPS cair abaixo desse limite.

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

Cada autoescalador também define um número mínimo e máximo de instâncias que não pode violar.

Observe que somente os grupos de instâncias gerenciadas oferecem recursos de escalonamento automático. Para mais informações, consulte Grupos de instâncias e Como fazer escalonamento automático de grupos de instâncias.

Gerenciar uploads de arquivos

Parte da funcionalidade do aplicativo Redmine inclui permitir que usuários façam upload e salvem arquivos depois de terem feito o login. O comportamento padrão do Redmine e de muitos outros aplicativos 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 você tem várias instâncias do Compute Engine escalonadas automaticamente atrá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 estão 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 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 você esteja criando o próprio aplicativo, use diretamente a API Cloud Storage para que haja compatibilidade com o armazenamento de arquivos.

No diagrama a seguir, veja o fluxo de upload (setas azuis) e recuperação (setas verdes) de arquivos com o Redmine e o 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 local 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.

Capacidade de armazenamento

Além de remover os uploads de arquivos com estado das instâncias do Compute Engine e deixá-los escalonar de modo dinâmico, 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 e 18h, de segunda a sexta de cada mês:

120.000.000 (solicitações por mês) * 90% (ocorrências em horários de pico) = 108,000,000 (solicitações em horários de pico por mês)

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, é preciso 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 quatro 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 duas 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 GCP. 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% das 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.

Esta estimativa da calculadora de preços do GCP 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 esta estimativa de preços atualizada, você visualiza a economia na transferência de dados conseguida com a troca para as bibliotecas hospedadas do Google.

Armazenamento

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 são esperados 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 esta estimativa de preços da calculadora de preços do GCP, 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 nas operações de E/S da 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 aqui uma estimativa da calculadora de preços do GCP que mostra o custo previsto para essa parte dos custos de arquitetura.

Como implantar a solução de exemplo

Para implantar o aplicativo de exemplo descrito nesta solução, visite o repositório do GitHub Aplicativos escalonáveis e resilientes no GCP (em inglês).

Apêndice: como 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 maneira 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 exibir a solicitação da Web de um usuário, cada instância precisa de outro software instalado sobre o sistema operacional básico, junto com os dados de configuração. Os dados de configuração incluem as informações de conexão do banco de dados e o nome do intervalo do Cloud Storage em que serão armazenados os arquivos. 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.

Nesta solução, usamos um modelo de instância, que especifica a imagem do Compute Engine que as instâncias usam ao serem iniciadas. Especificamente, esta solução usa a imagem do Ubuntu 14.10, desenvolvida e mantida pela Canonical. Como essa é a imagem do sistema operacional básico, ela não inclui nenhum software ou configuração exigido(a) 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 a ser executada para esta instância;
  3. executa o Chef e deixa-o 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/...

Como 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, ela precisa receber algumas informações para começar a atender às solicitações. Neste exemplo, cada instância precisa saber as informações de conexão do banco de dados, como o nome do host, o nome de usuário e a senha, além do nome do intervalo do Cloud Storage a ser usado e das credenciais com as quais se conectar.

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 de chave-valor arbitrários. Aqui, é possível especificar atributos no modelo de instância para incluir os dados de configuração necessários 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.

O Chef usa uma ferramenta denominada Ohai para analisar esses bits de informações de configuração nos metadados da instância. Ela faz isso preenchendo modelos para criar arquivos de configuração necessários ao aplicativo. O modelo a seguir cria o arquivo database.yaml, que contém as informações de conexão do banco de dados. Ele faz isso acessando automaticamente os devidos itens de metadados:

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.

Uma vantagem dessa abordagem é que a configuração do sistema é especificada em um manual do Chef. É 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 do 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.

A seguir

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…