Previsão: Nublado

Dimensionamento com base em desempenho no Windows Azure

Joseph Fultz

Baixar o código de exemplo

Sem dúvida, a computação em nuvem está conquistando cada vez mais notoriedade, e seu uso prático tem ganhado força entre as plataformas de tecnologia e em todo o setor. A computação em nuvem não é um conceito novo ou revolucionário; na verdade, ela já existe há anos na forma de hospedagem compartilhada e outros serviços desse tipo. Agora, no entanto, os avanços da tecnologia e anos de experiência executando servidores e serviços tornaram a computação em nuvem não só tecnicamente prática, mas cada vez mais interessante para clientes e provedores.

O progresso da computação em nuvem irá além da TI e alcançará cada parte da sua empresa — desde as pessoas que gerenciam hardware e serviços até os desenvolvedores e arquitetos, e os executivos que aprovarão o orçamento e pagarão as contas. É melhor se preparar para isso.

Nesta coluna, falarei principalmente para os desenvolvedores e arquitetos que precisam entender e aproveitar a computação em nuvem no seu trabalho. Darei algumas orientações sobre como realizar uma determinada tarefa, incluindo observações a respeito de considerações de arquitetura e seu impacto no custo e no desempenho. Dê-me sua opinião sobre os tópicos que abordo e, ainda mais importante, sobre os tópicos que são de especial interesse na computação em nuvem.

Espalhando a nuvem

Um dos primeiros benefícios da computação em nuvem pelo qual as pessoas se interessam é a ideia de que os proprietários de aplicativos não precisam se preocupar com a instalação, a configuração ou a manutenção da infraestrutura. Sejamos honestos: isso é muito interessante.

Porém, acho que é mais importante nos concentrarmos na capacidade de dimensionamento para atender às necessidades do proprietário do aplicativo, criando assim um modelo de custo mais eficiente sem sacrificar o desempenho ou desperdiçar recursos. Pela minha experiência, a elasticidade da demanda é um assunto abordado em qualquer conversa sobre a nuvem, independentemente da plataforma discutida.

Nesta edição, demonstrarei como usar os dados de contador de desempenho da execução de funções para automatizar o processo de redução ou ampliação do número de instâncias de uma determinada Web Role (função Web). Para isso, analisarei um amplo grupo representativo de recursos e funcionalidades do Windows Azure, como Computação do Windows Azure, Armazenamento do Windows Azure e a API de Gerenciamento REST.

O conceito é bastante simples: testar dados de desempenho coletados com base em um limite e, depois, aumentar ou diminuir devidamente o número de instâncias. Não entrarei em detalhes sobre como coletar dados de diagnóstico; deixarei isso para você ou um futuro artigo. Em vez disso, vou examinar dados de contador de desempenho que foram colocados em uma tabela no Armazenamento do Windows Azure, bem como o código e a instalação necessários para executar a chamada REST a fim de alterar a contagem de instâncias na configuração. Além disso, o código de exemplo para download conterá uma página simples que fará com que as chamadas de gerenciamento REST forcem a alteração da contagem de instâncias com base na entrada do usuário. O cenário é parecido com o desenho na Figura 1.

image: Performance-Based Scaling

Figura 1 Dimensionamento com base em desempenho

Definição do projeto

Para começar, criei um projeto do Serviço na Nuvem do Windows Azure que contém uma Worker Role (função de trabalho) e uma Web Role. Configurei a Web Role para publicar dados de contador de desempenho (mais especificamente % Tempo do Processador) da função e enviá-los ao armazenamento a cada 20 segundos. O código para obter isso reside no método WebRole::OnStart e é semelhante a este:

var performanceConfiguration = 
  new PerformanceCounterConfiguration();
performanceConfiguration.CounterSpecifier = 
  @"\Processor(_Total)\% Processor Time";
performanceConfiguration.SampleRate = 
  System.TimeSpan.FromSeconds(1.0);
            
// Add the new performance counter to the configuration 
config.PerformanceCounters.DataSources.Add(
  performanceConfiguration);
config.PerformanceCounters.ScheduledTransferPeriod = 
  System.TimeSpan.FromSeconds(20.0);

Este código registra o contador de desempenho, define o intervalo de coleta de dados e envia os dados para o armazenamento. Os valores de intervalo que usei funcionam bem para este exemplo, mas não são representativos dos valores que eu usaria em um sistema de produção. Em um sistema de produção, o intervalo de coleta seria bem maior porque eu estaria preocupado com as operações 24x7. Além disso, o intervalo de envio para o armazenamento seria maior para reduzir o número de transações no Armazenamento do Windows Azure.

Em seguida, criei um certificado autoassinado que posso usar para fazer as chamadas da API de Gerenciamento REST do Azure. Toda solicitação terá de ser autenticada, e o certificado é o meio para isso. Segui as instruções de como criar um certificado autoassinado descritas no artigo da Biblioteca TechNet “Criar um certificado de servidor autoassinado no IIS 7” (technet.microsoft.com/library/cc753127(WS.10)). Exportei um arquivo .cer e um arquivo .pfx. O arquivo .cer será usado para assinar as solicitações enviadas para a API de gerenciamento, e o arquivo .pfx será importado para a função de computação via interface de gerenciamento (veja a Figura 2).

image: Importing Certificates

Figura 2 Importando certificados

Voltarei mais tarde para pegar a impressão digital e colocá-la nas configurações das Web Roles e Worker Roles que estou criando, assim elas poderão acessar o repositório de certificados e recuperar o certificado.

Por último, para que isso funcione no Windows Azure, preciso de um projeto de computação em que eu possa publicar as duas funções e de um projeto de armazenamento para o qual transferir os dados de desempenho. Com esses elementos disponíveis, posso passar para a melhor parte do trabalho.

Está funcionando bem ou mal?

Agora que configurei a Web Role e adicionei o código para publicar os dados de contador de desempenho, a próxima etapa é buscar esses dados e compará-los com um valor limite. Criarei um método TestPerfData pelo qual recupero os dados da tabela e testo os valores. Vou criar uma instrução LINQ parecida com o seguinte:

double AvgCPU = (
  from d in selectedData
  where d.CounterName == 
    @"\Processor(_Total)\% Processor Time"
  select d.CounterValue).Average();

Comparando a média de utilização, posso determinar o desempenho atual do aplicativo. Se as instâncias estão funcionando muito bem, posso adicionar instâncias. Se elas estão funcionando mal e estou desperdiçando recursos (ou seja, dinheiro) porque há instâncias desnecessárias em execução, posso reduzir o número de instâncias.

Você encontrará uma análise detalhada do código e da instalação necessários para acessar os dados da tabela de contador de desempenho em uma postagem do blog que escrevi em blogs.msdn.com/b/joseph_fultz/archive/2010/06/30/querying-azure-perf-counter-data-with-linq.aspx. Eu uso um bloco if-then-else simples para avaliar o estado e determinar a ação desejada. Falarei sobre os detalhes depois que criar as funções necessárias para alterar a configuração do serviço em execução.

Usando a API de Gerenciamento REST

Para concluir o método TestPerfData, tenho mais um pouco de trabalho pela frente. Preciso de alguns métodos que me ajudem a descobrir o número de instâncias de uma dada função, criem uma nova configuração de serviço válida para essa função com uma contagem de instâncias ajustada e, por último, permitam atualizar a configuração.

Para isso, adicionei um arquivo de classe ao meu projeto e criei os seis métodos estáticos mostrados na Figura 3.

Figura 3 Métodos de configuração

Método Descrição
GetDeploymentInfo Recupera a configuração de implantação, inclusive a configuração de serviço codificada.
GetServiceConfig Recupera e decodifica a configuração de serviço com base nas informações de implantação.
GetInstanceCount Busca a contagem de instâncias de uma função especificada.
ChangeInstanceCount Atualiza a configuração de serviço e retorna o XML completo.
ChangeConfigFile Atualiza a configuração de serviço com a configuração de serviço fornecida para a função.
LookupCertificate Passa a configuração de ambiente que contém a impressão digital e recupera o certificado no repositório de certificados.

As chamadas que interagem com a API de Gerenciamento REST devem incluir um certificado. Para isso, o certificado é adicionado ao serviço hospedado e a impressão digital é adicionada à configuração da função e usada para buscar o certificado em tempo de execução. Depois que o serviço e a função estão configurados corretamente, uso o seguinte código para obter o certificado do Repositório de Certificados:

string Thumbprint = 
  RoleEnvironment.GetConfigurationSettingValue(
  ThumbprintSettingName);
X509Store certificateStore = 
  new X509Store(StoreName.My, StoreLocation.LocalMachine);
certificateStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs = 
  certificateStore.Certificates.Find(
  X509FindType.FindByThumbprint, Thumbprint, false);

Esse é o código principal do método LookUpCertificate e é chamado nos métodos em que desejo interagir com a API REST. Vou recuperar a função GetDeploymentInfo para mostrar um exemplo de como as chamadas são construídas. Para este exemplo, inseri no código algumas das variáveis necessárias para acessar a API REST:

string x_ms_version = "2009-10-01";
string SubscriptionID = "[your subscription ID]";
string ServiceName = "[your service name]";
string DeploymentSlot = "Production";

Preciso criar um HttpWebRequest com o URI apropriado, definir os cabeçalhos de solicitação e adicionar meu certificado. Aqui eu criei a cadeia de caracteres do URI e a usei para criar um novo objeto HttpWebRequest:

string RequestUri = "https://management.core.windows.net/" + 
  SubscriptionID + "/services/hostedservices/"+ 
  ServiceName + "/deploymentslots/" + DeploymentSlot;

HttpWebRequest RestRequest = 
  (HttpWebRequest)HttpWebRequest.Create(RequestUri);

Para que a chamada seja válida, deve incluir a versão no cabeçalho. Por isso, crio uma coleção nome-valor, adiciono a chave e os dados da versão e adiciono isso à coleção de cabeçalhos de solicitação:

NameValueCollection RequestHeaders = 
  new NameValueCollection();
RequestHeaders.Add("x-ms-version", x_ms_version);
if (RequestHeaders != null) {
  RestRequest.Headers.Add(RequestHeaders);
}

A última coisa a fazer para preparar essa solicitação específica é adicionar o certificado:

X509Certificate cert = LookupCertificate("RESTMgmtCert");
RestRequest.ClientCertificates.Add(cert);

Por último, executo a solicitação e leio a resposta:

RestResponse = RestRequest.GetResponse();
using (StreamReader RestResponseStream = new StreamReader(RestResponse.GetResponseStream(), true)) {
  ResponseBody = RestResponseStream.ReadToEnd();
  RestResponseStream.Close();
}

Esse é o padrão geral que usei para construir solicitações feitas à API de Gerenciamento REST. A função GetServiceConfig extrai a Configuração de Serviço da configuração de implantação, usando LINQ para instruções XML como a seguinte:

XElement DeploymentInfo = XElement.Parse(DeploymentInfoXML);
string EncodedServiceConfig = 
  (from element in DeploymentInfo.Elements()
where element.Name.LocalName.Trim().ToLower() == "configuration"
select (string) element.Value).Single();

No meu código, o retorno de GetServiceConfig é passado para as funções de contagem GetInstanceCount ou ChangeInstance (ou ambas) para extrair ou atualizar as informações. O retorno da função ChangeInstance é uma Configuração de Serviço atualizada, que é passada para ChangeConfigFile. Por sua vez, ChangeConfigFile envia a atualização para o serviço construindo uma solicitação semelhante à usada para buscar as informações de implantação, com as seguintes diferenças importantes:

  1. “/?comp=config” é adicionado ao final do URI
  2. O verbo PUT é usado no lugar de GET
  3. A configuração atualizada é transmitida como o corpo da solicitação

Unindo todos os elementos

Com as funções em vigor para procurar e alterar a configuração de serviço, e tendo feito o restante do trabalho preparatório, como configurar contadores, definir as configurações da cadeia de conexão para o Armazenamento e instalar certificados, chegou a hora de implementar o teste de limite de CPU.

O modelo do Visual Studio produz uma Worker Role que desperta a cada 10 segundos para executar o código. Para simplificar, estou ignorando isso e adicionando um único temporizador que executará a cada cinco minutos. No temporizador, uma instrução condicional simples testa se a utilização é superior ou inferior a 85%, e vou criar duas instâncias da Web Role. Desse modo, asseguro que o número de instâncias definitivamente diminuirá das duas instâncias iniciais para uma única.

Na Worker Role, tenho um método Run que declara e instancia o temporizador. Dentro do identificador de tempo decorrido, adiciono uma chamada à função TestPerfData que criei anteriormente. Para este exemplo, estou ignorando a implementação da condição greater-than porque sei que a utilização de CPU não será tão alta assim. Defino a condição less-than para menos de 85%, pois tenho certeza de que a média do contador será inferior a isso. Definir essas condições trabalhadas me permitirá ver a alteração por meio do console de gerenciamento da Web ou do Gerenciador de Servidores no Visual Studio.

No bloco less-than-85-percent, verifico a contagem de instâncias, modifico a configuração de serviço e atualizo a configuração de serviço em execução, conforme mostrado na Figura 4.

Figura 4 O bloco less-than-85-percent

else if (AvgCPU < 85.0) {
  Trace.TraceInformation("in the AvgCPU < 25 test.");
  string deploymentInfo = 
    AzureRESTMgmtHelper.GetDeploymentInfo();
  string svcconfig = 
    AzureRESTMgmtHelper.GetServiceConfig(deploymentInfo);
  int InstanceCount = 
    System.Convert.ToInt32(
    AzureRESTMgmtHelper.GetInstanceCount(
    svcconfig, "WebRole1"));
  if (InstanceCount > 1) {
    InstanceCount--;
    string UpdatedSvcConfig = 
      AzureRESTMgmtHelper.ChangeInstanceCount(
      svcconfig, "WebRole1", InstanceCount.ToString());
    AzureRESTMgmtHelper.ChangeConfigFile(UpdatedSvcConfig);
  }
}

Verifico a contagem de instâncias antes de ajustá-la para baixo, porque não quero que chegue a zero, que não é uma configuração válida e causaria erro.

Execução da amostra

Agora estou pronto para executar o exemplo e demonstrar a elasticidade no Windows Azure. Sabendo que meu código sempre está certo de primeira — cof cof — clico com o botão direito do mouse no Projeto do Serviço em Nuvem e escolho Publicar. A caixa de diálogo dá a opção de configurar suas credenciais, o que eu já havia feito (veja a Figura 5).

image: Publishing the Project

Figure 5 Publicando o projeto

Clico em OK e aguardo o pacote ser copiado e implantado. Quando a implantação é concluída, volto para o console de gerenciamento da Web e vejo as duas Web Roles e uma Worker Role em execução, conforme ilustrado na Figura 6.

image: Two Web Roles and One Worker Role

Figura 6 Duas Web Roles e uma Worker Role

Aguardo a ativação do evento de temporizador, executando o código que determinará se a média de utilização de CPU é inferior a 85%, e diminuo a contagem da instância de WebRole1. Quando isso acontecer, a página de gerenciamento será atualizada para refletir uma atualização na implantação.

Como estou usando VMs pequenas, alterando a contagem em apenas um, e o aplicativo é leve (uma página .aspx), a atualização não demora e vejo a implantação final autorreduzida mostrada na Figura 7.

image: Now One Web Role and One Worker Role

Figura 7 Agora uma Web Role e uma Worker Role

Céu azul

Quero compartilhar algumas reflexões finais sobre o exemplo no contexto de considerar uma implementação real. Existem alguns pontos importantes a considerar.

Primeiro, o teste é comum e trabalhado. Em uma implementação real, você precisaria avaliar mais de uma utilização de CPU simples e deverá considerar a fração em que ocorreu a coleta.

Além disso, é necessário avaliar os custos de uso do Armazenamento do Windows Azure. Dependendo da solução, pode ser recomendável apagar os registros da tabela e deixar somente os que são de interesse. Você pode diminuir o intervalo de carregamento para reduzir os custos da transação ou pode transferir os dados para o SQL Azure a fim de minimizar esse custo.

Também é necessário considerar o que acontece durante uma atualização. Uma atualização direta fará com que os usuários percam conectividade. Talvez seja melhor preparar as novas instâncias e trocar o endereço IP virtual. Porém, em qualquer um dos casos, você terá problemas de sessão e viewstate. Uma solução melhor é ficar sem monitoração de estado e desativar o teste durante ajustes de escala.

E isso é tudo quanto à minha implementação de elasticidade no Windows Azure. Baixe o código de exemplo e comece a brincar com ele hoje mesmo.

Joseph Fultz  é arquiteto do Microsoft Technology Center em Dallas, onde trabalha com clientes empresariais e ISVs desenvolvendo e criando protótipos de soluções de software que atendam a demandas de empresas e do mercado. Ele apresenta eventos como Tech•Ed e eventos de treinamentos internos semelhantes.

*Agradecemos ao seguinte especialista técnico pela revisão deste artigo:*Suraj Puri