Déployer une application de journal à équilibrage de charge réseau

Dans ce tutoriel, vous allez déployer un exemple d'application de journal utilisant Node.js pour l'interface et MySQL pour le backend. À la fin du tutoriel, votre déploiement contiendra les ressources suivantes :

Ressources de déploiement à équilibrage de charge réseau (cliquez pour agrandir)

Si vous ne connaissez pas encore Deployment Manager, consultez le guide de démarrage rapide ou les tutoriels du guide par étapes.

Avant de commencer

Créer vos modèles de ressources

Cet exemple lance un déploiement contenant diverses ressources Google Cloud. Pour commencer, vous allez créer des modèles qui définissent ces ressources séparément. Vous appellerez ensuite ces modèles dans votre configuration finale. Votre déploiement contient les ressources suivantes :

  • Une instance Compute Engine qui héberge une base de données MySQL pour l'application.
  • Un modèle d'instance pour les instances d'interface, qui utilise une image Docker pour l'application Node.js.
  • Un groupe d'instances géré qui génère deux instances frontend à l'aide du modèle d'instance.
  • Un autoscaler qui démarre ou arrête des instances frontend supplémentaires en fonction du trafic entrant.
  • Une vérification de l'état qui indique si les instances frontend sont disponibles ou non.
  • Un équilibreur de charge réseau disposant d'une règle de transfert.
  • Un pool cible pour le groupe d'instances géré.

Créer le modèle pour le backend MySQL

Le backend de cette application consiste en une instance Compute Engine unique exécutant un conteneur MySQL Docker. Le modèle container_vm.py définit une instance Compute Engine capable d'exécuter des conteneurs Docker. Pour vous assurer que le modèle suit la structure appropriée et contient toutes les propriétés requises, vous avez également besoin d'un fichier de schéma.

Copiez le modèle ci-dessous ou téléchargez-le depuis le dépôt 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

Téléchargez le fichier de schéma pour le modèle.

Le modèle comporte des propriétés non définies, telles que containerImage, qui sont définies dans des modèles ultérieurs.

Lorsque vous utilisez des images de conteneur sur des instances Compute Engine, vous devez également fournir un fichier manifeste qui décrit l'image de conteneur à utiliser. Créez une méthode d'assistance appelée container_helper.py pour définir le fichier manifeste du conteneur de façon dynamique :

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

Créer le modèle pour l'interface Node.js

L'interface de l'application exécute Node.js et permet aux utilisateurs de publier des messages sur la page Web. L'interface s'exécute sur un groupe d'instances de VM assisté par un autoscaler et un équilibreur de charge. Pour créer ces modèles d'interface, suivez les instructions ci-dessous.

  1. Créez une ressource de modèle d'instance.

    Vous avez besoin d'un modèle d'instance pour créer un groupe d'instances géré. Il s'agit d'un groupe d'instances de machines virtuelles identiques que vous contrôlez comme une seule entité.

    Créez un fichier nommé container_instance_template.py, puis téléchargez le schéma du modèle :

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

    Téléchargez le fichier de schéma pour le modèle.

  2. Créez un autoscaler, un groupe d'instances géré et un équilibreur de charge.

    Ensuite, créez un autre modèle qui utilise le modèle container_instance_template.py et crée les autres ressources de votre frontend, notamment un autoscaler, un équilibreur de charge et un groupe d'instances géré.

Ce modèle comprend les ressources suivantes :

  1. Un modèle d'instance utilisant le modèle container_instance_template.py.

  2. Un groupe d'instances géré qui utilise le modèle d'instance et un autoscaler qui référence le groupe d'instances géré. L'utilisation de références permet à Deployment Manager de générer les ressources dans un ordre spécifique. Dans ce cas, le groupe d'instances gérées est créé avant l'autoscaler.

  3. Un équilibreur de charge réseau qui contient les ressources suivantes :

    • Une règle de transfert avec une adresse IP externe unique exposée sur Internet.
    • Un pool cible contenant le groupe d'instances géré que vous avez précédemment créé.
    • Une vérification d'état à associer au pool cible.

Créez un fichier nommé frontend.py, puis téléchargez le schéma du modèle :

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

Téléchargez le fichier de schéma pour le modèle.

Créer un modèle commun

Enfin, créez un modèle combinant les modèles de backend et de frontend. Créez un fichier nommé nodejs.py avec le contenu suivant :

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

Téléchargez le fichier de schéma pour le modèle.

Notez que l'interface de votre application s'appelle env["deployment"]-frontend et que votre backend porte le même nom. Lorsque vous déployez l'application, Deployment Manager remplace automatiquement env["deployment"] par le nom de déploiement.

Créer une configuration

Une fois tous les modèles prêts, vous pouvez créer une configuration qui servira à déployer vos ressources. Créez un fichier de configuration nommé nodejs.yaml avec le contenu suivant :

# 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

Remplacez ZONE_TO_RUN par la zone dans laquelle vous souhaitez héberger vos ressources, par exemple us-central1-a.

Déployer vos ressources

Déployez maintenant vos ressources. À l'aide de Google Cloud CLI, exécutez la commande suivante :

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

Une fois le déploiement terminé, Deployment Manager affiche un récapitulatif des ressources créées, comparable au suivant :

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

Tester l'application

Pour tester votre application, commencez par récupérer l'adresse IP externe qui diffuse le trafic en adressant une requête à la règle de transfert :

$ 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

Dans ce cas, l'adresse IP externe est 104.154.81.44.

Ensuite, dans un navigateur, accédez à l'adresse IP externe avec le port 8080. Par exemple, si votre adresse IP externe est 104.154.81.44, l'URL sera la suivante :

http://104.154.81.44:8080

Vous devriez voir une page blanche s'afficher. Publiez ensuite un message sur la page. Accédez à l'URL suivante :

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

Une confirmation indiquant que votre message a été ajouté s'affiche. Revenez à l'URL principale. La page devrait afficher le message suivant :

hellothere!

Vous disposez désormais d'une application déployée capable de consigner les messages qui lui sont envoyés.

Étapes suivantes

Une fois cet exemple terminé, vous pouvez effectuer les étapes suivantes :