ネットワーク負荷分散 Logbook アプリケーションのデプロイ

このチュートリアルでは、Node.js をフロントエンドとして使用し、バックエンドで MySQL を使用するサンプル Logbook アプリケーションをデプロイします。チュートリアルの最後には、次のリソースがデプロイされています。

ネットワーク負荷分散型デプロイ リソース(クリックして拡大)

Deployment Manager を初めて使用する方は、クイックスタートまたはステップバイステップ ガイドのチュートリアルをご覧ください。

始める前に

リソース テンプレートを作成する

このサンプルでは、さまざまな Google Cloud Platform リソースを含むデプロイを作成します。はじめに、各リソースを個別に定義するテンプレートを作成します。その後、最終構成において各テンプレートを呼び出します。デプロイには次のリソースが含まれています。

  • アプリケーションの MySQL データベースをホストする Compute Engine インスタンス。
  • フロントエンド インスタンスのインスタンス テンプレート。Node.js アプリケーション用の Docker イメージを使用します。
  • マネージド インスタンス グループ。インスタンス テンプレートを使用して 2 つのフロントエンド インスタンスを作成します。
  • オートスケーラー。受信トラフィックに基づいて追加のフロントエンド インスタンスを開始または停止します。
  • フロントエンド インスタンスが使用可能かどうかを確認するヘルスチェック。
  • 転送ルールが設定されているネットワーク ロードバランサ。
  • マネージド インスタンス グループのターゲット プール。

MySQL バックエンドのテンプレートを作成する

このアプリケーションのバックエンドは、単一の Compute Engine インスタンスで、MySQL Docker コンテナを実行します。container_vm.py テンプレートは、Docker コンテナを実行できる Compute Engine インスタンスを定義します。テンプレートの形式が正しく、必要なプロパティがすべて設定されているかどうか確認するには、スキーマ ファイルも必要です。

次のテンプレートをコピーするか、GitHub リポジトリからダウンロードします。

# Copyright 2016 Google Inc. All rights reserved.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    """Creates a Container VM with the provided Container manifest."""

    from container_helper import GenerateManifest

    COMPUTE_URL_BASE = 'https://www.googleapis.com/compute/v1/'

    def GlobalComputeUrl(project, collection, name):
      return ''.join([COMPUTE_URL_BASE, 'projects/', project,
                      '/global/', collection, '/', name])

    def ZonalComputeUrl(project, zone, collection, name):
      return ''.join([COMPUTE_URL_BASE, 'projects/', project,
                      '/zones/', zone, '/', collection, '/', name])

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

      base_name = context.env['name']

      # Properties for the container-based instance.
      instance = {
          'zone': context.properties['zone'],
          'machineType': ZonalComputeUrl(context.env['project'],
                                         context.properties['zone'],
                                         'machineTypes',
                                         'f1-micro'),
          'metadata': {
              'items': [{
                  'key': 'gce-container-declaration',
                  'value': GenerateManifest(context)
                  }]
              },
          'disks': [{
              'deviceName': 'boot',
              'type': 'PERSISTENT',
              'autoDelete': True,
              'boot': True,
              'initializeParams': {
                  'diskName': base_name + '-disk',
                  'sourceImage': GlobalComputeUrl('cos-cloud',
                                                  'images',
                                                  context.properties[
                                                      'containerImage'])
                  },
              }],
          'networkInterfaces': [{
              'accessConfigs': [{
                  'name': 'external-nat',
                  'type': 'ONE_TO_ONE_NAT'
                  }],
              'network': GlobalComputeUrl(context.env['project'],
                                          'networks',
                                          'default')
              }],
            'serviceAccounts': [{
                'email': 'default',
                'scopes': ['https://www.googleapis.com/auth/logging.write']
                }]
          }

      # Resources to return.
      resources = {
          'resources': [{
              'name': base_name,
              'type': 'compute.v1.instance',
              'properties': instance
              }]
          }

      return resources
    

テンプレートのスキーマ ファイルをダウンロードします

テンプレートには containerImage などの未定義のプロパティが含まれています。このようなプロパティは後のテンプレートで定義されます。

Compute Engine インスタンスでコンテナ イメージを使用する場合は、使用するコンテナ イメージを記述したマニフェスト ファイルも用意する必要があります。container_helper.py というヘルパー メソッドを作成して、コンテナ マニフェストを動的に定義します。

# 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 フロントエンドのテンプレートを作成する

アプリケーションのフロントエンドで Node.js を実行し、ユーザーがウェブページにメッセージを投稿できるようにします。フロントエンドは仮想マシン インスタンス グループ上で稼働し、オートスケーラーとロードバランサによってサポートされます。次の手順でフロントエンド テンプレートを作成します。

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

    マネージド インスタンス グループを作成して、同一の仮想マシン インスタンスのグループを単一のエンティティとして管理するには、インスタンス テンプレートが必要です。

    container_instance_template.py という名前のファイルを作成し、テンプレートのスキーマをダウンロードします。

    # 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. オートスケーラー、マネージド インスタンス グループ、ロードバランサを作成します。

    次に、container_instance_template.py テンプレートを使用する別のテンプレートを作成し、オートスケーラーやロードバランサ、マネージド インスタンス グループなど、残りのフロントエンド リソースを作成します。

このテンプレートには次のリソースが含まれています。

  1. container_instance_template.py テンプレートを使用するインスタンス テンプレート

  2. インスタンス テンプレートを使用するマネージド インスタンス グループ。マネージド インスタンス グループを参照するオートスケーラー。参照を使用することで、Deployment Manager が特定の順序でリソースを作成するように設定できます。この場合、マネージド インスタンス グループはオートスケーラーの前に作成されます。

  3. 次のリソースを含むネットワーク ロードバランサ

    • インターネットに公開された単一の外部 IP アドレスによる転送ルール
    • 作成済みのマネージド インスタンス グループを格納するターゲット プール
    • ターゲット プールに関連するヘルスチェック

frontend.py という名前のファイルを作成し、テンプレートのスキーマをダウンロードします。

# Copyright 2016 Google Inc. All rights reserved.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    """Creates autoscaled, network LB IGM running specified docker image."""

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

      # Pull the region out of the zone
      region = context.properties['zone'][:context.properties['zone'].rfind('-')]
      name = context.env['name']

      resources = [{
          'name': name,
          'type': 'container_instance_template.py',
          'properties': {
              'port': context.properties['port'],
              'dockerEnv': context.properties['dockerEnv'],
              'dockerImage': context.properties['dockerImage'],
              'containerImage': context.properties['containerImage']
          }
      }, {
          'name': name + '-igm',
          'type': 'compute.v1.instanceGroupManager',
          'properties': {
              'zone': context.properties['zone'],
              'targetSize': context.properties['size'],
              'targetPools': ['$(ref.' + name + '-tp.selfLink)'],
              'baseInstanceName': name + '-instance',
              'instanceTemplate': '$(ref.' + name + '-it.selfLink)'
          }
      }, {
          'name': name + '-as',
          'type': 'compute.v1.autoscaler',
          'properties': {
              'zone': context.properties['zone'],
              'target': '$(ref.' + name + '-igm.selfLink)',
              'autoscalingPolicy': {
                  'maxNumReplicas': context.properties['maxSize']
              }
          }
      }, {
          'name': name + '-hc',
          'type': 'compute.v1.httpHealthCheck',
          'properties': {
              'port': context.properties['port'],
              'requestPath': '/_ah/health'
          }
      }, {
          'name': name + '-tp',
          'type': 'compute.v1.targetPool',
          'properties': {
              'region': region,
              'healthChecks': ['$(ref.' + name + '-hc.selfLink)']
          }
      }, {
          'name': name + '-lb',
          'type': 'compute.v1.forwardingRule',
          'properties': {
              'region': region,
              'portRange': context.properties['port'],
              'target': '$(ref.' + name + '-tp.selfLink)'
          }
      }]
      return {'resources': resources}
    

テンプレートのスキーマ ファイルをダウンロードします

統合テンプレートを作成する

最後に、バックエンド テンプレートとフロントエンド テンプレートを統合するテンプレートを作成します。次の内容のファイルを nodejs.py という名前で作成します。

# Copyright 2016 Google Inc. All rights reserved.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    """Create nodejs template with the back-end and front-end templates."""

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

      backend = context.env['deployment'] + '-backend'
      frontend = context.env['deployment'] + '-frontend'
      firewall = context.env['deployment'] + '-application-fw'
      application_port = 8080
      mysql_port = 8080
      resources = [{
          'name': backend,
          'type': 'container_vm.py',
          'properties': {
              'zone': context.properties['zone'],
              'dockerImage': 'gcr.io/deployment-manager-examples/mysql',
              'containerImage': 'family/cos-stable',
              'port': mysql_port
          }
      }, {
          'name': frontend,
          'type': 'frontend.py',
          'properties': {
              'zone': context.properties['zone'],
              'dockerImage': 'gcr.io/deployment-manager-examples/nodejsservice',
              'port': application_port,
              # Define the variables that are exposed to container as env variables.
              'dockerEnv': {
                  'SEVEN_SERVICE_MYSQL_PORT': mysql_port,
                  'SEVEN_SERVICE_PROXY_HOST': '$(ref.' + backend
                                              + '.networkInterfaces[0].networkIP)'
              },
              # If left out will default to 1
              'size': 2,
              # If left out will default to 1
              'maxSize': 20
          }
      }, {
          'name': firewall,
          'type': 'compute.v1.firewall',
          'properties': {
              'allowed': [{
                  'IPProtocol': 'TCP',
                  'ports': [application_port]
              }],
              'sourceRanges': ['0.0.0.0/0']
          }
      }]
      return {'resources': resources}
    

テンプレートのスキーマ ファイルをダウンロードします

アプリケーションのフロントエンドには env["deployment"]-frontend という名前が設定されます。バックエンドにも同様の名前が設定されます。アプリケーションをデプロイすると、env["deployment"] は Deployment Manager によって自動的にデプロイ名に置換されます。

構成を作成する

すべてのテンプレートが用意できたら、リソースのデプロイに使用する構成を作成します。次の内容の構成ファイルを nodejs.yaml という名前で作成します。

# Copyright 2016 Google Inc. All rights reserved.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    # Launches an autoscaled, load-balanced frontend running nodejs for serving
    # traffic. Also launches a single MySQL container instance, wires the two
    # together using references, and passes them as env variables to the underlying
    # frontend Docker containers.
    imports:
    - path: nodejs.py

    resources:
    - name: nodejs
      type: nodejs.py
      properties:
        zone: ZONE_TO_RUN
    

ZONE_TO_RUN は、リソースのゾーン(us-central1-a など)で置き換えます。

リソースをデプロイする

では、リソースをデプロイしましょう。gcloud コマンドライン ツールを使用して、次のコマンドを実行します。

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

デプロイが完了すると、Deployment Manager は次のようなメッセージを表示し、作成されたリソースの要約を示します。

Waiting for create operation-1468522101491-5379cf2344539-5961abe8-a500190c...done.
    Create operation operation-1468522101491-5379cf2344539-5961abe8-a500190c completed successfully.
    NAME                                   TYPE                             STATE      ERRORS
    advanced-configuration-application-fw  compute.v1.firewall              COMPLETED  []
    advanced-configuration-backend         compute.v1.instance              COMPLETED  []
    advanced-configuration-frontend-as     compute.v1.autoscaler            COMPLETED  []
    advanced-configuration-frontend-hc     compute.v1.httpHealthCheck       COMPLETED  []
    advanced-configuration-frontend-igm    compute.v1.instanceGroupManager  COMPLETED  []
    advanced-configuration-frontend-it     compute.v1.instanceTemplate      COMPLETED  []
    advanced-configuration-frontend-lb     compute.v1.forwardingRule        COMPLETED  []
    advanced-configuration-frontend-tp     compute.v1.targetPool            COMPLETED  []
    

アプリケーションをテストする

アプリケーションをテストするため、転送ルールのクエリを行い、トラフィックを処理している外部 IP アドレスを取得します。

    $ gcloud compute forwarding-rules describe advanced-configuration-frontend-lb --region us-central1
    IPAddress: 104.154.81.44
    IPProtocol: TCP
    creationTimestamp: '2016-07-14T11:48:37.228-07:00'
    description: ''
    id: '9033201246750269546'
    kind: compute#forwardingRule
    name: advanced-configuration-frontend-lb
    portRange: 8080-8080
    region: https://www.googleapis.com/compute/v1/projects/myproject/regions/us-central1
    selfLink: https://www.googleapis.com/compute/v1/projects/myproject/regions/us-central1/forwardingRules/advanced-configuration-frontend-lb
    target: https://www.googleapis.com/compute/v1/projects/myproject/regions/us-central1/targetPools/advanced-configuration-frontend-tp
    

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

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

http://104.154.81.44:8080
    

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

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

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

hellothere!
    

これで、受信したメッセージをログに記録できるアプリケーションがデプロイされました。

次のステップ

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