Créer une application de journal à équilibrage de charge HTTP

Cet exemple illustre le développement d'une application de journal avancée qui utilise Node.js pour l'interface et MySQL pour le backend. Le modèle crée et connecte également un équilibreur de charge HTTP qui répartit la charge entre deux zones et un autoscaler qui met automatiquement l'application à l'échelle.

Ressources de déploiement équilibrées pour les charges HTTP
Ressources de déploiement à équilibrage de charge HTTP (cliquez pour agrandir)

Pour cet exemple, nous partons du principe que vous connaissez bien les conteneurs Docker, ainsi que les ressources Compute Engine, en particulier l'équilibrage de charge HTTP, l'autoscaling, les groupes d'instances gérés et les modèles d'instances.

Pour accéder à d'autres tutoriels d'introduction, consultez le guide de démarrage ou le guide par étapes.

Avant de commencer

Créer vos modèles

Dans cet exemple, un déploiement est lancé avec plusieurs types de ressources. Pour commencer, vous allez créer des modèles réutilisables qui définissent ces ressources séparément. Vous utiliserez ensuite ces modèles dans la configuration finale.

À la fin de cet exemple, vous disposerez d'un déploiement contenant les ressources suivantes :

  • Une seule instance Compute Engine pour la machine virtuelle MySQL backend
  • Un modèle d'instance qui utilise une image Docker
  • Deux groupes d'instances gérés à évolution automatique dans deux zones différentes exécutant le service Node.js de frontend
  • Un autre groupe d'instances géré à évolution automatique envoyant des données statiques
  • Une vérification d'état et un équilibreur de charge HTTP pour le trafic distribué entre les différents groupes d'instances gérés

Créer des modèles de backend

Le backend de cette application consiste en une instance Compute Engine unique exécutant un conteneur MySQL Docker. Créez un modèle qui définit une instance Compute Engine utilisant une image optimisée pour les conteneurs. Nommez le fichier container_vm.[py|jinja].

Jinja



{% from 'container_helper.jinja' import GenerateManifest %}
{% set COMPUTE_URL_BASE = 'https://www.googleapis.com/compute/v1/' %}

resources:
- name: {{ env['name'] }}
  type: compute.v1.instance
  properties:
    zone: {{ properties['zone'] }}
    machineType: {{ COMPUTE_URL_BASE }}projects/{{ env['project'] }}/zones/{{ properties['zone'] }}/machineTypes/f1-micro
    metadata:
      items:
      - key: gce-container-declaration
        value: |
          {{ GenerateManifest(env['name'], properties['port'], properties['dockerImage'], properties['dockerEnv'])|indent(10) }}
    disks:
    - deviceName: boot
      type: PERSISTENT
      autoDelete: true
      boot: true
      initializeParams:
        diskName: {{ env['name'] }}-disk
        sourceImage: {{ COMPUTE_URL_BASE }}projects/cos-cloud/global/images/{{ properties['containerImage'] }}
    networkInterfaces:
    - accessConfigs:
      - name: external-nat
        type: ONE_TO_ONE_NAT
      network: {{ COMPUTE_URL_BASE }}projects/{{ env['project'] }}/global/networks/default
    serviceAccounts:
      - email: default
        scopes:
        - https://www.googleapis.com/auth/logging.write
        - https://www.googleapis.com/auth/monitoring.write

Python

# 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

Le modèle définit plusieurs variables, telles que containerImage et manifest, dont la valeur sera renseignée lorsque vous définirez votre configuration. Ce modèle a pour seule fonction de créer une unique instance de machine virtuelle (VM).

Lorsque vous utilisez des images de conteneur sur des instances Compute Engine, vous devez également fournir un fichier manifeste (différent du fichier manifeste Deployment Manager) pour indiquer à Compute Engine l'image de conteneur à utiliser. Créez une méthode d'assistance appelée container_helper.[py|jinja] pour définir le fichier manifeste du conteneur de façon dynamique :

Jinja



{% macro GenerateManifest(name, port, dockerImage, dockerEnv) -%}
apiVersion: v1
kind: Pod
metadata:
  name: {{ name }}
spec:
  containers:
  - name: {{ name }}
    image: {{ dockerImage }}
    ports:
    - hostPort: {{ port }}
      containerPort: {{ port }}
    {% if dockerEnv -%}
    env:
    {% for key, value in dockerEnv.items() -%}
    - name: {{ key }}
      value: '{{ value }}'
    {% endfor -%}
    {% endif -%}
{%- endmacro -%}

Python

# 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 des modèles d'interface

L'interface de cette application exécute Node.js et permet aux utilisateurs de publier des messages sur la page Web. Deux groupes d'instances gérés contiennent deux instances chacun : un groupe d'instances géré principal et un groupe d'instances géré secondaire pour l'équilibrage de charge.

Pour créer ces modèles d'interface, suivez les instructions ci-dessous.

  1. Créez un modèle d'instance.

    Vous devez disposer d'une ressource de modèle d'instance pour créer un groupe d'instances géré, qui est un groupe d'instances de VM identiques gérées de manière centralisée. Dans cet exemple, vous allez créer un groupe d'instances géré pour les instances frontend Node.js, mais vous devez d'abord créer le modèle d'instance.

    Définissez un fichier nommé container_instance_template.[py|jinja] :

    Jinja

    
    
    {% from 'container_helper.jinja' import GenerateManifest %}
    {% set IT_NAME = env['name'] + '-it' %}
    
    resources:
    - name: {{ IT_NAME }}
      type: compute.v1.instanceTemplate
      properties:
        properties:
          metadata:
            items:
            - key: gce-container-declaration
              value: |
                {{ GenerateManifest(env['name'], properties['port'],properties['dockerImage'], properties['dockerEnv'])|indent(12) }}
          machineType: f1-micro
          disks:
          - deviceName: boot
            boot: true
            autoDelete: true
            mode: READ_WRITE
            type: PERSISTENT
            initializeParams:
              sourceImage: https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/{{ properties['containerImage'] }}
          networkInterfaces:
          - accessConfigs:
            - name: external-nat
              type: ONE_TO_ONE_NAT
            network: https://www.googleapis.com/compute/v1/projects/{{ env['project'] }}/global/networks/default
          serviceAccounts:
            - email: default
              scopes:
              - https://www.googleapis.com/auth/logging.write
              - https://www.googleapis.com/auth/monitoring.write
    outputs:
    - name: instanceTemplateSelfLink
      value: $(ref.{{ IT_NAME }}.selfLink)
    

    Python

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

  2. Créez un groupe d'instances géré avec autoscaling.

    Maintenant que vous disposez d'un modèle d'instance, vous pouvez définir un modèle qui utilise le modèle d'instance pour créer un groupe d'instances géré à évolution automatique. Créez un fichier nommé autoscaled_group.[py|jinja] avec le contenu suivant :

    Jinja

    
    
    resources:
      - name: {{ env["name"] }}-igm
        type: compute.v1.instanceGroupManager
        properties:
          zone: {{ properties["zone"] }}
          targetSize: {{ properties["size"] }}
          baseInstanceName: {{ env["name"] }}-instance
          instanceTemplate: {{ properties["instanceTemplate"] }}
    
      - name: {{ env["name"] }}-as
        type: compute.v1.autoscaler
        properties:
          zone: {{ properties["zone"] }}
          target: $(ref.{{ env["name"] }}-igm.selfLink)
          autoscalingPolicy:
            maxNumReplicas: {{ properties["maxSize"] }}
    

    Python

    # 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."""
    
      # NOTE: Once we can specify the port/service during creation of IGM,
      # we will wire it up here.
      name = context.env['name']
      resources = [{
          'name': name + '-igm',
          'type': 'compute.v1.instanceGroupManager',
          'properties': {
              'zone': context.properties['zone'],
              'targetSize': context.properties['size'],
              'baseInstanceName': name + '-instance',
              'instanceTemplate': context.properties['instanceTemplate']
          }
      }, {
          'name': name + '-as',
          'type': 'compute.v1.autoscaler',
          'properties': {
              'zone': context.properties['zone'],
              'target': '$(ref.' + name + '-igm.selfLink)',
              'autoscalingPolicy': {
                  'maxNumReplicas': context.properties['maxSize']
    
              }
          }
      }]
      return {'resources': resources}
    

    Créez le fichier de schéma correspondant :

    Jinja

    # 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.
    
    info:
      title: Autoscaled, network LB IGM template
      author: Google
      description: Creates an autoscaled Instance Group Manager running the specified Docker image
      version: 1.0
    
    required:
    - zone
    - instanceTemplate
    
    properties:
      zone:
        type: string
        description: Zone in which this VM will run
    
      instanceTemplate:
        type: string
        description: URL for the instance template to use for IGM
    
      size:
        type: integer
        default: 1
        description: Initial size of the Managed Instance Group
    
      maxSize:
        type: integer
        default: 1
        description: Maximum size the Managed Instance Group will be autoscaled to
    

    Python

    # 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.
    
    info:
      title: Autoscaled, network LB IGM template
      author: Google
      description: Creates an autoscaled Instance Group Manager running the specified Docker image
      version: 1.0
    
    required:
    - zone
    - instanceTemplate
    
    properties:
      zone:
        type: string
        description: Zone in which this VM will run
    
      instanceTemplate:
        type: string
        description: URL for the instance template to use for IGM
    
      size:
        type: integer
        default: 1
        description: Initial size of the Managed Instance Group
    
      maxSize:
        type: integer
        default: 1
        description: Maximum size the Managed Instance Group will be autoscaled to
    

  3. Créez des ressources à l'aide de ces modèles.

    Jusqu'à présent, vous avez défini des modèles de base qui déterminent les propriétés de vos ressources. Définissez la configuration de votre frontend à l'aide de ces modèles. Créez un fichier nommé service.[py|jinja] avec le contenu suivant :

    Jinja

    
    
    resources:
      - name: {{ env["name"] }}
        type: container_instance_template.jinja
        properties:
          port: {{ properties["port"] }}
          dockerEnv: {{ properties["dockerEnv"] }}
          dockerImage: {{ properties["dockerImage"] }}
          containerImage: {{ properties["containerImage"] }}
    
      - name: {{ env["name"] }}-pri
        type: autoscaled_group.jinja
        properties:
          zone: {{ properties["primaryZone"] }}
          size: {{ properties["primarySize"] }}
          maxSize: {{ properties["maxSize"] }}
          port: {{ properties["port"] }}
          service: {{ properties["service"] }}
          baseInstanceName: {{ env["name"] }}-instance
          instanceTemplate: $(ref.{{ env["name"] }}-it.selfLink)
    
      - name: {{ env["name"] }}-sec
        type: autoscaled_group.jinja
        properties:
          zone: {{ properties["secondaryZone"] }}
          size: {{ properties["secondarySize"] }}
          maxSize: {{ properties["maxSize"] }}
          port: {{ properties["port"] }}
          service: {{ properties["service"] }}
          baseInstanceName: {{ env["name"] }}-instance
          instanceTemplate: $(ref.{{ env["name"] }}-it.selfLink)
    
      - name: {{ env["name"] }}-hc
        type: compute.v1.httpHealthCheck
        properties:
          port: {{ properties["port"] }}
          requestPath: /_ah/health
    
      - name: {{ env["name"] }}-bes
        type: compute.v1.backendService
        properties:
          port: {{ properties["port"] }}
          portName: {{ properties["service"] }}
          backends:
            - name: {{ env["name"] }}-primary
              group: $(ref.{{ env["name"] }}-pri-igm.instanceGroup)
            - name: {{ env["name"] }}-secondary
              group: $(ref.{{ env["name"] }}-sec-igm.instanceGroup)
          healthChecks: [ $(ref.{{ env["name"] }}-hc.selfLink) ]
    

    Python

    # 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 primary/secondary zone autoscaled IGM running specified container."""
    
    def GenerateConfig(context):
      """Generate YAML resource configuration."""
    
      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 + '-pri',
          'type': 'autoscaled_group.py',
          'properties': {
              'zone': context.properties['primaryZone'],
              'size': context.properties['primarySize'],
              'maxSize': context.properties['maxSize'],
              'port': context.properties['port'],
              'service': context.properties['service'],
              'baseInstanceName': name + '-instance',
              'instanceTemplate': '$(ref.' + name + '-it.selfLink)'
          }
      }, {
          'name': name + '-sec',
          'type': 'autoscaled_group.py',
          'properties': {
              'zone': context.properties['secondaryZone'],
              'size': context.properties['secondarySize'],
              'maxSize': context.properties['maxSize'],
              'port': context.properties['port'],
              'service': context.properties['service'],
              'baseInstanceName': name + '-instance',
              'instanceTemplate': '$(ref.' + name + '-it.selfLink)'
          }
      }, {
          'name': name + '-hc',
          'type': 'compute.v1.httpHealthCheck',
          'properties': {
              'port': context.properties['port'],
              'requestPath': '/_ah/health'
          }
      }, {
          'name': name + '-bes',
          'type': 'compute.v1.backendService',
          'properties': {
              'port': context.properties['port'],
              'portName': context.properties['service'],
              'backends': [{
                  'name': name + '-primary',
                  'group': '$(ref.' + name + '-pri-igm.instanceGroup)'
              }, {
                  'name': name + '-secondary',
                  'group': '$(ref.' + name + '-sec-igm.instanceGroup)'
              }],
              'healthChecks': ['$(ref.' + name + '-hc.selfLink)']
          }
      }]
      return {'resources': resources}
    

    Créez le fichier de schéma correspondant :

    Jinja

    # 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.
    
    info:
      title: Autoscaled IGM
      author: Google
      description: Creates primary/secondary zone autoscaled IGM running specified container.
      version: 1.0
    
    imports:
    - path: autoscaled_group.jinja
    - path: ../../common/jinja/container_instance_template.jinja
      name: container_instance_template.jinja
    
    required:
    - port
    - service
    - primaryZone
    - secondaryZone
    - dockerImage
    
    properties:
      primarySize:
        type: integer
        default: 1
        description: The size of the primary autoscaled IGM
    
      secondarySize:
        type: integer
        default: 0
        description: The size of the secondary autoscaled IGM
    
      maxSize:
        type: integer
        default: 1
        description: The maximum size of the IGM
    
      containerImage:
        type: string
        default: family/cos-stable
        description: The container image to be used
    
      dockerEnv:
        type: object
        default: {}
        description: The container environment variables
    
      dockerImage:
        type: string
        description: the docker image to be used
    
      port:
        type: integer
        description: Port to expose on the container as well as on the load balancer (e.g., 8080)
    
      service:
        type: string
        description: Name of the service the port exposes for loadbalancing (backendService) purposes
    
      primaryZone:
        type: string
        description: Primary Zone in which to run the service
    
      secondaryZone:
        type: string
        description: Secondary Zone in which to run the service
    

    Python

    # 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.
    
    info:
      title: Autoscaled IGM
      author: Google
      description: Creates primary/secondary zone autoscaled IGM running specified container.
      version: 1.0
    
    imports:
    - path: autoscaled_group.py
    - path: ../../common/python/container_instance_template.py
      name: container_instance_template.py
    
    required:
    - port
    - service
    - primaryZone
    - secondaryZone
    - dockerImage
    
    properties:
      primarySize:
        type: integer
        default: 1
        description: The size of the primary autoscaled IGM
    
      secondarySize:
        type: integer
        default: 0
        description: The size of the secondary autoscaled IGM
    
      maxSize:
        type: integer
        default: 1
        description: The maximum size of the IGM
    
      containerImage:
        type: string
        default: family/cos-stable
        description: The container image to be used
    
      dockerEnv:
        type: object
        default: {}
        description: The container environment variables
    
      dockerImage:
        type: string
        description: the docker image to be used
    
      port:
        type: integer
        description: Port to expose on the container as well as on the load balancer (e.g., 8080)
    
      service:
        type: string
        description: Name of the service the port exposes for loadbalancing (backendService) purposes
    
      primaryZone:
        type: string
        description: Primary Zone in which to run the service
    
      secondaryZone:
        type: string
        description: Secondary Zone in which to run the service
    

    Voici le détail des éléments créés par ce modèle :

    1. Deux groupes d'instances gérés, un groupe principal et un groupe secondaire.

      Le modèle utilise le modèle autoscaled_group.[py|jinja] pour créer un groupe d'instances géré principal et secondaire à évolution automatique.

    2. Le modèle crée ensuite un service de backend et un vérificateur d'état. Le service de backend est nécessaire pour l'équilibrage de charge HTTP. De plus, il définit la capacité de diffusion des groupes d'instances qui en font partie. Dans ce cas, les groupes d'instances gérés principal et secondaire appartiennent à ce backend. Par conséquent, les propriétés par défaut du service de backend s'appliquent.

      Par défaut, un service de backend effectue un équilibrage de charge en fonction de l'utilisation du processeur par les groupes d'instances associés, mais vous pouvez également effectuer l'équilibrage de charge en fonction du nombre de requêtes par seconde (RPS).

      Remarque : Une vérification d'état est obligatoire lors de la création d'un service de backend.

Créer un modèle commun

Enfin, créez un modèle commun combinant les modèles de backend et d'interface. Créez un fichier nommé application.[py|jinja] :

Jinja



{% set BACKEND = env["deployment"] + "-backend" %}
{% set FRONTEND = env["deployment"] + "-frontend" %}
{% set STATIC_SERVICE = env["deployment"] + "-static-service" %}
{% set APPLICATION = env["deployment"] + "-application" %}

{% set APPLICATION_PORT = 8080 %}
{% set LB_PORT = 8080 %}
{% set MYSQL_PORT = 8080 %}

{% set CONTAINER_IMAGE = "family/cos-stable" %}

resources:
- name: {{ BACKEND }}
  type: container_vm.jinja
  properties:
    zone: {{ properties["primaryZone"] }}
    dockerImage: {{ properties["backendImage"] }}
    containerImage: {{ CONTAINER_IMAGE }}
    port: {{ MYSQL_PORT }}

- name: {{ FRONTEND }}
  type: service.jinja
  properties:
    primaryZone: {{ properties["primaryZone"] }}
    primarySize: 2
    secondaryZone: {{ properties["secondaryZone"] }}
    secondarySize: 0
    dockerImage: {{ properties["frontendImage"] }}
    containerImage: {{ CONTAINER_IMAGE }}
    port: {{ APPLICATION_PORT }}
    service: http
    # If left out will default to 1
    maxSize: 20

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

- name: {{ STATIC_SERVICE }}
  type: service.jinja
  properties:
    primaryZone: {{ properties["primaryZone"] }}
    primarySize: 2
    secondaryZone: {{ properties["secondaryZone"] }}
    secondarySize: 0
    dockerImage: {{ properties["staticImage"] }}
    containerImage: {{ CONTAINER_IMAGE }}
    port: {{ APPLICATION_PORT }}
    service: httpstatic
    # If left out will default to 1
    maxSize: 20

- name: {{ APPLICATION }}-urlmap
  type: compute.v1.urlMap
  properties:
    defaultService: $(ref.{{ FRONTEND }}-bes.selfLink)
    hostRules:
      - hosts: ["*"]
        pathMatcher: pathmap
    pathMatchers:
      - name: pathmap
        defaultService: $(ref.{{ FRONTEND }}-bes.selfLink)
        pathRules:
          - paths: ["/static", "/static/*"]
            service: $(ref.{{ STATIC_SERVICE }}-bes.selfLink)
- name: {{ APPLICATION }}-targetproxy
  type: compute.v1.targetHttpProxy
  properties:
    urlMap: $(ref.{{ APPLICATION }}-urlmap.selfLink)
- name: {{ APPLICATION }}-l7lb
  type: compute.v1.globalForwardingRule
  properties:
    IPProtocol: TCP
    portRange: {{ LB_PORT }}
    target: $(ref.{{ APPLICATION }}-targetproxy.selfLink)
- name: {{ APPLICATION }}-fw
  type: compute.v1.firewall
  properties:
    allowed:
      - IPProtocol: TCP
        ports: [ {{ LB_PORT }} ]
    sourceRanges: [ 0.0.0.0/0 ]

Python


# 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 appplication template with back-end and front-end templates."""

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

  backend = context.env['deployment'] + '-backend'
  frontend = context.env['deployment'] + '-frontend'
  static_service = context.env['deployment'] + '-static-service'
  application = context.env['deployment'] + '-application'

  container_image = 'family/cos-stable'

  application_port = 8080
  lb_port = 8080
  mysql_port = 8080

  resources = [{
      'name': backend,
      'type': 'container_vm.py',
      'properties': {
          'zone': context.properties['primaryZone'],
          'dockerImage': context.properties['backendImage'],
          'containerImage': container_image,
          'port': mysql_port
      }
  }, {
      'name': frontend,
      'type': 'service.py',
      'properties': {
          'primaryZone': context.properties['primaryZone'],
          'primarySize': 2,
          'secondaryZone': context.properties['secondaryZone'],
          'secondarySize': 0,
          'dockerImage': context.properties['frontendImage'],
          'containerImage': container_image,
          'port': application_port,
          'service': 'http',
          # If left out will default to 1
          'maxSize': 20,
          # 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)'
          }
      }
  }, {
      'name': static_service,
      'type': 'service.py',
      'properties': {
          'primaryZone': context.properties['primaryZone'],
          'primarySize': 2,
          'secondaryZone': context.properties['secondaryZone'],
          'secondarySize': 0,
          'dockerImage': context.properties['staticImage'],
          'containerImage': container_image,
          'port': application_port,
          'service': 'httpstatic',
          # If left out will default to 1
          'maxSize': 20
      }
  }, {
      'name': application + '-urlmap',
      'type': 'compute.v1.urlMap',
      'properties': {
          'defaultService': '$(ref.' + frontend + '-bes.selfLink)',
          'hostRules': [{
              'hosts': ['*'],
              'pathMatcher': 'pathmap'
          }],
          'pathMatchers': [{
              'name': 'pathmap',
              'defaultService': '$(ref.' + frontend + '-bes.selfLink)',
              'pathRules': [{
                  'paths': ['/static', '/static/*'],
                  'service': '$(ref.' + static_service + '-bes.selfLink)'
              }]
          }]
      }
  }, {
      'name': application + '-targetproxy',
      'type': 'compute.v1.targetHttpProxy',
      'properties': {
          'urlMap': '$(ref.' + application + '-urlmap.selfLink)'
      }
  }, {
      'name': application + '-l7lb',
      'type': 'compute.v1.globalForwardingRule',
      'properties': {
          'IPProtocol': 'TCP',
          'portRange': lb_port,
          'target': '$(ref.' + application + '-targetproxy.selfLink)'
      }
  }, {
      'name': application + '-fw',
      'type': 'compute.v1.firewall',
      'properties': {
          'allowed': [{
              'IPProtocol': 'TCP',
              'ports': [lb_port]
          }],
          'sourceRanges': ['0.0.0.0/0']
      }
  }]
  return {'resources': resources}

Créez le fichier de schéma correspondant :

Jinja

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

info:
  title: Application Template
  author: Google
  description: Create application template with back-end and front-end templates
  version: 1.0

imports:
- path: service.jinja
- path: ../../common/jinja/container_vm.jinja
  name: container_vm.jinja

required:
- primaryZone
- secondaryZone
- backendImage
- frontendImage
- staticImage

properties:
  primaryZone:
    type: string
    description: Primary Zone in which to run the service

  secondaryZone:
    type: string
    description: Secondary Zone in which to run the service

  backendImage:
    type: string
    description: Docker image to use in the backend

  frontendImage:
    type: string
    description: Docker image to use in the frontend service

  staticImage:
    type: string
    description: Docker image to use in the static service

Python

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

info:
  title: Application Template
  author: Google
  description: Create application template with back-end and front-end templates
  version: 1.0

imports:
- path: service.py
- path: ../../common/python/container_vm.py
  name: container_vm.py

required:
- primaryZone
- secondaryZone
- backendImage
- frontendImage
- staticImage

properties:
  primaryZone:
    type: string
    description: Primary Zone in which to run the service

  secondaryZone:
    type: string
    description: Secondary Zone in which to run the service

  backendImage:
    type: string
    description: Docker image to use in the backend

  frontendImage:
    type: string
    description: Docker image to use in the frontend service

  staticImage:
    type: string
    description: Docker image to use in the static service

En plus du frontend et du backend, le modèle permet également de définir des ressources supplémentaires :

  1. Un service statique avec des groupes d'instances gérés principaux et secondaires. Ce service statique diffuse une page Web accessible via le chemin /static dans votre application.

  2. Une ressource de mappage d'URL. L'équilibrage de charge HTTP nécessite un mappage d'URL pour mapper les différentes URL sur les chemins corrects. Dans ce cas, le chemin par défaut, indiqué par la propriété defaultService, est le service de backend que vous avez créé précédemment. Si un utilisateur accède à /static, le mappage d'URL mappe ce chemin sur le service statique, tel que défini dans la section pathMatchers.

  3. Une règle de transfert globale et un proxy HTTP cible. Comme la charge de l'application est répartie entre deux zones distinctes, vous avez besoin d'une règle de transfert globale qui diffuse une seule adresse IP externe. En outre, un proxy HTTP cible est nécessaire pour configurer l'équilibrage de charge HTTP.

  4. Règle de pare-feu qui autorise le trafic via le port 8080.

Créer la configuration

Maintenant que vos modèles et les schémas associés sont prêts, vous pouvez créer une configuration pour déployer ces ressources. Créez un fichier de configuration nommé application.yaml avec le contenu suivant et remplacez ZONE_TO_RUN et SECONDARY_ZONE_TO_RUN par les zones principale et secondaire de votre choix.

Jinja

# 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 in two zones running nodejs
# for serving traffic using L7 loadbalancing. 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.
#
# NOTE: Due to the fact that IGM does not allow specifying service/port to
# created IG, you must run the following commands after creation of the
# template:
#
# export DEPLOYMENT=<DEPLOYMENT NAME>
# export PRIMARY_ZONE=<PRIMARY ZONE>
# export SECONDARY_ZONE=<SECONDARY ZONE>
#
# gcloud compute instance-groups unmanaged set-named-ports ${DEPLOYMENT}-frontend-pri-igm \
#  --named-ports http:8080,httpstatic:8080 \
#  --zone ${PRIMARY_ZONE}
#
# gcloud compute instance-groups unmanaged set-named-ports ${DEPLOYMENT}-frontend-sec-igm \
#  --named-ports http:8080,httpstatic:8080 \
#  --zone ${SECONDARY_ZONE}
#
# Then to see the IP that exposes the application, you can do:
# gcloud compute forwarding-rules list | grep application-${DEPLOYMENT}-l7lb

imports:
- path: application.jinja

resources:
- name: nodejs
  type: application.jinja
  properties:
    primaryZone: ZONE_TO_RUN
    secondaryZone: SECOND_ZONE_TO_RUN
    backendImage: gcr.io/deployment-manager-examples/mysql
    frontendImage: gcr.io/deployment-manager-examples/nodejsservice
    staticImage: gcr.io/deployment-manager-examples/nodejsservicestatic

Python

# 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 in two zones running nodejs
# for serving traffic using L7 loadbalancing. 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.
#
# NOTE: Due to the fact that IGM does not allow specifying service/port to
# created IG, you must run the following commands after creation of the
# template:
#
# export DEPLOYMENT=<DEPLOYMENT NAME>
# export PRIMARY_ZONE=<PRIMARY ZONE>
# export SECONDARY_ZONE=<SECONDARY ZONE>
#
# gcloud compute instance-groups unmanaged set-named-ports ${DEPLOYMENT}-frontend-pri-igm \
#  --named-ports http:8080,httpstatic:8080 \
#  --zone ${PRIMARY_ZONE}
#
# gcloud compute instance-groups unmanaged set-named-ports ${DEPLOYMENT}-frontend-sec-igm \
#  --named-ports http:8080,httpstatic:8080 \
#  --zone ${SECONDARY_ZONE}
#
# Then to see the IP that exposes the application, you can do:
# gcloud compute forwarding-rules list | grep application-${DEPLOYMENT}-l7lb

imports:
- path: application.py

resources:
- name: nodejs
  type: application.py
  properties:
    primaryZone: ZONE_TO_RUN
    secondaryZone: SECOND_ZONE_TO_RUN
    backendImage: gcr.io/deployment-manager-examples/mysql
    frontendImage: gcr.io/deployment-manager-examples/nodejsservice
    staticImage: gcr.io/deployment-manager-examples/nodejsservicestatic

Déployer votre configuration

Vous allez maintenant déployer vos ressources. À l'aide de la Google Cloud CLI, exécutez la commande suivante, en remplaçant éventuellement advanced-configuration-l7 par le nom de déploiement de votre choix. Sachez que le nom de votre déploiement sera automatiquement utilisé comme nom pour les ressources.

Dans cet exemple, le nom du déploiement est advanced-configuration-l7. Si vous décidez de le modifier, veillez à utiliser le nouveau nom du déploiement dans tous les exemples suivants.

gcloud deployment-manager deployments create advanced-configuration-l7 --config application.yaml

La réponse doit présenter des ressources semblables aux suivantes :

Waiting for create operation-1469468950934-5387966d431f0-49b11bc4-1421b2f0...done.
Create operation operation-1469468950934-5387966d431f0-49b11bc4-1421b2f0 completed successfully.
NAME                                               TYPE                             STATE      ERRORS
advanced-configuration-l7-application-fw           compute.v1.firewall              COMPLETED  []
advanced-configuration-l7-application-l7lb         compute.v1.globalForwardingRule  COMPLETED  []
advanced-configuration-l7-application-targetproxy  compute.v1.targetHttpProxy       COMPLETED  []
advanced-configuration-l7-application-urlmap       compute.v1.urlMap                COMPLETED  []
advanced-configuration-l7-backend                  compute.v1.instance              COMPLETED  []
advanced-configuration-l7-frontend-bes             compute.v1.backendService        COMPLETED  []
advanced-configuration-l7-frontend-hc              compute.v1.httpHealthCheck       COMPLETED  []
advanced-configuration-l7-frontend-it              compute.v1.instanceTemplate      COMPLETED  []
advanced-configuration-l7-frontend-pri-as          compute.v1.autoscaler            COMPLETED  []
advanced-configuration-l7-frontend-pri-igm         compute.v1.instanceGroupManager  COMPLETED  []
advanced-configuration-l7-frontend-sec-as          compute.v1.autoscaler            COMPLETED  []
advanced-configuration-l7-frontend-sec-igm         compute.v1.instanceGroupManager  COMPLETED  []
advanced-configuration-l7-static-service-bes       compute.v1.backendService        COMPLETED  []
advanced-configuration-l7-static-service-hc        compute.v1.httpHealthCheck       COMPLETED  []
advanced-configuration-l7-static-service-it        compute.v1.instanceTemplate      COMPLETED  []
advanced-configuration-l7-static-service-pri-as    compute.v1.autoscaler            COMPLETED  []
advanced-configuration-l7-static-service-pri-igm   compute.v1.instanceGroupManager  COMPLETED  []
advanced-configuration-l7-static-service-sec-as    compute.v1.autoscaler            COMPLETED  []
advanced-configuration-l7-static-service-sec-igm   compute.v1.instanceGroupManager  COMPLETED  []

Ajouter des libellés de service

Spécifiez ensuite les libellés de service adaptés à vos groupes d'instances gérés. Les libellés de service sont des métadonnées utilisées par le service d'équilibrage de charge pour regrouper des ressources.

Pour ajouter des libellés de service, exécutez les commandes suivantes en indiquant pour les zones principale et secondaire celles que vous avez sélectionnées dans le fichier de configuration du déploiement :

gcloud compute instance-groups unmanaged set-named-ports advanced-configuration-l7-frontend-pri-igm \
  --named-ports http:8080,httpstatic:8080 \
  --zone [PRIMARY_ZONE]

gcloud compute instance-groups unmanaged set-named-ports advanced-configuration-l7-frontend-sec-igm \
  --named-ports http:8080,httpstatic:8080 \
  --zone [SECONDARY_ZONE]

Tester votre configuration

Pour tester votre configuration, vous devez 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 list | grep advanced-configuration-l7-l7lb
advanced-configuration-l7-l7lb             107.178.249.126 TCP         advanced-configuration-l7-targetproxy

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

Dans un navigateur, accédez à l'adresse IP externe sur le port 8080. Par exemple, si votre adresse IP externe est 107.178.249.126, l'URL sera la suivante :

http://107.178.249.126:8080

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

http://107.178.249.126:8080?msg=hello_world!

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

hello_world!

Vous pouvez également consulter la page statique que vous avez créée ou vérifier l'état de votre application en accédant aux URL suivantes :

# Static web page
http://107.178.249.126:8080/static

# Health check
http://107.178.249.126:8080/_ah/health

Félicitations, votre configuration a bien été déployée.

Créer des images Docker (facultatif)

Docker vous permet d'automatiser et d'exécuter des logiciels dans des conteneurs. Les conteneurs vous permettent d'isoler différents services et peuvent tous s'exécuter sur une seule instance Linux.

Cet exemple utilise certaines images Docker existantes, mais vous pouvez également créer vos propres versions de ces images Docker. Pour découvrir comment créer des images de backend MySQL et des images d'interface Node.js, consultez la section Créer vos modèles de ressources.

Pour créer l'image Docker qui diffuse la page Web statique, procédez comme suit :

  1. Créez une instance de VM avec une image optimisée pour les conteneurs :

    gcloud compute instances create docker-playground \
      --image-family container-vm \
      --image-project google-containers \
      --zone us-central1-a \
      --machine-type f1-micro
    
  2. Connectez-vous à l'instance :

    gcloud compute ssh --zone us-central1-a docker-playground
    
  3. Créez un fichier nommé Dockerfile avec le contenu suivant :

    FROM node:latest
    
    RUN mkdir /var/www/
    ADD service.js /var/www/service.js
    WORKDIR /var/www/
    RUN npm install mysql
    
    CMD ["node", "service.js"]
    
  4. Créez un fichier nommé service.js avec le contenu suivant :

    var http = require('http');
    var url = require('url');
    
    console.log('Started static node server')
    
    http.createServer(function (req, res) {
      reqUrl = url.parse(req.url, true);
    
      res.useChunkedEncodingByDefault = false;
      res.writeHead(200, {'Content-Type': 'text/html'});
    
      if (reqUrl.pathname == '/_ah/health') {
        res.end('ok');
      } else if (reqUrl.pathname == '/exit') {
        process.exit(-1)
      } else {
          res.end('static server');
      }
    }).listen(8080, '0.0.0.0');
    
    console.log('Static server running at http://127.0.0.1:8080/');
    
  5. Créez l'image Docker en remplaçant username par votre nom d'utilisateur Docker Hub. Si vous ne possédez pas de nom d'utilisateur Docker Hub, créez-en un avant de continuer.

    sudo docker build --no-cache -t username/nodejsservicestatic .
    
  6. Envoyez les images dans le dépôt Docker :

    sudo docker push username/nodejsservicestatic
    

Vous disposez désormais des images Docker pour exécuter Node.js et MySQL. Vous pouvez afficher ces images dans le dépôt en recherchant leurs noms. Pour tester les images, vous pouvez remplacer toutes les instances de gcr.io/deployment-manager-examples/mysql et gcr.io/deployment-manager-examples/nodejsservice par vos images correspondantes.

Étapes suivantes

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

  • Reprenez cet exemple et créez une page Web plus robuste ou ajoutez d'autres services au serveur Web.
  • Apprenez-en plus sur les configurations ou les déploiements.
  • Essayez de créer vos propres configurations.