Creazione di un'app di log con bilanciamento del carico HTTP

Questo esempio avanzato mostra come creare un'app di logbook che utilizza node.js per il frontend e MySQL per il backend. Il modello crea e connette anche un bilanciatore del carico HTTP che bilancia il carico tra due zone e un gestore della scalabilità automatica per scalare automaticamente l'app.

Risorse di deployment con bilanciamento del carico HTTP
Risorse di deployment con bilanciamento del carico HTTP (fai clic per ingrandire)

Questo esempio presuppone che tu abbia familiarità con i container Docker e le risorse di Compute Engine, in particolare il bilanciamento del carico HTTP, la scalabilità automatica, i gruppi di istanze gestite e i modelli di istanza.

Per altri tutorial introduttivi, consulta la Guida introduttiva o la Guida passo passo.

Prima di iniziare

Creazione di modelli

Questo esempio avvia un deployment con diversi tipi di risorse. Per iniziare, crea modelli riutilizzabili che definiscono queste risorse separatamente. In seguito, utilizzerai questi modelli nella configurazione finale.

Alla fine di questo esempio, avrai un deployment che contiene queste risorse:

  • Un'unica istanza Compute Engine per la macchina virtuale MySQL di backend.
  • Un modello di istanza che utilizza un'immagine Docker.
  • Due gruppi di istanze gestite con scalabilità automatica in due zone diverse che eseguono il servizio node.js di frontend.
  • Altri due gruppo di istanze gestite con scalabilità automatica che forniscono dati statici.
  • Un controllo di integrità e un bilanciatore del carico HTTP per distribuire il traffico tra i rispettivi gruppi di istanze gestite.

Creazione dei modelli di backend

Il backend di questa app è una singola istanza Compute Engine che esegue un container MySQL Docker. Creando un modello che definisca un'istanza Compute Engine che usa un'immagine ottimizzata per i container. Assegna al file il nome 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

Il modello definisce una serie di variabili, come containerImage e manifest, che verranno compilate quando definisci la configurazione. Questo modello da solo crea una singola istanza di macchina virtuale (VM).

Quando utilizzi immagini container sulle istanze di Compute Engine, devi anche fornire un file manifest (diverso dal manifest di Deployment Manager) per descrivere a Compute Engine quale immagine del container utilizzare. Crea un metodo helper denominato container_helper.[py|jinja] per definire in modo dinamico il file manifest del container:

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)

Creazione dei modelli di frontend

Il frontend di questa app esegue Node.js e consente agli utenti di pubblicare messaggi sulla pagina web. Ci saranno due gruppi di istanze gestite contenenti due istanze: un gruppo di istanze gestite principali e un gruppo di istanze gestite secondarie per il bilanciamento del carico.

Per creare questi modelli di frontend, utilizza le istruzioni seguenti.

  1. Creare un modello di istanza.

    Per creare un gruppo di istanze gestite, ovvero un gruppo di istanze VM identiche gestite centralmente, è necessaria una risorsa modello di istanza. Questo esempio crea un gruppo di istanze gestite per le istanze node.js del frontend, ma prima devi creare il modello di istanza.

    Definisci un file denominato 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. Creare un gruppo di istanze gestite con scalabilità automatica.

    Ora che hai un modello di istanza, puoi definire un modello che lo utilizza per creare un gruppo di istanze gestite con scalabilità automatica. Crea un nuovo file denominato autoscaled_group.[py|jinja] con i seguenti contenuti:

    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}
    

    Crea il file di schema corrispondente:

    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. Crea risorse utilizzando questi modelli.

    Fino a questo punto, hai definito i modelli di base che determinano le proprietà delle tue risorse. Utilizzando questi modelli, definisci la configurazione del frontend. Crea un nuovo file denominato service.[py|jinja] con il seguente contenuto:

    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}
    

    Crea il file di schema corrispondente:

    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
    

    Analizziamo in dettaglio che cosa sta creando questo modello:

    1. Due gruppi di istanze gestite, uno principale e uno secondario.

      Il modello utilizza il modello autoscaled_group.[py|jinja] per creare un gruppo di istanze gestite principali e secondarie con scalabilità automatica.

    2. Quindi, il modello crea un servizio di backend e un controllo di integrità. Per il bilanciamento del carico HTTP è necessario un servizio di backend che definisce la capacità di gestione dei gruppi di istanze al suo interno. In questo caso, i gruppi di istanze gestite principali e secondarie fanno parte di questo backend e si applicano le proprietà predefinite del servizio di backend.

      Per impostazione predefinita, un servizio di backend esegue il bilanciamento del carico in base all'utilizzo della CPU dei gruppi di istanze associati, ma è anche possibile bilanciare il carico in base alle richieste al secondo (RPS).

      Nota: un controllo di integrità è sempre richiesto quando si crea un servizio di backend.

Creazione di un modello unificatore

Infine, crea un modello unificante che combini i modelli di backend e frontend. Crea un nuovo file denominato 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}

Crea un file di schema corrispondente:

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

Oltre al frontend e al backend, il modello definisce anche alcune risorse aggiuntive:

  1. Un servizio statico con gruppi di istanze gestite principali e secondarie. Questo servizio statico pubblica una pagina web che si trova nel percorso /static della tua app.

  2. Una risorsa mappa URL. Il bilanciamento del carico HTTP richiede una mappa degli URL per mappare i diversi URL ai percorsi corretti. In questo caso, il percorso predefinito, indicato dalla proprietà defaultService, è il servizio di backend che hai creato in precedenza. Se un utente va a /static, la mappa URL mapperà quel percorso al servizio statico, come definito nella sezione pathMatchers.

  3. Una regola di forwarding globale e un proxy HTTP di destinazione. Poiché l'app viene bilanciata del carico tra due zone distinte, avrai bisogno di una regola di forwarding globale che gestisca un unico indirizzo IP esterno. Inoltre, per la configurazione del bilanciamento del carico HTTP è necessario un proxy HTTP di destinazione.

  4. Una regola firewall che consente il traffico attraverso la porta 8080.

Creazione della configurazione

Ora che i tuoi modelli e gli schemi correlati sono pronti, puoi creare una configurazione per il deployment di queste risorse. Crea un file di configurazione denominato application.yaml con i contenuti seguenti e sostituisci ZONE_TO_RUN e SECONDARY_ZONE_TO_RUN con le zone principali e secondarie che preferisci.

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

Deployment della configurazione

Ora esegui il deployment delle risorse. Utilizzando Google Cloud CLI, esegui questo comando, scegliendo facoltativamente di sostituire advanced-configuration-l7 con un nome di deployment a tua scelta. Ricorda che il nome del deployment sarà utilizzato automaticamente per assegnare un nome alle risorse.

In questo esempio, il nome del deployment è advanced-configuration-l7. Se scegli di modificare il nome del deployment, assicurati di utilizzare quel nome in tutti i seguenti esempi.

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

La risposta dovrebbe essere simile alle seguenti risorse:

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

Aggiungere etichette di servizio

Quindi, specifica le etichette di servizio appropriate per i gruppi di istanze gestite. Le etichette di servizio sono metadati utilizzati dal servizio di bilanciamento del carico per raggruppare le risorse.

Per aggiungere etichette di servizio, esegui questi comandi, abbinando le zone primarie e secondarie a quelle selezionate nel file di configurazione del deployment:

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]

Test della configurazione

Per testare la configurazione, recupera l'indirizzo IP esterno che gestisce il traffico eseguendo una query sulla regola di forwarding:

gcloud compute forwarding-rules list | grep advanced-configuration-l7-l7lb
advanced-configuration-l7-l7lb             107.178.249.126 TCP         advanced-configuration-l7-targetproxy

In questo caso, l'IP esterno è 107.178.249.126.

In un browser, visita l'indirizzo IP esterno sulla porta 8080. Ad esempio, se il tuo IP esterno è 107.178.249.126, l'URL sarà:

http://107.178.249.126:8080

Dovresti visualizzare una pagina vuota. Quindi, pubblica un messaggio nella pagina. Vai al seguente URL:

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

Verrà visualizzata la conferma dell'aggiunta del messaggio. Torna all'URL principale e ora la pagina dovrebbe contenere il messaggio:

hello_world!

Puoi anche visitare la pagina statica che hai creato o controllare lo stato della tua app visitando i seguenti URL:

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

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

Complimenti, hai eseguito il deployment della configurazione.

(Facoltativo) Creazione di immagini Docker

Docker consente di automatizzare ed eseguire il software all'interno dei container. I container ti consentono di isolare diversi servizi all'interno di container che possono essere eseguiti su una singola istanza Linux.

In questo esempio sono state utilizzate alcune immagini Docker esistenti, ma puoi anche creare versioni personalizzate di queste immagini Docker. Puoi trovare le istruzioni per creare le immagini backend MySQL e le immagini frontend Node.js nella sezione Creazione di modelli di risorse.

Per creare l'immagine Docker che pubblica la pagina web statica:

  1. Crea una nuova istanza VM con un'immagine ottimizzata per i container:

    gcloud compute instances create docker-playground \
      --image-family container-vm \
      --image-project google-containers \
      --zone us-central1-a \
      --machine-type f1-micro
    
  2. Connettiti all'istanza:

    gcloud compute ssh --zone us-central1-a docker-playground
    
  3. Crea un file denominato Dockerfile con il seguente contenuto:

    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. Crea un file denominato service.js con il seguente contenuto:

    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. Crea l'immagine Docker, sostituendo username con il tuo nome utente Docker Hub. Se non hai un nome utente Docker Hub, creane uno prima di creare l'immagine Docker.

    sudo docker build --no-cache -t username/nodejsservicestatic .
    
  6. Esegui il push delle immagini nel repository Docker:

    sudo docker push username/nodejsservicestatic
    

Ora hai le immagini Docker per eseguire Node.js e MySQL. Puoi effettivamente vedere queste immagini nel repository cercando i nomi delle immagini. Per provare le immagini, puoi sostituire tutte le istanze di gcr.io/deployment-manager-examples/mysql e gcr.io/deployment-manager-examples/nodejsservice con le tue immagini corrispondenti.

Passaggi successivi

Dopo aver completato questo esempio, puoi:

  • Continua a sviluppare questo esempio creando una pagina web più solida o aggiungendo altri servizi al server web.
  • Scopri di più sulle configurazioni o sui deployment.
  • Prova a creare configurazioni personalizzate.