フューチャー技術ブログ

GCP連載#10 Terraform ではなくCloud Deployment Manager を使ってみよう

はじめに

GCP 連載もいよいよ最終日です!

普段は、Terraform を使っているのですが、ふとCloud Deployment Manager を使ったことないことに気づきました。そこで、Cloud Deployment Manager について紹介したいと思います。

Cloud Deployment Manager とは

Cloud Deployment Manager とは、GCP のリソースをYAML で宣言的に記述し、デプロイできるサービスです。要は、AWS のCloudFormation などにあたるサービスです。

ちなみにですが、Cloud Deployment Manager は、テンプレートをPython やJinja2 を使って、パラメータ化することもできます。ざっくりですが、以下にCloud Deployment Manager と似たサービスをクラウド毎に記載します。

GCP AWS AWS Alibaba Cloud
Management tools Cloud Deployment Manager AWS CloudFormation Cloud Development Kit (CDK) Resource Orchestration Service
Format JSON, YAML, Python, Jinja2 JSON, YAML TypeScript, Python, Java, C# JSON

事前準備

事前に以下は実施済みであることを前提とします。

  • プロジェクト作成
  • gcloud コマンドが使える状態

まず、Deployment Manager のAPI を有効化し、次にgcloud コマンドでログインし、プロジェクトを指定します。
[MY_PROJECT]にあらかじめ作成したプロジェクトのID を指定します。

$ gcloud auth login
$ gcloud config set project [MY_PROJECT]

Cloud Deployment Manager 事始め

まずは、GCE を作成して慣れてみたいと思います。

定義ファイルの作成

YAML でリソースを定義します。

vm.yaml
resources:
- type: compute.v1.instance
name: test-vm
properties:
zone: asia-northeast1-a
machineType: https://www.googleapis.com/compute/v1/projects/[MY_PROJECT]/zones/asia-northeast1-a/machineTypes/f1-micro
disks:
- deviceName: boot
type: PERSISTENT
boot: true
autoDelete: true
initializeParams:
sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/family/debian-9
networkInterfaces:
- network: https://www.googleapis.com/compute/v1/projects/[MY_PROJECT]/global/networks/default
accessConfigs:
- name: External NAT
type: ONE_TO_ONE_NAT

定義ファイルの設定内容を以下に記載します。

  • name: インスタンス名を指定
  • zone: 作成するゾーンを指定
  • machineType: インスタンスタイプを指定

また、[MY_PROJECT]は、作成したプロジェクトのID を指定します。

リソースのデプロイ

リソースのデプロイは、gcloudをコマンドを使用してデプロイします。

まずは、プレビューで作成したいと思います。プレビューを指定すると作成や更新前にどのような変化をもたらすかを確認することができます。要は、dry-run 的な用途で使えるオプションです。

コマンドの構成について

コマンドの構成について以下に記載します。
詳細は、こちらを参考にしてください。

$ gcloud deployment-manager deployments COMMAND DEPLOYMENT_NAME --config [FILE_PATH] --preview
  • COMMAND: create, delete, update などを指定
  • DEPLOYMENT_NAME: デプロイの名前を指定(任意の名前)
  • FILE_PATH: 先ほど作成したリソース定義ファイルのパスを指定
  • –preview: dry-run 的に実行したい時に指定

それでは実際にcreateコマンドを実行したいと思います。

$ gcloud deployment-manager deployments create test --config vm.yaml --preview
The fingerprint of the deployment is xxx
Waiting for create [xxx]...done.
Create operation xxx completed successfully.
NAME TYPE STATE ERRORS INTENT
test-vm compute.v1.instance IN_PREVIEW [] CREATE_OR_ACQUIRE

実行結果のステータスが、IN_PREVIEWであることからプレビュー状態であることがわかります。
プレビュー実行後にブラウザで確認すると以下のように構成などを確認することができます。

ブラウザ上からデプロイすることも可能ですが、gcloudコマンドから実行したいと思います。
updateを指定し、DEPLOYMENT_NAMEを指定します(ここではtest)

$ gcloud deployment-manager deployments update test
The fingerprint of the deployment is xxx==
Waiting for update [xxx]...done.
Update operation xxx completed successfully.
NAME TYPE STATE ERRORS INTENT
test-vm compute.v1.instance COMPLETED []

実行結果から先ほどまではIN_PREVIEWだったが、COMPLETEDに変わっていることがわかります。

また、以下のブラウザからもプレビューからデプロイに変わっていることがわかります。

実際に、GCE コンソールに遷移すると作成されていることが確認できます。

その他のコマンドを紹介したいと思います。
listコマンドでデプロイの一覧を表示することができます。

$ gcloud deployment-manager deployments list
NAME LAST_OPERATION_TYPE STATUS DESCRIPTION MANIFEST ERRORS
test preview DONE []

describeコマンドでデプロイの詳細情報を確認することができます。

$ gcloud deployment-manager deployments describe test
---
fingerprint: xxx==
id: 'xxx'
insertTime: 'xxx'
manifest: manifest-xxx
name: test
operation:
endTime: 'xxx'
name: xxx
operationType: update
progress: 100
startTime: 'xxx'
status: DONE
user: xxx
NAME TYPE STATE INT
test-vm compute.v1.instance COMPLETED

もし、デプロイを削除したい場合は、deleteコマンドを使用します。
Do you want to continue (y/N)? は、yと入力します。

$ gcloud deployment-manager deployments delete test
The following deployments will be deleted:
- test

Do you want to continue (y/N)? y

Waiting for delete [xxx]...done.
Delete operation xxx completed successfully.

これでGCE の作成から削除までの一連の操作を行いました。

複数のVM インスタンスを作成

先ほど作成した定義ファイルを複製するかたちで作成します。
ファイル名は、vms.yamlとします。

vms.yaml
resources:
- type: compute.v1.instance
name: test-vm-01
properties:
zone: asia-northeast1-a
machineType: https://www.googleapis.com/compute/v1/projects/[MY_PROJECT]/zones/asia-northeast1-a/machineTypes/f1-micro
disks:
- deviceName: boot
type: PERSISTENT
boot: true
autoDelete: true
initializeParams:
sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/family/debian-9
networkInterfaces:
- network: https://www.googleapis.com/compute/v1/projects/[MY_PROJECT]/global/networks/default
accessConfigs:
- name: External NAT
type: ONE_TO_ONE_NAT
- type: compute.v1.instance
name: test-vm-02
properties:
zone: asia-northeast1-a
machineType: https://www.googleapis.com/compute/v1/projects/[MY_PROJECT]/zones/asia-northeast1-a/machineTypes/f1-micro
disks:
- deviceName: boot
type: PERSISTENT
boot: true
autoDelete: true
initializeParams:
sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/family/debian-9
networkInterfaces:
- network: https://www.googleapis.com/compute/v1/projects/[MY_PROJECT]/global/networks/default
accessConfigs:
- name: External NAT
type: ONE_TO_ONE_NAT

nameを01と02に分けるかたちでVM を2台分の定義を記述し、デプロイします。

$ gcloud deployment-manager deployments create test-vms --config vms.yaml
The fingerprint of the deployment is xxx==
Waiting for create [xxx]...done.
Create operation xxx completed successfully.
NAME TYPE STATE ERRORS INTENT
test-vm-01 compute.v1.instance COMPLETED []
test-vm-02 compute.v1.instance COMPLETED []

デプロイ後、ブラウザでも2台作成されていることがわかります。

このようにYAML で定義ファイルを書いて作成することができることがわかりました。
しかし、このような書き方では冗長さを感じてしまいますね。
次は、再利用性を高めるためのテンプレートを紹介します。

テンプレートを利用する

テンプレートを利用するとリソースを定義したファイルを独立させて再利用性を高めることができます。テンプレートを作成するには、PythonJinja2を使用することができます。Pythonを使用する場合は、Python 3.x で作成する必要があります(公式ドキュメントの日本語サイトだとPython 2.7 と記載がありますが、英語サイトだとPython 3.x と記載があるので注意)

テンプレートの作成

今回もGCE 2台作成し、合わせてファイアウォールの設定なども行いたいと思います。
以下のファイルを準備します。

.
├── firewall_template.py
├── gce_template.py
├── network_template.py
├── vm_template.py
└── vms.yaml

環境変数とプロパティについて

テンプレートのプロパティは、任意の変数を表します。
例えば以下のように変数を指定することができます。

context.properties['zone'],

また、デプロイメント固有の環境変数を指定することもできます。
環境変数の呼び出し方は、以下の構文を使用することでプロジェクトID を取得します。
詳細な環境変数については、こちらを参考にしてください。

ontext.env['project']

VM インスタンスを定義します。

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

def GenerateConfig(context):
resources = [{
'name': context.env['name'],
'type': 'compute.v1.instance',
'properties': {
'zone': context.properties['zone'],
'machineType': ''.join([COMPUTE_URL_BASE, 'projects/',
context.env['project'], '/zones/',
context.properties['zone'], '/machineTypes/',
context.properties['machineType']]),
'disks': [{
'deviceName': 'boot',
'type': 'PERSISTENT',
'boot': True,
'autoDelete': True,
'initializeParams': {
'sourceImage': ''.join([COMPUTE_URL_BASE, 'projects/',
'debian-cloud/global/',
'images/family/debian-9'])
}
}],
'networkInterfaces': [{
'network': '$(ref.' + context.properties['network']
+ '.selfLink)',
'accessConfigs': [{
'name': 'External NAT',
'type': 'ONE_TO_ONE_NAT'
}]
}]
}
}]
return {'resources': resources}

ネットワークを定義します。

def GenerateConfig(context):
resources = [{
'name': context.env['name'],
'type': 'compute.v1.network',
'properties': {
'routingConfig': {
'routingMode': 'REGIONAL'
},
'autoCreateSubnetworks': True
}
}]
return {'resources': resources}

ファイアウォールを定義します。

def GenerateConfig(context):
resources = [{
'name': context.env['name'],
'type': 'compute.v1.firewall',
'properties': {
'network': '$(ref.' + context.properties['network'] + '.selfLink)',
'sourceRanges': ['0.0.0.0/0'],
'allowed': [{
'IPProtocol': 'TCP',
'ports': [80]
}]
}
}]
return {'resources': resources}

ここで上記のテンプレートに指定する変数の定義を作成します。

gce_template.py
NETWORK_NAME = 'test-network'

def GenerateConfig(unused_context):
resources = [{
'name': 'test-vm-01',
'type': 'vm_template.py',
'properties': {
'machineType': 'f1-micro',
'zone': 'asia-northeast1-a',
'network': NETWORK_NAME
}
}, {
'name': 'test-vm-02',
'type': 'vm_template.py',
'properties': {
'machineType': 'g1-small',
'zone': 'asia-northeast1-a',
'network': NETWORK_NAME
}
}, {
'name': NETWORK_NAME,
'type': 'network_template.py'
}, {
'name': NETWORK_NAME + '-firewall',
'type': 'firewall_template.py',
'properties': {
'network': NETWORK_NAME
}
}]
return {'resources': resources}

最後にテンプレートをインポートするための定義ファイルを作成します。

imports:
- path: vm_template.py
- path: network_template.py
- path: firewall_template.py
- path: gce_template.py

resources:
- name: gce-setup
type: gce_template.py

デプロイ

実行方法は、今まで変わらずgcloud コマンドから実行します。

$ gcloud deployment-manager deployments create test-templates --config vms.yaml
The fingerprint of the deployment is xxx==
Waiting for update [xxxx]...done.
Update operation xxx completed successfully.
NAME TYPE STATE ERRORS INTENT
test-network compute.v1.network COMPLETED []
test-network-firewall compute.v1.firewall COMPLETED []
test-vm-01 compute.v1.instance COMPLETED []
test-vm-02 compute.v1.instance COMPLETED []

ブラウザ上でもデプロイされていることが確認できます。

エラーメモ

gcloud コマンドからデプロイした際に何かしらのエラーが発生したとします。コード修正後に以下のように再度“create”を実行すると“already exists and cannot be created”というエラーが発生します。

原因は、失敗したとしても中途半端にデプロイは作られてしまうため、既にあるといったエラーが発生します。

$ gcloud deployment-manager deployments create test-templates --config vms.yaml
ERROR: (gcloud.deployment-manager.deployments.create) ResponseError: code=409, message='projects/xxx/global/deployments/test-templates' already exists and cannot be created.

それでは、どのように解消するかというと以下のように“update”を使用します。

$ gcloud deployment-manager deployments update test-templates --config vms.yaml

この中途半端に作成されるのは解消されて欲しいですね。。

感想

GCP リソースをPython で操れることで、自由度の高いテンプレートを作成できる点は面白いと思いました。
また、Terraform は、tfstate というファイルをGCS やS3 に保存する必要があるので、アクセス権限や同時実行などを気にする必要がありますが、CloudFormation 同様に状態管理は、GCP 側でよしなにやってもらえるのもメリットだと感じました。

ただ、Google 自体はTerraformerなどのツールを作成していることから割とTerraform 推しなのでは?と感じるところもあります(個人的見解)

そのため、個人的には、GCP だけでなくAWS や、最近ではAlibaba Cloud などのクラウドを利用する機会が多いので、Terraform 一択になってしまうのが本音です。とはいえ、今回のように触れたことのないサービスを使ってみるのも新たな知見として楽しめるので、今後もいろいろ触れていきたいと思います。

この記事に類似するオススメする記事です。よければ合わせてチェックください。

参考

Google Cloud Deployment Manager documentation
gcloud deployment-manager
deploymentmanager-samples
環境変数
サポートしているリソースタイプ