Como implantar um aplicativo de registro de ocorrências no balanceamento de carga da rede

Neste tutorial, você implantará um aplicativo de registro de ocorrências de exemplo que usa o Node.js para o front-end e MySQL para o back-end. Ao término do tutorial, a implantação terá os seguintes recursos:

Recursos de implantação em rede com balanceamento de carga (clique para ampliar)

Se você não estiver familiarizado com o Deployment Manager, consulte o Guia de início rápido ou os tutoriais do Guia passo a passo.

Antes de começar

Como criar modelos de recurso

Neste exemplo, você inicia uma implantação com diversos recursos do Google Cloud. Para começar, crie separadamente os modelos que definem esses recursos. Em seguida, chame esses modelos na configuração final. Sua implantação contém estes recursos:

  • uma instância do Compute Engine que hospeda um banco de dados MySQL para o aplicativo;
  • um modelo de instâncias de front-end que usa uma imagem do Docker para o aplicativo Node.js;
  • um grupo de instâncias gerenciadas que usa o modelo para criar duas instâncias de front-end;
  • um autoescalador que inicia ou para instâncias adicionais de front-end com base no tráfego recebido;
  • uma verificação de integridade que inspeciona se as instâncias de front-end estão disponíveis para o trabalho;
  • um balanceador de carga de rede com uma regra de encaminhamento;
  • um pool de destino para o grupo de instâncias gerenciadas

Como criar o modelo para o back-end do MySQL

O back-end desse aplicativo é uma única instância do Compute Engine que executa um contêiner Docker com o MySQL. O modelo container_vm.py define uma instância do Compute Engine que executa contêineres Docker. Para garantir que o modelo siga a estrutura correta e inclua todas as propriedades necessárias, você precisa de um arquivo de esquema.

Copie o modelo abaixo ou faça o download dele do repositório do GitHub:

# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Creates a Container VM with the provided Container manifest."""

from container_helper import GenerateManifest


COMPUTE_URL_BASE = 'https://www.googleapis.com/compute/v1/'


def GlobalComputeUrl(project, collection, name):
  return ''.join([COMPUTE_URL_BASE, 'projects/', project,
                  '/global/', collection, '/', name])


def ZonalComputeUrl(project, zone, collection, name):
  return ''.join([COMPUTE_URL_BASE, 'projects/', project,
                  '/zones/', zone, '/', collection, '/', name])


def GenerateConfig(context):
  """Generate configuration."""

  base_name = context.env['name']

  # Properties for the container-based instance.
  instance = {
      'zone': context.properties['zone'],
      'machineType': ZonalComputeUrl(context.env['project'],
                                     context.properties['zone'],
                                     'machineTypes',
                                     'f1-micro'),
      'metadata': {
          'items': [{
              'key': 'gce-container-declaration',
              'value': GenerateManifest(context)
              }]
          },
      'disks': [{
          'deviceName': 'boot',
          'type': 'PERSISTENT',
          'autoDelete': True,
          'boot': True,
          'initializeParams': {
              'diskName': base_name + '-disk',
              'sourceImage': GlobalComputeUrl('cos-cloud',
                                              'images',
                                              context.properties[
                                                  'containerImage'])
              },
          }],
      'networkInterfaces': [{
          'accessConfigs': [{
              'name': 'external-nat',
              'type': 'ONE_TO_ONE_NAT'
              }],
          'network': GlobalComputeUrl(context.env['project'],
                                      'networks',
                                      'default')
          }],
        'serviceAccounts': [{
            'email': 'default',
            'scopes': ['https://www.googleapis.com/auth/logging.write']
            }]
      }

  # Resources to return.
  resources = {
      'resources': [{
          'name': base_name,
          'type': 'compute.v1.instance',
          'properties': instance
          }]
      }

  return resources

Faça o download do arquivo de esquema para o modelo.

O modelo tem algumas propriedades indefinidas, como containerImage, que serão definidas em modelos posteriores.

Quando você usa imagens de contêiner em instâncias do Compute Engine, também precisa fornecer um arquivo de manifesto que descreve a imagem de contêiner a ser usada. Crie um método auxiliar chamado container_helper.py para definir dinamicamente o manifesto do contêiner:

# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helper methods for working with containers in config."""

import six
import yaml


def GenerateManifest(context):
  """Generates a Container Manifest given a Template context.

  Args:
    context: Template context, which must contain dockerImage and port
        properties, and an optional dockerEnv property.

  Returns:
    A Container Manifest as a YAML string.
  """
  env_list = []
  if 'dockerEnv' in context.properties:
    for key, value in six.iteritems(context.properties['dockerEnv']):
      env_list.append({'name': key, 'value': str(value)})

  manifest = {
      'apiVersion': 'v1',
      'kind': 'Pod',
      'metadata': {
          'name': str(context.env['name'])
          },
      'spec': {
          'containers': [{
              'name': str(context.env['name']),
              'image': context.properties['dockerImage'],
              'ports': [{
                  'hostPort': context.properties['port'],
                  'containerPort': context.properties['port']
                  }],
              }]
          }
      }

  if env_list:
    manifest['spec']['containers'][0]['env'] = env_list

  return yaml.dump(manifest, default_flow_style=False)

Como criar o modelo para o front-end do Node.js

O front-end do aplicativo executa o Node.js e permite que os usuários postem mensagens na página da Web. Esse front-end é executado em um grupo de instâncias de máquina virtual, com o suporte de um autoescalador e um balanceador de carga. Para criar modelos de front-end, use as instruções a seguir.

  1. Crie um recurso de modelo de instância.

    Você precisa de um modelo de instância para criar um grupo de instâncias gerenciadas, que é um grupo de instâncias idênticas de máquina virtual (VM) que você controla como uma única entidade.

    Crie um arquivo chamado container_instance_template.py e faça o download do esquema para o modelo:

    # Copyright 2016 Google Inc. All rights reserved.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    """Creates a Container VM with the provided Container manifest."""
    
    from container_helper import GenerateManifest
    
    
    def GenerateConfig(context):
      """Generates configuration."""
    
      image = ''.join(['https://www.googleapis.com/compute/v1/',
                       'projects/cos-cloud/global/images/',
                       context.properties['containerImage']])
      default_network = ''.join(['https://www.googleapis.com/compute/v1/projects/',
                                 context.env['project'],
                                 '/global/networks/default'])
    
      instance_template = {
          'name': context.env['name'] + '-it',
          'type': 'compute.v1.instanceTemplate',
          'properties': {
              'properties': {
                  'metadata': {
                      'items': [{
                          'key': 'gce-container-declaration',
                          'value': GenerateManifest(context)
                          }]
                      },
                  'machineType': 'f1-micro',
                  'disks': [{
                      'deviceName': 'boot',
                      'boot': True,
                      'autoDelete': True,
                      'mode': 'READ_WRITE',
                      'type': 'PERSISTENT',
                      'initializeParams': {'sourceImage': image}
                      }],
                  'networkInterfaces': [{
                      'accessConfigs': [{
                          'name': 'external-nat',
                          'type': 'ONE_TO_ONE_NAT'
                          }],
                      'network': default_network
                      }],
                    'serviceAccounts': [{
                        'email': 'default',
                        'scopes': ['https://www.googleapis.com/auth/logging.write']
                        }]
                  }
              }
          }
    
      outputs = [{'name': 'instanceTemplateSelfLink',
                  'value': '$(ref.' + instance_template['name'] + '.selfLink)'}]
    
      return {'resources': [instance_template], 'outputs': outputs}
    

    Faça o download do arquivo de esquema para o modelo.

  2. Crie um autoescalador, um grupo de instâncias gerenciado e um balanceador de carga.

    Em seguida, crie outro modelo que use o container_instance_template.py e cria o restante dos recursos de front-end, incluindo um autoescalador, um balanceador de carga e um grupo de instâncias gerenciadas.

Esse modelo inclui os seguintes recursos:

  1. Um modelo de instância usando o modelo container_instance_template.py.

  2. Um grupo de instâncias gerenciadas que usa o modelo de instância e um autoescalador que faz referência ao grupo de instâncias gerenciadas. O uso de referências garante que o Deployment Manager crie os recursos em uma ordem específica. Neste caso, o grupo de instâncias gerenciadas é criado antes do autoescalador.

  3. Um balanceador de carga de rede contendo os seguintes recursos:

    • uma regra de encaminhamento com um único endereço IP externo exposto à Internet;
    • um pool de destino que contém o grupo de instâncias gerenciadas criado anteriormente;
    • uma verificação de integridade para anexar ao pool de destino

Crie um arquivo chamado frontend.py e faça o download do esquema para o modelo:

# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Creates autoscaled, network LB IGM running specified docker image."""


def GenerateConfig(context):
  """Generate YAML resource configuration."""

  # Pull the region out of the zone
  region = context.properties['zone'][:context.properties['zone'].rfind('-')]
  name = context.env['name']

  resources = [{
      'name': name,
      'type': 'container_instance_template.py',
      'properties': {
          'port': context.properties['port'],
          'dockerEnv': context.properties['dockerEnv'],
          'dockerImage': context.properties['dockerImage'],
          'containerImage': context.properties['containerImage']
      }
  }, {
      'name': name + '-igm',
      'type': 'compute.v1.instanceGroupManager',
      'properties': {
          'zone': context.properties['zone'],
          'targetSize': context.properties['size'],
          'targetPools': ['$(ref.' + name + '-tp.selfLink)'],
          'baseInstanceName': name + '-instance',
          'instanceTemplate': '$(ref.' + name + '-it.selfLink)'
      }
  }, {
      'name': name + '-as',
      'type': 'compute.v1.autoscaler',
      'properties': {
          'zone': context.properties['zone'],
          'target': '$(ref.' + name + '-igm.selfLink)',
          'autoscalingPolicy': {
              'maxNumReplicas': context.properties['maxSize']
          }
      }
  }, {
      'name': name + '-hc',
      'type': 'compute.v1.httpHealthCheck',
      'properties': {
          'port': context.properties['port'],
          'requestPath': '/_ah/health'
      }
  }, {
      'name': name + '-tp',
      'type': 'compute.v1.targetPool',
      'properties': {
          'region': region,
          'healthChecks': ['$(ref.' + name + '-hc.selfLink)']
      }
  }, {
      'name': name + '-lb',
      'type': 'compute.v1.forwardingRule',
      'properties': {
          'region': region,
          'portRange': context.properties['port'],
          'target': '$(ref.' + name + '-tp.selfLink)'
      }
  }]
  return {'resources': resources}

Faça o download do arquivo de esquema para o modelo.

Como criar um modelo unificado

Por fim, crie um modelo que reúna os modelos de back-end e front-end. Crie um arquivo chamado nodejs.py com o seguinte conteúdo:

# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Create nodejs template with the back-end and front-end templates."""


def GenerateConfig(context):
  """Generate configuration."""

  backend = context.env['deployment'] + '-backend'
  frontend = context.env['deployment'] + '-frontend'
  firewall = context.env['deployment'] + '-application-fw'
  application_port = 8080
  mysql_port = 3306
  resources = [{
      'name': backend,
      'type': 'container_vm.py',
      'properties': {
          'zone': context.properties['zone'],
          'dockerImage': 'gcr.io/qwiklabs-resources/mysql',
          'containerImage': 'family/cos-stable',
          'port': mysql_port,
          'dockerEnv': {
              'MYSQL_ROOT_PASSWORD': 'mypassword'
          }
      }
  }, {
      'name': frontend,
      'type': 'frontend.py',
      'properties': {
          'zone': context.properties['zone'],
          'dockerImage': 'gcr.io/qwiklabs-resources/nodejsservice',
          'port': application_port,
          # Define the variables that are exposed to container as env variables.
          'dockerEnv': {
              'SEVEN_SERVICE_MYSQL_PORT': mysql_port,
              'SEVEN_SERVICE_PROXY_HOST': '$(ref.' + backend
                                          + '.networkInterfaces[0].networkIP)'
          },
          # If left out will default to 1
          'size': 2,
          # If left out will default to 1
          'maxSize': 20
      }
  }, {
      'name': firewall,
      'type': 'compute.v1.firewall',
      'properties': {
          'allowed': [{
              'IPProtocol': 'TCP',
              'ports': [application_port]
          }],
          'sourceRanges': ['0.0.0.0/0']
      }
  }]
  return {'resources': resources}

Faça o download do arquivo de esquema para o modelo.

Observe que o front-end do aplicativo é denominado de env["deployment"]-frontend, e que o back-end também é denominado da mesma forma. Quando você implanta o aplicativo, o Deployment Manager substitui automaticamente env["deployment"] pelo nome da implantação.

Como criar a configuração

Depois que todos os modelos estiverem prontos, será possível criar uma configuração que será usada para implantar os recursos. Crie um arquivo de configuração chamado nodejs.yaml com o seguinte conteúdo:

# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Launches an autoscaled, load-balanced frontend running nodejs for serving
# traffic. Also launches a single MySQL container instance, wires the two
# together using references, and passes them as env variables to the underlying
# frontend Docker containers.
imports:
- path: nodejs.py

resources:
- name: nodejs
  type: nodejs.py
  properties:
    zone: ZONE_TO_RUN

Substitua ZONE_TO_RUN pela zona em que você quer seus recursos, como us-central1-a.

Como implantar recursos

Agora, implante os recursos. Usando a Google Cloud CLI, execute:

gcloud deployment-manager deployments create advanced-configuration --config nodejs.yaml

Quando a implantação estiver completa, o Deployment Manager mostrará um resumo dos recursos criados, semelhante ao seguinte:

Waiting for create operation-1468522101491-5379cf2344539-5961abe8-a500190c...done.
Create operation operation-1468522101491-5379cf2344539-5961abe8-a500190c completed successfully.
NAME                                   TYPE                             STATE      ERRORS
advanced-configuration-application-fw  compute.v1.firewall              COMPLETED  []
advanced-configuration-backend         compute.v1.instance              COMPLETED  []
advanced-configuration-frontend-as     compute.v1.autoscaler            COMPLETED  []
advanced-configuration-frontend-hc     compute.v1.httpHealthCheck       COMPLETED  []
advanced-configuration-frontend-igm    compute.v1.instanceGroupManager  COMPLETED  []
advanced-configuration-frontend-it     compute.v1.instanceTemplate      COMPLETED  []
advanced-configuration-frontend-lb     compute.v1.forwardingRule        COMPLETED  []
advanced-configuration-frontend-tp     compute.v1.targetPool            COMPLETED  []

Como testar o app

Para testar o aplicativo, consiga primeiro o endereço IP externo que está suprindo o tráfego, por meio da consulta da regra de encaminhamento:

$ gcloud compute forwarding-rules describe advanced-configuration-frontend-lb --region us-central1
IPAddress: 104.154.81.44
IPProtocol: TCP
creationTimestamp: '2016-07-14T11:48:37.228-07:00'
description: ''
id: '9033201246750269546'
kind: compute#forwardingRule
name: advanced-configuration-frontend-lb
portRange: 8080-8080
region: https://www.googleapis.com/compute/v1/projects/myproject/regions/us-central1
selfLink: https://www.googleapis.com/compute/v1/projects/myproject/regions/us-central1/forwardingRules/advanced-configuration-frontend-lb
target: https://www.googleapis.com/compute/v1/projects/myproject/regions/us-central1/targetPools/advanced-configuration-frontend-tp

Neste caso, o IP externo é 104.154.81.44.

Em seguida, em um navegador, visite o endereço IP externo com a porta 8080. Por exemplo, se o endereço IP externo for 104.154.81.44, o URL será:

http://104.154.81.44:8080

Aparecerá uma página em branco, o que é esperado. Em seguida, poste uma mensagem na página. Acesse o seguinte URL:

http://104.154.81.44:8080?msg=hellothere!

Você verá uma confirmação de que a mensagem foi adicionada. Volte ao URL principal, e a página terá a mensagem:

hellothere!

Agora você tem um aplicativo implantado capaz de registrar as mensagens recebidas.

Próximas etapas

Depois de concluir este exemplo, você poderá: