Implementar uma app de registo com balanceamento de carga de rede

Neste tutorial, implementa uma app de registo de exemplo que usa o Node.js para o respetivo front-end e o MySQL para o respetivo back-end. No final do tutorial, a sua implementação tem os seguintes recursos:

Recursos de implementação com equilíbrio de carga de rede (clique para aumentar)

Se não tiver experiência no Deployment Manager, consulte os tutoriais de início rápido ou o guia passo a passo.

Antes de começar

Criar modelos de recursos

Este exemplo inicia uma implementação que contém uma variedade de Google Cloud recursos. Para começar, crie modelos que definam estes recursos em separado. Mais tarde, chama estes modelos na sua configuração final. A implementação contém os seguintes recursos:

  • Uma instância do Compute Engine que aloja uma base de dados MySQL para a app.
  • Um modelo de instância para instâncias de front-end, que usa uma imagem Docker para a app Node.js.
  • Um grupo de instâncias geridas, que usa o modelo de instância para criar duas instâncias de front-end.
  • Um escalador automático, que inicia ou para instâncias de front-end adicionais com base no tráfego recebido.
  • Uma verificação de funcionamento, que verifica se as instâncias de front-end estão disponíveis para realizar tarefas.
  • Um balanceador de carga de rede com uma regra de encaminhamento.
  • Um grupo de destino para o grupo de instâncias geridas.

Criar o modelo para o back-end do MySQL

O back-end desta app é uma única instância do Compute Engine que executa um contentor do Docker do MySQL. O modelo container_vm.py define uma instância do Compute Engine que pode executar contentores Docker. Para garantir que o modelo segue a estrutura correta e contém todas as propriedades necessárias, também precisa de um ficheiro de esquema.

Copie o modelo abaixo ou transfira-o 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

Transfira o ficheiro de esquema do modelo.

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

Quando usa imagens de contentores em instâncias do Compute Engine, também tem de fornecer um ficheiro de manifesto que descreva a imagem de contentor a usar. Crie um método auxiliar denominado container_helper.py para definir dinamicamente o manifesto do contentor:

# 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)

Criar o modelo para o front-end do Node.js

O front-end da app é executado no Node.js e permite que os utilizadores publiquem mensagens na página Web. O front-end é executado num grupo de instâncias de máquinas virtuais, suportado por um escalador automático e um equilibrador de carga. Para criar modelos de frontend, use as seguintes instruções.

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

    Precisa de um modelo de instância para criar um grupo de instâncias geridas, que é um grupo de instâncias de máquinas virtuais (VMs) idênticas que controla como uma única entidade.

    Crie um ficheiro com o nome container_instance_template.py e transfira o 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}
    

    Transfira o ficheiro de esquema do modelo.

  2. Crie um redimensionador automático, um grupo de instâncias gerido e um equilibrador de carga.

    Em seguida, crie outro modelo que use o container_instance_template.py modelo e crie os restantes recursos de front-end, incluindo um escalador automático, um equilibrador de carga e um grupo de instâncias gerido.

Este modelo inclui os seguintes recursos:

  1. Um modelo de instância que usa o container_instance_template.pymodelo.

  2. Um grupo de instâncias geridas que usa o modelo de instância e um autoscaler que faz referência ao grupo de instâncias geridas. A utilização de referências garante que o Deployment Manager cria os recursos numa ordem específica. Neste caso, o grupo de instâncias gerido é criado antes do escalador automático.

  3. Um balanceador de carga de rede que contém os seguintes recursos:

    • Uma regra de encaminhamento com um único endereço IP externo exposto à Internet.
    • Um conjunto de destinos que contém o grupo de instâncias geridas que criou anteriormente.
    • Uma verificação de funcionamento a anexar ao conjunto de destino.

Crie um ficheiro com o nome frontend.py e transfira o esquema do 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}

Transfira o ficheiro de esquema do modelo.

Criar um modelo unificador

Por último, crie um modelo que reúna os modelos de back-end e frontend. Crie um ficheiro denominado 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}

Transfira o ficheiro de esquema do modelo.

Tenha em atenção que o frontend da sua app tem o nome env["deployment"]-frontend e que o backend também tem um nome semelhante. Quando implementa a app, o Deployment Manager substitui automaticamente env["deployment"] pelo nome da implementação.

A criar a sua configuração

Depois de ter todos os modelos prontos, pode criar uma configuração que vai ser usada para implementar os seus recursos. Crie um ficheiro de configuração denominado 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 onde quer os seus recursos, como us-central1-a.

Implementar os seus recursos

Agora, implemente os seus recursos. Usando a CLI do Google Cloud, execute:

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

Quando a implementação estiver concluída, o Deployment Manager mostra 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  []

Testar a app

Para testar a sua app, obtenha primeiro o endereço IP externo que está a publicar tráfego, consultando a 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, num navegador, aceda ao endereço IP externo com a porta 8080. Por exemplo, se o seu endereço IP externo for 104.154.81.44, o URL seria:

http://104.154.81.44:8080

Deve ver uma página em branco, o que é esperado. Em seguida, publique uma mensagem na página. Aceda ao seguinte URL:

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

É apresentada uma confirmação de que a mensagem foi adicionada. Regresse ao URL principal e a página deve apresentar a seguinte mensagem:

hellothere!

Agora, tem uma app implementada que pode registar as mensagens que lhe são enviadas.

Passos seguintes

Depois de concluir este exemplo, pode: