Deployment Manager の最初の一歩

## Google Cloud Deployment Managerとは

<https://cloud.google.com/deployment-manager>

アプリケーションに必要なリソースをyamlで宣言的に記述する、いわゆるInfrastructure as Codeを実現するためのサービスです。 AWSでいうところのCloud Formationですね。

説明するにあたっていくつが用語が出てくるので簡単に説明しておきます。

**リソース**

デプロイされる個々のインフラストラクチャの単位です。 Compute EngineのVMや、Cloud Storageのバケット、ネットワークのファイアウォール設定などデプロイされるもの全てです。

**デプロイメント**

アプリケーションをデプロイする単位。リソースを複数含めることができて、それらを一括で管理できます。

**構成ファイル**

yamlによって記述されるデプロイメントの定義。


## 特長

![img](https://i.gyazo.com/8701165817be33a7e2a9bd633b82caf7.png)

DeploymentManagerのページに行くと特長が書いてあるので見ていきましょう。


### 並行的なデプロイメント

何もしなければすべてのリソースは並行的に作成されます。 つまり、VMを1台作成しても100台作成してもほとんど時間は変わらないということですね。


### テンプレート

yaml + jinja または Python でテンプレートを作成することで、設定を使い回せます。


### 更新

デプロイメントのリソースは一括で管理されるので、一度に作成、一度に削除、コピーが簡単にできます。 


### 入出力パラメータ

作成されたリソースのうち動的に変わるパラメータ、例えばエメフェラルなIPなどを他のリソースの作成条件等に 簡単に使用することができます。


### スキーマファイル

テンプレートで与えられなかった値に対してデフォルト値を設けたり、バリデーションをかけたりできます。


### 参照

入出力パラメータを使用して生じた依存関係は、自動的に解決され、リソースの作成順序が保証されます。 循環参照など問題が起きた場合はデプロイ前にエラーを発します。


### プレビューモード

作成、変更内容を事前に確認できます。


## 使ってみる

最小構成のyamlファイルを使ってDeploymentManagerを使ってみます。

前提条件として `gcloud` コマンドがインストールされ、ログイン等の設定が終わっているものとします。 
<https://cloud.google.com/sdk/downloads>


### API type の確認

まずは、デプロイしようとするリソースのAPIを確認します。

DeploymentManagerでサポートされるリソースの一覧が下記に書かれています。 
<https://cloud.google.com/deployment-manager/docs/configuration/supported-resource-types>


### VMインスタンスの定義

今回は、VMインスタンスを作成することにします。 `compute.v1.instance` というリソース type を使用し、 `vm.yaml` という名前でデプロイを作成しました。

```vm.yaml
resources:
  - name: vm
    type: compute.v1.instance
```


### 設定の確認

構成ファイルが正しく記述されているかを調べるには、 `--preview` オプションを付けてデプロイするのが一番手っ取り早いです。 `glcoud` コマンドを使って `my-first-vm` という名前でをつけてデプロイします。 ファイルにデプロイ名を記載しないことで、他のデプロイとしてコピーを簡単に作成できます。 production, staging 環境など複数環境のコピーが簡単に作成できますね。

```bash
$ gcloud deployment-manager deployments create my-first-vm --config vm.yaml --preview
ERROR: (gcloud.deployment-manager.deployments.create) Error in Operation
- code: CONDITION_NOT_MET
  location: /deployments/my-first-vm/resources/vm->$.properties
  message: '"": domain: validation; keyword: properties; message: required property(ies)
    not found; missing: ["zone"]; required: ["zone"]'
```

どうやら `zone` というプロパティは必須のようです。 properties 以下に プロパティを定義します。

```yaml
resources:
  - name: vm
    type: compute.v1.instance
    properties:
      zone: us-central1-a
```


### 2度目のデプロイ

一度でもデプロイした構成は **失敗** や **プレビュー状態** であってもDeploymentManager上に作成されます。 2度目以降のデプロイには `update` コマンドを使用します。 `zone` を追加したデプロイを再度プレビューしてみましょう。

```bash
$ gcloud deployment-manager deployments update my-first-vm --config vm.yaml --preview
Update operation completed successfully.
NAME  TYPE                 STATE       ERRORS  INTENT
vm    compute.v1.instance  IN_PREVIEW  []      CREATE_OR_ACQUIRE
```

エラーが表示されなかったので、デプロイ可能になりました。 `IN_PREVIEW` という `STATE` はまだデプロイされていないことを意味します。


### プレビューのコミット

プレビューをコミットするには、設定ファイル(`--config`)を指定せずに `update` を実行します。

```bash
$ gcloud deployment-manager deployments update my-first-vm
ERROR: (gcloud.deployment-manager.deployments.update) Error in Operation
- code: RESOURCE_ERROR
  location: /deployments/my-first-vm/resources/vm
  message: "{\"ResourceType\":\"compute.v1.instance\",\"ResourceErrorCode\":\"400\"\
    ,\"ResourceErrorMessage\":{\"code\":400,\"errors\":[{\"domain\":\"global\",\"\
    message\":\"Invalid value for field 'resource.machineType': ''. Machine type specified\
    \ for the instance is not machine type.\",\"reason\":\"invalid\"}],\"message\"\
    :\"Invalid value for field 'resource.machineType': ''. Machine type specified\
    \ for the instance is not machine type.\",\"statusMessage\":\"Bad Request\",\"\
    requestPath\":\"https://www.googleapis.com/compute/v1/projects/[your-project]/zones/us-central1-a/instances\"\
    ,\"httpMethod\":\"POST\"}}"
```

`machineType` プロパティがないのでデプロイに失敗しました。このように、デプロイして初めて分かる構文ミスもあるので注意が必要です。


### 最小の構成ファイル

最終的に、デプロイに成功する最小の構成ファイルは以下のようになります。 `[your-project]` にはプロジェクトIDが入ります。

```yaml
resources:
  - name: vm
    type: compute.v1.instance
    properties:
      zone: us-central1-a
      machineType: zones/us-central1-a/machineTypes/f1-micro
      disks:
      - deviceName: boot
        boot: true
        initializeParams:
          sourceImage: projects/debian-cloud/global/images/family/debian-9
      networkInterfaces:
        - network: projects/[your-project]/global/networks/default
```

この状態で再度デプロイすると、 `STATE` が `COMPLETED` になります。

```bash
$ gcloud deployment-manager deployments update my-first-vm --config vm.yaml
Update operation completed successfully.
NAME  TYPE                 STATE      ERRORS  INTENT
vm    compute.v1.instance  COMPLETED  []
```


### 作成されたVMを確認

クラウドコンソール( console.cloud.google.com )から Compute Engine を開くと、VMが無事に作成されています。 ![img](https://i.gyazo.com/d1e46b65e68b58edb8e29124f24943b9.png)

Deploymentmanagerのページからもここに飛べます。 このVMには `goog-dm: my-first-vm` というタグが付いており、デプロイメントから作成されたことがわかります。

下の方にある ![img](https://i.gyazo.com/11a5447d84e7f0c0bd50d7b51066d698.png) ボタンを押すとこのVMの作成に使われたパラメータ一覧を見れます。

```json
{
  "cpuPlatform": "Intel Sandy Bridge",
  "creationTimestamp": "2018-12-11T01:37:48.242-08:00",
  "deletionProtection": false,
  "disks": [
    {
      "autoDelete": false,
      "boot": true,
      "deviceName": "boot",
      "guestOsFeatures": [
        {
          "type": "VIRTIO_SCSI_MULTIQUEUE"
        }
      ],
      "index": 0,
      "interface": "SCSI",
      "kind": "compute#attachedDisk",
      "licenses": [
        "projects/debian-cloud/global/licenses/debian-9-stretch"
      ],
      "mode": "READ_WRITE",
      "source": "projects/[your-project]/zones/us-central1-a/disks/vm",
      "type": "PERSISTENT"
    }
  ],
  "id": "3042933826887236484",
  "kind": "compute#instance",
  "labelFingerprint": "isLgN0Vp5Xc=",
  "labels": {
    "goog-dm": "my-first-vm"
  },
  "machineType": "projects/[your-project]/zones/us-central1-a/machineTypes/f1-micro",
  "metadata": {
    "fingerprint": "vp8l7LsNCFw=",
    "kind": "compute#metadata"
  },
  "name": "vm",
  "networkInterfaces": [
    {
      "fingerprint": "oe2Zo41r_OM=",
      "kind": "compute#networkInterface",
      "name": "nic0",
      "network": "projects/[your-project]/global/networks/default",
      "networkIP": "10.128.0.2",
      "subnetwork": "projects/[your-project]/regions/us-central1/subnetworks/default"
    }
  ],
  "scheduling": {
    "automaticRestart": true,
    "onHostMaintenance": "MIGRATE",
    "preemptible": false
  },
  "selfLink": "projects/[your-project]/zones/us-central1-a/instances/vm",
  "startRestricted": false,
  "status": "RUNNING",
  "tags": {
    "fingerprint": "42WmSpB8rSM="
  },
  "zone": "projects/[your-project]/zones/us-central1-a"
}
```

このJSONの中身は、REST Resource: instances ページに詳細が載っています。

<https://cloud.google.com/compute/docs/reference/rest/v1/instances>

[Output Only] になっていないものに着目すると、送信しなかった値のデフォルト値がわかります。 この中で以下の値はデフォルト値を使わないことを推奨します。

**autoDelete: false** 
VMの削除とともにディスクを削除する設定です。特に残す意図がないなら `true` にするべきです。


### 削除

デプロイメントを削除するには `delete` サブコマンドを使います。

```bash
$ gcloud deployment-manager deployments delete my-first-vm
```

`--delete-policy=ABANDON` オプションで、リソースを残したままデプロイメントを削除することもできます。

```bash
$ gcloud deployment-manager deployments delete my-first-vm --delete-policy=ABANDON
```


## テンプレート

先ほど作成した構成ファイルから、以下の値を変数として切り出すと便利そうです。

-   zone
-   machineType
-   project

テンプレートは構成ファイルからimportして使います。構成ファイルに記述した `properties` 以下の値が変数としてテンプレート上に展開されます。 さらに、 `properties` 以外にテンプレートとして有効な環境変数が予め用意されています。これらの環境変数は、DeploymentManagerが独自に用意するもので、 手動で編集することはできません。

<https://cloud.google.com/deployment-manager/docs/configuration/templates/use-environment-variables>


### テンプレートの作成

構成ファイルを `vm.jinja` という名前でコピーしてテンプレートを作成してみましょう。 2重の中括弧 `{{}}` で囲った部分が変数として展開される部分です。 先程の変数をテンプレートに変換すると以下のようになりました。

```vm.jinja
resources:
  - name: vm
    type: compute.v1.instance
    properties:
      zone: {{ properties['zone'] }}
      machineType: zones/{{ properties['zone'] }}/machineTypes/{{ properties['machineType'] }}
      disks:
      - deviceName: boot
        autoDelete: true
        boot: true
        initializeParams:
          sourceImage: {{ properties['sourceImage'] }}
      networkInterfaces:
        - network: projects/{{ env['project'] }}/global/networks/default
```


### 構成ファイルからのimport

元の `vm.yaml` から `vm.jinja` をインポートしましよう。 `resources` と同じトップレベルの階層に `imports` を追加して、 `vm.jinja` を import します。

`resources` → `type` は `vm.jinja` となります。

テンプレート化した部分を `properties` 以下に実際の値を追記します。

```vm.yaml
imports:
  - path: vm.jinja

resources:
  - name: hoge-vm
    type: vm.jinja
    properties:
      zone: us-central1-a
      machineType: f1-micro
      sourceImage: projects/debian-cloud/global/images/family/debian-9
```


### テンプレートの利用

テンプレートを用いることで `machineType` の異なる3つの VM が少ない記述で作成できました。

```vm.yaml
imports:
  - path: vm.jinja

resources:
  - name: vm1
    type: vm.jinja
    properties:
      zone: us-central1-a
      machineType: f1-micro
      sourceImage: projects/debian-cloud/global/images/family/debian-9

resources:
  - name: vm2
    type: vm.jinja
    properties:
      zone: us-central1-a
      machineType: n1-standard-1
      sourceImage: projects/debian-cloud/global/images/family/debian-9

resources:
  - name: vm3
    type: vm.jinja
    properties:
      zone: us-central1-a
      machineType: n1-standard-4
      sourceImage: projects/debian-cloud/global/images/family/debian-9
```


## 参考

公式のチュートリアル、サンプルコードが参考になります。


### チュートリアル

<https://cloud.google.com/deployment-manager/docs/tutorials>


### サンプル集

<https://github.com/GoogleCloudPlatform/deploymentmanager-samples>