HTTP 負荷分散型 Logbook アプリの作成

ここでは、フロントエンドに node.js を、バックエンドに MySQL を使用する Logbook アプリをビルドする手順について、高度なサンプルを通じて詳細に説明します。また、テンプレートを使用して、2 つのゾーン間で負荷分散を行う HTTP ロードバランサと、アプリを自動的にスケールするオートスケーラーを作成して接続します。

HTTP 負荷分散型デプロイメント リソース
HTTP 負荷分散型デプロイメント リソース(クリックで拡大)

このサンプルでは、ユーザーが Docker コンテナについて十分に理解していること、そして HTTP 負荷分散や自動スケーリング、マネージド インスタンス グループ、インスタンス テンプレートなど、Compute Engine リソースについて十分に理解していることを前提としています。

詳細なチュートリアルについては、スタートガイドまたは手順ガイドをご覧ください。

始める前に

テンプレートの作成

このサンプルでは、さまざまなリソースタイプを含むデプロイメントを作成します。初めに、各リソースを個別に定義する再利用可能なテンプレートを作成します。その後、最終構成において各テンプレートを利用します。

このサンプルを完了すると、次のリソースを含むデプロイが作成されます。

  • バックエンド MySQL 仮想マシン用の 1 つの Compute Engine インスタンス。
  • Docker イメージを使用するインスタンス テンプレート。
  • 2 つの異なるゾーンでそれぞれフロントエンド node.js サービスを実行する 2 つの自動スケーリング マネージド インスタンス グループ。
  • 静的データを提供するさらに 2 つの自動スケーリング マネージド インスタンス グループ。
  • ヘルスチェックと、各マネージド インスタンス グループ間でトラフィックを分散する HTTP ロードバランサ。

バックエンド テンプレートの作成

このアプリのバックエンドは、単一の Compute Engine インスタンスで、MySQL Docker コンテナを実行します。コンテナ最適化イメージを使用する Compute Engine インスタンスを定義するテンプレートを作成します。ファイルに 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

このテンプレートは containerImagemanifest などのさまざまな変数を定義します。これらは、構成を定義する際に指定されます。このテンプレートのみの場合、単一の仮想マシン インスタンスのみが作成されます。

Compute Engine インスタンス上でコンテナ イメージを使用する場合は、Deployment Manager マニフェストとは別のマニフェスト ファイルを用意して、使用するコンテナ イメージを Compute Engine に伝える必要があります。container_helper.[py|jinja] というヘルパー メソッドを作成して、コンテナ マニフェストを動的に定義します。

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)

フロントエンド テンプレートの作成

このアプリのフロントエンドで Node.js を実行し、ユーザーがウェブページにメッセージを投稿できるようにします。それぞれ 2 つのインスタンスを含む 2 つのマネージド インスタンス グループを作成します。一方はメインのマネージド インスタンス グループで、もう一方は負荷分散用のサブのマネージド インスタンス グループとなります。

次の手順でこうしたフロントエンド テンプレートを作成します。

  1. インスタンス テンプレートを作成します。

    マネージド インスタンス グループを作成するには、インスタンス テンプレート リソースが必要です。マネージド インスタンス グループとは、一元的に管理されるまったく同一の VM インスタンスで構成されるグループです。このサンプルではフロントエンド node.js インスタンス用のマネージド インスタンス グループを作成しますが、まず先にインスタンス テンプレートを作成する必要があります。

    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. 自動スケーリングされるマネージド インスタンス グループを作成します。

    インスタンス テンプレートの作成が完了したので、このインスタンス テンプレートを使用して自動スケーリング マネージド インスタンス グループを作成するテンプレートを定義できます。次の内容のファイルを autoscaled_group.[py|jinja] という名前で新規に作成します。

    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}
    

    対応するスキーマ ファイルを作成します。

    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. テンプレートを使用してリソースを作成します。

    この時点までで、リソースのプロパティを決定するベース テンプレートの定義が完了しています。このテンプレートを使用して、フロントエンドのセットアップを定義します。次の内容のファイルを service.[py|jinja] という名前で新規に作成します。

    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}
    

    対応するスキーマ ファイルを作成します。

    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
    

    このテンプレートによって何が作成されるのか、分解して確認してみましょう。

    1. 2 つのマネージド インスタンス グループ(メインとサブ)を作成します。

      このテンプレートは、autoscaled_group.[py|jinja] テンプレートを使用して、メインとサブの自動スケーリング マネージド インスタンス グループを作成します。

    2. 次に、バックエンド サービスとヘルス チェッカーを作成します。バックエンド サービスは HTTP 負荷分散に必要とされ、テンプレートによって、バックエンド サービス内のインスタンス グループのサービス容量が定義されます。今回の場合、メインとサブのマネージド インスタンス グループは、どちらもバックエンドの一部であり、バックエンド サービスのデフォルト プロパティが適用されます。

      デフォルトでは、バックエンド サービスは関連インスタンス グループの CPU 利用率に基づいて負荷分散を実行しますが、リクエスト数/秒(RPS)に基づいて負荷分散を実行させることもできます。

      注: バックエンド サービスを作成する場合は、常にヘルスチェックが必要となります。

統合テンプレートの作成

最後に、バックエンド テンプレートとフロントエンド テンプレートを結合する統合テンプレートを作成します。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}

対応するスキーマ ファイルを作成します。

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

このテンプレートは、フロントエンドとバックエンドに加え、次の追加リソースも定義します。

  1. メインとサブのマネージド インスタンス グループを持つ静的サービス。この静的サービスは、アプリ内の /static パスにあるウェブページを提供します。

  2. URL マップリソース。HTTP 負荷分散は、さまざまな URL を正しいパスにマッピングするための URL マップを必要とします。今回の場合、defaultService プロパティで示されるデフォルトパスは、すでに作成済みのバックエンド サービスとなります。/static に移動すると、URL マップは、pathMatchers セクションで定義されているとおりに、パスを静的サービスにマッピングします。

  3. グローバル転送ルールとターゲット HTTP プロキシ。アプリは 2 つのゾーンで負荷分散が実行されるため、単一の外部 IP アドレスを提供するグローバル転送ルールが必要となります。また、HTTP 負荷分散をセットアップするために、ターゲット HTTP プロキシが必要となります。

  4. ポート 8080 を通じたトラフィックを許可するファイアウォール ルール。

構成の作成

テンプレートと関連するスキーマの準備が整ったので、リソースをデプロイする構成を作成できます。次のコンテンツを含む application.yaml という名の構成ファイルを作成します。ZONE_TO_RUNSECONDARY_ZONE_TO_RUN は、デベロッパーが選択したメインゾーンとサブゾーンにそれぞれ置き換えてください。

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

構成のデプロイ

リソースをデプロイします。Google Cloud CLI で次のコマンドを実行します。必要に応じて、advanced-configuration-l7 のところは、任意のデプロイメント名で置き換えることもできます。なお、リソース名の設定時には、デベロッパーのデプロイメント名が自動的に使用されます。

このサンプルのデプロイメント名は advanced-configuration-l7 です。デプロイメント名を変更するよう選択した場合は、下記のすべてのサンプルで必ずそのデプロイメント名を使用するようにしてください。

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

返されるレスポンスは、次のリソースのようになります。

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

サービスラベルの追加

次に、マネージド インスタンス グループに対して、適切なサービスラベルを指定します。サービスラベルは、負荷分散サービスがリソースをグループ化する際に使用するメタデータです。

サービスラベルを追加するには、次のコマンドを実行します。メインゾーンとサブゾーンは、それぞれデプロイメント構成ファイルで選択したゾーンに合わせてください。

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]

構成のテスト

構成をテストするには、転送ルールのクエリを行うことで、トラフィックにサービスを提供する外部 IP アドレスを取得します。

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

この場合の外部 IP アドレスは 107.178.249.126 です。

ブラウザで、ポート 8080 を使用して外部 IP アドレスにアクセスします。たとえば、外部 IP が 107.178.249.126 の場合、URL は次のようになります。

http://107.178.249.126:8080

空白のページが表示されるはずです。これは想定どおりです。次に、メッセージをページに投稿します。次の URL に移動します。

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

メッセージが追加されたことを確認するメッセージが表示されます。メイン URL に戻ると、ページにメッセージが表示されているはずです。

hello_world!

また、作成した静的ページにアクセスして、アプリの状態をチェックすることもできます。次の URL にアクセスしてください。

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

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

これで構成は正常にデプロイされました。

(省略可)Docker イメージの作成

Docker を利用すると、コンテナ内でソフトウェアを自動的に実行できます。コンテナにより、各種サービスをコンテナ内で分離し、すべてを単一の Linux インスタンス上で実行できます。

今回のサンプルでは既存の Docker イメージを使用していますが、独自バージョンの Docker イメージを作成することもできます。MySQL バックエンド イメージと Node.js フロントエンド イメージの作成手順については、Docker イメージの作成をご覧ください。

静的ウェブページを提供する Docker イメージを作成するには:

  1. コンテナ最適化イメージを備えた新しい VM インスタンスを作成します。

    gcloud compute instances create docker-playground \
      --image-family container-vm \
      --image-project google-containers \
      --zone us-central1-a \
      --machine-type f1-micro
    
  2. インスタンスに接続します。

    gcloud compute ssh --zone us-central1-a docker-playground
    
  3. 次の内容のファイルを Dockerfile という名前で作成します。

    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. 次の内容のファイルを service.js という名前で作成します。

    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. Docker イメージをビルドします。username は、各自の Docker Hub ユーザー名に置き換えてください。Docker Hub ユーザー名を持っていない場合は、Docker イメージをビルドする前に、まずアカウントを作成してください。

    sudo docker build --no-cache -t username/nodejsservicestatic .
    
  6. イメージを Docker リポジトリに push します。

    sudo docker push username/nodejsservicestatic
    

Node.js と MySQL を実行する Docker イメージが完成しました。イメージ名を検索することで、リポジトリ上でイメージを見つけることができます。イメージを試してみる場合は、gcr.io/deployment-manager-examples/mysqlgcr.io/deployment-manager-examples/nodejsservice のすべてのインスタンスを各自のイメージと置き換えてください。

次のステップ

このサンプルを完成させた後は、次のステップに進めます。

  • 堅牢なウェブページの作成やウェブサーバー サービスの追加など、今回のサンプルを基にビルドを継続する。
  • 構成デプロイメントについて、さらに理解する。
  • 独自の構成を作成してみる。