Desplegar una aplicación de libro de registro con balanceo de carga de red

En este tutorial, desplegarás una aplicación de registro de ejemplo que usa Node.js para su frontend y MySQL para su backend. Al final del tutorial, tu implementación tendrá los siguientes recursos:

Recursos de implementación con balanceo de carga de red (haz clic para ampliar)

Si no has usado nunca Deployment Manager, consulta los tutoriales Inicio rápido o Guía detallada.

Antes de empezar

Crear plantillas de recursos

En este ejemplo se inicia una implementación que contiene varios recursos de Google Cloud. Para empezar, crea plantillas que definan estos recursos por separado. Más adelante, llamarás a estas plantillas en tu configuración final. Tu despliegue contiene estos recursos:

  • Una instancia de Compute Engine que aloja una base de datos MySQL para la aplicación.
  • Una plantilla de instancia para las instancias de frontend, que usa una imagen de Docker para la aplicación Node.js.
  • Un grupo de instancias gestionado que usa la plantilla de instancia para crear dos instancias de frontend.
  • Un autoescalador que inicia o detiene instancias de frontend adicionales en función del tráfico entrante.
  • Una comprobación de estado que comprueba si las instancias de frontend están disponibles para realizar tareas.
  • Un balanceador de carga de red con una regla de reenvío.
  • Un grupo de destino para el grupo de instancias gestionado.

Crear la plantilla del backend de MySQL

El backend de esta aplicación es una única instancia de Compute Engine que ejecuta un contenedor Docker de MySQL. La plantilla container_vm.py define una instancia de Compute Engine que puede ejecutar contenedores Docker. Para asegurarse de que la plantilla sigue la estructura correcta y contiene todas las propiedades necesarias, también necesita un archivo de esquema.

Copia la plantilla que aparece a continuación o descárgala del repositorio de 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

Descargue el archivo de esquema de la plantilla.

La plantilla tiene algunas propiedades indefinidas, como containerImage, que se definen en plantillas posteriores.

Cuando usas imágenes de contenedor en instancias de Compute Engine, también debes proporcionar un archivo de manifiesto que describa qué imagen de contenedor quieres usar. Crea un método auxiliar llamado container_helper.py para definir dinámicamente el manifiesto del contenedor:

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

Crear la plantilla del frontend de Node.js

El frontend de la aplicación ejecuta Node.js y permite a los usuarios publicar mensajes en la página web. El frontend se ejecuta en un grupo de instancias de máquina virtual, compatible con un escalador automático y un balanceador de carga. Para crear plantillas de frontend, sigue estas instrucciones.

  1. Crea un recurso de plantilla de instancia.

    Necesitas una plantilla de instancia para crear un grupo de instancias gestionado, que es un grupo de instancias de máquina virtual idénticas que controlas como una sola entidad.

    Crea un archivo llamado container_instance_template.py y descarga el esquema de la plantilla:

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

    Descargue el archivo de esquema de la plantilla.

  2. Crea una herramienta de ajuste automático de escala, un grupo de instancias gestionado y un balanceador de carga.

    A continuación, cree otra plantilla que use la plantilla container_instance_template.py y cree el resto de los recursos de frontend, incluidos una herramienta de escalado automático, un balanceador de carga y un grupo de instancias gestionado.

Esta plantilla incluye los siguientes recursos:

  1. Una plantilla de instancia que usa la container_instance_template.py plantilla.

  2. Un grupo de instancias gestionado que usa la plantilla de instancia y un auto escalador que hace referencia al grupo de instancias gestionado. El uso de referencias asegura que Deployment Manager cree los recursos en un orden específico. En este caso, el grupo de instancias gestionado se crea antes que el escalador automático.

  3. Un balanceador de carga de red que contiene los siguientes recursos:

    • Una regla de reenvío con una única dirección IP externa expuesta a Internet.
    • Un grupo de destino que contenga el grupo de instancias gestionado que ha creado anteriormente.
    • Comprobación de estado que se va a asociar al grupo de destino.

Crea un archivo llamado frontend.py y descarga el esquema de la plantilla:

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

Descargue el archivo de esquema de la plantilla.

Crear una plantilla unificadora

Por último, crea una plantilla que combine las plantillas de backend y de frontend. Crea un archivo llamado nodejs.py con el siguiente contenido:

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

Descargue el archivo de esquema de la plantilla.

Ten en cuenta que el frontend de tu aplicación se llama env["deployment"]-frontend y que el backend también tiene un nombre similar. Cuando despliegues la aplicación, Deployment Manager sustituirá automáticamente env["deployment"] por el nombre del despliegue.

Crear una configuración

Una vez que tengas todas las plantillas, puedes crear una configuración que se usará para desplegar tus recursos. Crea un archivo de configuración llamado nodejs.yaml con el siguiente contenido:

# 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

Sustituye ZONE_TO_RUN por la zona en la que quieras que estén tus recursos, como us-central1-a.

Desplegar recursos

Ahora, despliega tus recursos. Con Google Cloud CLI, ejecuta el siguiente comando:

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

Cuando se complete la implementación, Deployment Manager mostrará un resumen de los recursos creados, similar al siguiente:

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  []

Probar la aplicación

Para probar tu aplicación, primero debes obtener la dirección IP externa que está sirviendo el tráfico. Para ello, consulta la regla de reenvío:

$ 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

En este caso, la IP externa es 104.154.81.44.

A continuación, en un navegador, visita la dirección IP externa con el puerto 8080. Por ejemplo, si tu dirección IP externa es 104.154.81.44, la URL sería la siguiente:

http://104.154.81.44:8080

Debería mostrarse una página en blanco, que es lo esperado. A continuación, publica un mensaje en la página. Ve a la URL siguiente:

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

Verás una confirmación de que se ha añadido tu mensaje. Vuelva a la URL principal. La página debería mostrar el siguiente mensaje:

hellothere!

Ahora tienes una aplicación implementada que puede registrar los mensajes que se le envían.

Pasos siguientes

Una vez que haya completado esta muestra, podrá hacer lo siguiente: