サイトをリニューアルいたしました。(6/1)

GCPインフラ構築自動化の道 part1 ~Terraform 導入編~

ちょっとした設定ファイルでクラウドインフラを管理できるTerraformをご存じでしょうか?GCPのCloud Consoleでインスタンスを構築すること自体は簡単ですが、手作業で複数台構築するのはそれなりに時間がかかり、上司から構築手順書や設計書をきちんとしろなんて言われた日には管理が煩雑になります。せっかくのクラウド環境なのに手作業で管理するなんてもう限界!と思っている方にオススメしたい運用ツールです。

インフラ構築自動化という概念

様々なビジネスにおいてIT技術者不足が叫ばれるようになった昨今、サービス提供を円滑に進めるため心を砕く技術者の方は多くいらっしゃると思います。サーバーの運用主体が急速にGCP含むクラウドインフラ基盤へ移行しており、構築や運用作業はサーバールームのコンソール操作から解放され、WebUIやREST APIのみで作業が完結する時代になりました。

これまでのサーバー構築と言えば、人間が作業するための設計書や手順書を作成して、人間が紙の資料を見ながら1台1台手で作業し、テストも人間が行いました。即ち、インフラ構築はマンパワーで対応するのが当たり前だったのです。ところが、REST APIのみで作業が完結するということは、作業内容をマシンにやらせることができるため、設計~構築~テストを全て自動化可能です。マシンに作業させるために、設計や構築はコーディングの作業になります。これがいわゆる、Infrastructure as Codeという概念になります。

この記事で紹介するTerraformは、HashiCorpが開発したInfrastructure as Codeを実現するためのオープンソースソフトウェア(OSS)です。ソフトウェア構成は「terraformコマンド」の実行ファイルが1つだけであり、クラウド基盤と接続するためのプラグインは記述した設定ファイルに基づき、Terraformのリポジトリから自動で取得&導入するため、煩雑な導入作業は不要になっています。

Terraformを導入する

Terraformの導入は以下の5工程です。

  1. Terraformのzipファイルをダウンロードする。
  2. zipファイルを展開してTerraformの実行ファイルを取得する。
  3. Terraformの実行ファイルを適当なディレクトリに配置する。
  4. Terraformの実行ファイルを配置したディレクトリをPATH変数に設定する。
  5. Cloud SDKのデフォルト認証を行う。※設定済みの場合は不要

macOSユーザーやLinuxユーザーの方はコマンドライン環境やPATH変数設定に抵抗はないと思います。しかし、WindowsのPATH変数設定は分かりづらいため、執筆者の環境(Windows10 Pro 64bit)で実施した手順を紹介致します。

Terraformをインストールする(Windows編)

  1. 公式サイトのダウンロードページから、自分のPC(Terraformを動かす環境)のOSのリンクをクリックして、zipファイルをダウンロードしてください。ここでは、Windows 64-bitのリンクをクリックします。
  2. 執筆時点のバージョンは0.11.1でしたので、「terraform_0.11.1_windows_amd64.zip」というファイルがダウンロードされます。
    ※ダウンロード位置はブラウザ設定や操作により変わりますが、通常はダウンロードフォルダ(C:\Users\{ユーザー名}\Downloads)になります。
  3. zipファイルを展開して、「terraform.exe」をフォルダにコピーします。フォルダはどこでもよいですが、今回は「C:\opt\terraform\terraform.exe」に配置しました。
    ※以降の説明もこのフォルダに配置したことを前提とします。
  4. Windows10のCreators Update以降、コントロールパネルの場所がわかりにくくなってしまいましたが、「ファイル名を指定して実行」を開き「control sysdm.cpl」を入力して実行するとシステムのプロパティが起動します。
  5. [詳細設定]タブ→[環境変数]ボタンをクリックします。
  6. 環境変数ウィンドウが開きますので、ユーザー環境変数の”Path”を選択して[編集]ボタンを押します。
  7. [新規]ボタンを押して、テキストボックスに「C:\opt\terraform\」と入力し、Enterキーを押します。その後、[OK]ボタンを押します。
  8. 環境変数ウィンドウに戻りますので、[OK]ボタンを押します。これで、Path変数が有効化されました。

インストールが終わりましたら、試しにterraformコマンドを実行してみましょう。Windowsの場合はコマンドプロンプトかPowerShellを、macOSやLinuxの場合はターミナルを開いて以下のコマンドを実行してください。

コード
terraform version
以下のようにバージョン番号が表示されれば、インストールは成功です。
コード
Terraform v0.11.1

TerraformにおけるGCP認証について

terraformコマンドを導入しただけでは、GCPのプロダクトを操作することはできません。GCPプロジェクトの認証情報が必要になります。Terraformで使用できる認証は以下の2種類です。

  • Cloud SDKのアプリケーションデフォルト認証設定
  • GCPプロジェクトのサービスアカウントの認証キーJSONファイル

この記事では、Cloud SDKのアプリケーションデフォルト認証を使用することを前提として解説しています。Cloud SDKの導入手順については以下のapps-gcp記事を参照してください。

Cloud SDKをmac, Linux, Windowsにインストール・初期設定までの導入手順徹底解説!!

Cloud SDKでアプリケーションデフォルト認証が設定できているかは、gcloudコマンドを以下のように実行することで確認できます。

コード
gcloud auth list

アプリケーションデフォルト認証が設定されている場合

以下のように、Googleアカウントのメールアドレスが表示されます。

コード
> gcloud auth list
          Credentialed Accounts
ACTIVE  ACCOUNT
*       yourname@yourdomain.com

アプリケーションデフォルト認証が設定されていない場合

「No credentialed accounts.」というメッセージが表示されます。

コード
> gcloud auth list

No credentialed accounts.

To login, run:
 $ gcloud auth login `ACCOUNT`

アプリケーションデフォルト認証を設定する

デフォルト認証が設定されていない場合は、以下のコマンドを実行して、デフォルト認証の初期化を行ってください。

コード
gcloud init
手順の詳細はGoogle公式ドキュメント「Cloud SDK の初期化」を参照してください。

TerraformでGCEインスタンスを作る

ということで、早速TerraformでGCEインスタンスを作成してみましょう。
この記事は「apps-gcp-terraform-test」プロジェクトで作成しています。

前提条件

TerraformでGCPプロジェクトにGCEインスタンスを作成するには、GCPプロジェクトのAPIのうち、以下が有効である必要があります。

  • Google Compute Engine API
  • Google Service Management API

有効になっていない場合は、Cloud Consoleの「APIとサービス」→「ライブラリ」を開き、上記APIを有効化してください。

Terraform設定ファイル(tfファイル)を作る

Terraformでインスタンスを作成するには、リソースを定義する設定ファイルを作成する必要があります。(以降、Terraformの設定ファイルのことをtfファイルと呼称します。)
まずは、Terraform作業用のフォルダを作成して、そこに「compute_instance.tf」という名前でテキストファイルを作成してみましょう。
この記事では、Cドライブの下に「terraform-workspace」というフォルダを作成し、その中に「compute_instance.tf」というテキストファイルを作成します。
「compute_instance.tf」の内容は以下の通りです。

コード
provider "google" {
	project = "apps-gcp-terraform-test"
	region  = "asia-northeast1"
  }
  
  resource "google_compute_instance" "apps-gcp-terraform" {
	name         = "apps-gcp-terraform"
	machine_type = "g1-small"
	zone         = "asia-northeast1-a"
  
	boot_disk {
	  initialize_params {
		size  = 10
		type  = "pd-standard"
		image = "debian-cloud/debian-9"
	  }
	}
  
	network_interface {
	  network       = "default"
	  access_config = {}
	}
  
	service_account = {
	  scopes = ["logging-write", "monitoring-write"]
	}
  }
このtfファイルは、GCPプロジェクト「apps-gcp-terraform-test」の「asia-northeast1-a」ゾーンに対して以下のようなGCEインスタンスの作成を定義しています。

インスタンス名 apps-gcp-terraform
マシンタイプ g1-small
ゾーン asia-northeast1-a
ブートディスク サイズ:10GB
種類:標準の永続ディスク
OSイメージ:debian 9 (stretch)
ネットワークインターフェース ネットワーク: default
内部IP: エフェメラル
外部IP: エフェメラル
サービスアカウント デフォルトアカウント

Terraformコマンドを実行してインスタンスを作る

これで最低限の準備は整いました。Terraformコマンドの出番です。
ここで使用するコマンドは以下の3つです。

  • terraform init … 実行環境の初期化を行う
  • terraform plan … リソース作成の計画を表示する
  • terraform apply … リソース作成を行う

まずは、Terraformのワークスペースを初期化するため、先ほどtfファイルを配置したterraform-workspaceフォルダで「terraform init」コマンドを実行しましょう。

コード
> cd C:\terraform-workspace
> terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "google" (1.2.0)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.google: version = "~> 1.2"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
terraform initコマンドを実行すると、カレントディレクトリに「.terraform」サブディレクトリを作成し初期化を行います。また、実行環境がインターネット接続できる環境であれば自動的にプラグインをダウンロードします。「Terraform has been successfully initialized!」というメッセージが表示されれば、初期化成功です。
続いて「terraform plan」コマンドを実行します。
コード
PS C:\workspace-terraform> terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
Terraform will perform the following actions:
  + google_compute_instance.apps-gcp-terraform
      id:                                                  <computed>
      boot_disk.#:                                         "1"
      boot_disk.0.auto_delete:                             "true"
      boot_disk.0.device_name:                             <computed>
      boot_disk.0.disk_encryption_key_sha256:              <computed>
      boot_disk.0.initialize_params.#:                     "1"
      boot_disk.0.initialize_params.0.image:               "debian-cloud/debian-9"
      boot_disk.0.initialize_params.0.size:                "10"
      boot_disk.0.initialize_params.0.type:                "pd-standard"
      can_ip_forward:                                      "false"
      cpu_platform:                                        <computed>
      create_timeout:                                      "4"
      instance_id:                                         <computed>
      label_fingerprint:                                   <computed>
      machine_type:                                        "g1-small"
      metadata_fingerprint:                                <computed>
      name:                                                "apps-gcp-terraform"
      network_interface.#:                                 "1"
      network_interface.0.access_config.#:                 "1"
      network_interface.0.access_config.0.assigned_nat_ip: <computed>
      network_interface.0.address:                         <computed>
      network_interface.0.name:                            <computed>
      network_interface.0.network:                         "default"
      network_interface.0.subnetwork_project:              <computed>
      scheduling.#:                                        <computed>
      self_link:                                           <computed>
      service_account.#:                                   "1"
      service_account.0.email:                             <computed>
      service_account.0.scopes.#:                          "2"
      service_account.0.scopes.172152165:                  "https://www.googleapis.com/auth/logging.write"
      service_account.0.scopes.4177124133:                 "https://www.googleapis.com/auth/monitoring.write"
      tags_fingerprint:                                    <computed>
      zone:                                                "asia-northeast1-a"

Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
terraform planコマンドは、tfファイルとstateファイル(管理状態を保存するファイル)に基づいてリソース変更の計画を表示します。ただし、初回実行時はstateファイルが無いため、tfファイルのリソースを全て新規作成する計画を表示します。次に「terraform apply」コマンドを実行します。
コード
> terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
Terraform will perform the following actions:
  + google_compute_instance.apps-gcp-terraform
...(省略)...
Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_compute_instance.apps-gcp-terraform: Creating...
  boot_disk.#:                                         "" => "1"
  boot_disk.0.auto_delete:                             "" => "true"
  boot_disk.0.device_name:                             "" => "<computed>"
  boot_disk.0.disk_encryption_key_sha256:              "" => "<computed>"
  boot_disk.0.initialize_params.#:                     "" => "1"
  boot_disk.0.initialize_params.0.image:               "" => "debian-cloud/debian-9"
  boot_disk.0.initialize_params.0.size:                "" => "10"
  boot_disk.0.initialize_params.0.type:                "" => "pd-standard"
  can_ip_forward:                                      "" => "false"
  cpu_platform:                                        "" => "<computed>"
  create_timeout:                                      "" => "4"
  instance_id:                                         "" => "<computed>"
  label_fingerprint:                                   "" => "<computed>"
  machine_type:                                        "" => "g1-small"
  metadata_fingerprint:                                "" => "<computed>"
  name:                                                "" => "apps-gcp-terraform"
  network_interface.#:                                 "" => "1"
  network_interface.0.access_config.#:                 "" => "1"
  network_interface.0.access_config.0.assigned_nat_ip: "" => "<computed>"
  network_interface.0.address:                         "" => "<computed>"
  network_interface.0.name:                            "" => "<computed>"
  network_interface.0.network:                         "" => "default"
  network_interface.0.subnetwork_project:              "" => "<computed>"
  scheduling.#:                                        "" => "<computed>"
  self_link:                                           "" => "<computed>"
  service_account.#:                                   "" => "1"
  service_account.0.email:                             "" => "<computed>"
  service_account.0.scopes.#:                          "" => "2"
  service_account.0.scopes.172152165:                  "" => "https://www.googleapis.com/auth/logging.write"
  service_account.0.scopes.4177124133:                 "" => "https://www.googleapis.com/auth/monitoring.write"
  tags_fingerprint:                                    "" => "<computed>"
  zone:                                                "" => "asia-northeast1-a"

google_compute_instance.apps-gcp-terraform: Still creating... (10s elapsed)
google_compute_instance.apps-gcp-terraform: Creation complete after 16s (ID: apps-gcp-terraform)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
「Do you want to perform these actions? ~ Enter a value:」というメッセージが表示され、変更を加えて良いかの確認を求められます。(ちなみに、terraform applyの変更確認はv0.11.0以降の新機能です。)
“yes”と入力してEnterを押すと、インスタンス作成を行います。「Apply complete!」のメッセージが表示されたら、作成完了です。実際に作成されているか、gcloudコマンドでapps-gc-terraformインスタンスを見てみましょう。
コード
PS C:\workspace-terraform> gcloud compute instances describe apps-gcp-terraform --format=text
cpuPlatform:                                 Intel Broadwell
creationTimestamp:                           2017-11-23T21:16:47.058-08:00
deletionProtection:                          False
disks[0].autoDelete:                         True
disks[0].boot:                               True
disks[0].deviceName:                         persistent-disk-0
disks[0].index:                              0
disks[0].interface:                          SCSI
disks[0].kind:                               compute#attachedDisk
disks[0].licenses[0]:                        https://www.googleapis.com/compute/v1/projects/debian-cloud/global/licenses/debia
n-9-stretch
disks[0].mode:                               READ_WRITE
disks[0].source:                             https://www.googleapis.com/compute/v1/projects/apps-gcp-terraform-test/zones/asia-northeast1-
a/disks/apps-gcp-terraform
disks[0].type:                               PERSISTENT
id:                                          *******************
kind:                                        compute#instance
labelFingerprint:                            ************
machineTpe:                                 https://www.googleapis.com/compute/v1/projects/apps-gcp-terraform-test/zones/asia-northeast1-a/machineTypes/g1-small
metadata.fingerprint:                        ************
metadata.kind:                               compute#metadata
name:                                        apps-gcp-terraform
networkInterfaces[0].accessConfigs[0].kind:  compute#accessConfig
networkInterfaces[0].accessConfigs[0].name:  external-nat
networkInterfaces[0].accessConfigs[0].natIP: **.***.***.***
networkInterfaces[0].accessConfigs[0].type:  ONE_TO_ONE_NAT
networkInterfaces[0].kind:                   compute#networkInterface
networkInterfaces[0].name:                   nic0
networkInterfaces[0].network:                https://www.googleapis.com/compute/v1/projects/apps-gcp-terraform-test/global/networks/default
networkInterfaces[0].networkIP:              10.146.0.2
networkInterfaces[0].subnetwork:             https://www.googleapis.com/compute/v1/projects/apps-gcp-terraform-test/regions/asia-northeast1/subnetworks/default
scheduling.automaticRestart:                 True
scheduling.onHostMaintenance:                MIGRATE
scheduling.preemptible:                      False
selfLink:                                    https://www.googleapis.com/compute/v1/projects/apps-gcp-terraform-test/zones/asia-northeast1-a/instances/apps-gcp-terraform
serviceAccounts[0].email:                    *************-compute@developer.gserviceaccount.com
serviceAccounts[0].scopes[0]:                https://www.googleapis.com/auth/logging.write
serviceAccounts[0].scopes[1]:                https://www.googleapis.com/auth/monitoring.write
startRestricted:                             False
status:                                      RUNNING
tags.fingerprint:                            ************
zone:                                        https://www.googleapis.com/compute/v1/projects/apps-gcp-terraform-test/zones/asia-northeast1-a
※一部の情報を*でマスクしています。

apps-gcp-terraformインスタンスをgcloudコマンドで確認できました。apps-gcp-terraformインスタンスのパラメータも、「Terraform設定ファイル(tfファイル)を作る」で想定した内容になっていることが確認できます。

tfファイルの内容について

tfファイルを作成し、terraformコマンドを実行することで、GCEインスタンスが作成できることを確認しました。次はtfファイルに記述した内容が、GCEインスタンスにおける何のパラメータに対応するかを説明します。
まず、GCEインスタンスを含め、GCPのリソースを作成・管理する際に必ず記載しなければならない項目は provider セクションです。以下のように記載します。

コード
provider "google" {
  project     = "プロジェクトID"
  region      = "リージョン"
}
この記事ではGoogle Cloud Providerを使用するため、「provider “google”」を指定します。「project = 」に自分のプロジェクトIDを記載します。「region = 」のところは、デフォルトのリージョンを指定します。また、これはtfファイルのルールですが、文字列は全てダブルクオートで囲みます。

次はresourceセクションです。以下のように記載します。

コード
resource "リソース種別" "リソース識別名" {
    プロパティ = "設定値"    サブリソース {        プロパティ = "設定値"    }
}
リソース種別は、TerraformのGoogle Cloud Providerプラグインが扱う”リソース種別”を指定します。指定可能な”リソース種別”はTerraform公式ドキュメントの「Google Cloud Provider」に記載されています。”リソース識別名”はTerraform内部で処理するための名称です。
プロパティはリソース毎の設定の名称(GCEインスタンスであれば、インスタンス名、ゾーン、マシンタイプ等)になります。概ね、APIリファレンスの名称と同じですが、異なる部分もあるため、この記事で説明していないリソースやプロパティの設定は、Terraform公式ドキュメントを読んで下さい。

GCEインスタンスを作成する場合は、リソース種別に”google_compute_instance”を指定します。主なプロパティを以下に示します。

コード
resource "google_compute_instance" "{terraform resouce name}" {
  name         = "インスタンス名"
  machine_type = "マシンタイプ"
  project      = "プロジェクトID"
  zone         = "ゾーン"
  tags         = [ “example” ]

  boot_disk {
    initialize_params {
      size = ディスクサイズ
      type = "ディスクの種類"
      image = "OSイメージ"
    }    auto_delete = "ブール値"
  }  network_interface {
    network = "ネットワーク名"
    access_config = {}
  }
    metadata {    メタデータキー = “メタデータ値”  }
  service_account = {
    scopes = [ "logging-write", "monitoring-write" ]
  }
}
  • {terraform resouce name} … Terraform管理上の名前を記述。使用可能な文字は英数大小文字(A~Z,a~z,0~9)と記号(-,_)です。
  • name … “インスタンス名”を記述
  • machine_type … “マシンタイプ”を記述 (例: f1-micro、n1-standard-2、等)
  • project … “プロジェクトID”を記述。providerでプロジェクト名を記述している場合は省略可能、複数プロジェクトを管理するときに明記する。
  • zone … “ゾーン”を記述 (例: asia-northeast1-a)
  • tags … ネットワークタグを記述
  • boot_disk … ブートディスクに関する設定を記述
    • initialize_params … ブートディスクの初期化パラメータを記述
      • size … ディスクサイズをGB単位で記述
      • type … ディスクタイプを記述。標準の永続ディスクの場合は「pd-standard」、SSD永続ディスクの場合は「pd-ssd」と記述。
      • image … OSイメージを記述。「イメージプロジェクト名/イメージ名」または「イメージプロジェクト名/イメージファミリ」を記述する。プロジェクト名やイメージ名は「gcloud compute images list」で取得した内容を記述する。(例: centos-cloud/centos-7)
    • auto_delete … インスタンス削除と同時にディスクも削除するかを指定。省略時は”true”が指定される。
  • network_interface … ネットワークインターフェースに関する設定を記述
    • network … インターフェースを接続するネットワーク名を記述
    • access_config … 外部IPアドレスの設定を記述。エフェメラルの場合は{}のみを記述する。外部IPアドレスが不要な場合は、access_config行そのものを削除する。
  • metadata … インスタンスに設定するメタデータを「キー = ”値”」の形式で記述
    スタートアップスクリプト等を記述する際に使用する。代表的なメタデータキーを以下に示す。
  • startup-script … インスタンス起動時に実行するスクリプトの内容を記述。設定の詳細はこちらを参照。
  • shutdown-script … インスタンス停止時に実行するスクリプト内容を記述。設定の詳細はこちらを参照。
  • ssh-keys … SSHログイン用の公開鍵を記述。この設定については次回の記事で説明します。
  • service_account … インスタンスデフォルトサービスアカウントに関する設定を記述
    • scopes … サービスアカウントのアクセススコープを記述。[ ]でリスト表記し、,(カンマ)で区切る。記述可能な設定値はGCP公式ドキュメントの「gloud compute instances create」の–scopesオプションの項を参照。

tfファイルのプロパティを変更する

では、最初に作成したtfファイルに、startup-scriptのメタデータを追加してみます。
startup-scriptメタデータを追加すると、インスタンス起動時に、メタデータ設定した内容がスクリプトとして実行されます。

コード
provider "google" {
  project     = "apps-gcp-terraform-test"
  region      = "asia-northeast1"
}

resource "google_compute_instance" "apps-gcp-terraform" {
  name         = "apps-gcp-terraform"
  machine_type = "g1-small"
  project      = "apps-gcp-terraform-test"
  zone         = "asia-northeast1-a"

  boot_disk {
    initialize_params {
      size = 10
      type = "pd-standard"
      image = "debian-cloud/debian-9"
    }
  }

  network_interface {
    network = "default"
    access_config = {}
  }  
  metadata {    startup-script = "logger 'Test startup script'"  }  
  service_account = {
    scopes = [ "logging-write", "monitoring-write", ]
  }
}
ちなみに、”logger ‘Test startup script'”は、syslogに対して「Test startup script」というメッセージを出力する、というコマンドを意味します。

terraform planコマンドを実行してみます。

コード
> terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

google_compute_instance.apps-gcp-terraform: Refreshing state... (ID: apps-gcp-terraform)

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  ~ google_compute_instance.apps-gcp-terraform
      metadata.%:              "0" => "1"
      metadata.startup-script: "" => "logger 'Test startup script'"


Plan: 0 to add, 1 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
「Plan: 0 to add, 1 to change, 0 to destroy.」というメッセージに注目していただくと、追加0件、変更1件、削除0件と出ていることがわかります。この状態で「terraform apply」コマンドを実行してみましょう。
コード
> terraform apply

...(省略)...

google_compute_instance.apps-gcp-terraform: Modifying... (ID: apps-gcp-terraform)

  metadata.%:              "0" => "1"
  metadata.startup-script: "" => "logger 'Test startup script'"

google_compute_instance.apps-gcp-terraform: Still modifying... (ID: apps-gcp-terraform, 10s elapsed)
google_compute_instance.apps-gcp-terraform: Modifications complete after 12s (ID: apps-gcp-terraform)

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
反映が完了しました。実際にstartup-scriptメタデータが設定されたか確認してみましょう。
コード
> gcloud compute instances describe apps-gcp-terraform --format=text
creationTimestamp:                          2017-11-23T21:16:47.058-08:00
...(省略)...
metadata.fingerprint:                       ************
metadata.items[0].key:                      startup-script
metadata.items[0].value:                    logger 'Test startup script'
metadata.kind:                              compute#metadata
...(省略)...
設定通り動作するか確認するため、インスタンスを再起動してみましょう。
コード
> gcloud compute instances reset apps-gcp-terraform
Updated [https://www.googleapis.com/compute/v1/projects/apps-gcp-terraform-test/zones/asia-northeast1-a/instances/apps-gcp-terraform].
少し待ってから、インスタンスのsyslogを参照するコマンドを実行します。
コード
> gcloud compute ssh apps-gcp-terraform --command="sudo grep Test /var/log/syslog"
Nov 24 07:27:46 localhost root: Test startup script
“Test startup script”が表示されていることから、startup-scriptが動作していると判断することができます。

次は、インスタンスをスケールアップしてみます。tfファイルのmachine_typeを「g1-small」から「n1-standart-1」に修正します。

コード
provider "google" {
  project     = "apps-gcp-terraform-test"
  region      = "asia-northeast1"
}

resource "google_compute_instance" "apps-gcp-terraform" {
  name         = "apps-gcp-terraform"
  machine_type = "n1-standard-1"
  zone         = "asia-northeast1-a"

  boot_disk {
    initialize_params {
      size = 10
      type = "pd-standard"
      image = "debian-cloud/debian-9"
    }
  }

  network_interface {
    network = "default"
    access_config = {}
  }  
  metadata {    startup-script = “logger ‘Test startup script’”  }  
  service_account = {
    scopes = [ "logging-write", "monitoring-write", ]
  }
}
terraform planコマンドを実行してみます。
コード
> terraform plan
...(省略)...
Terraform will perform the following actions:

-/+ google_compute_instance.apps-gcp-terraform (new resource required)...(省略)...      machine_type:                                        "g1-small" => "n1-standard-1" (forces new resource)...(省略)...
Plan: 1 to add, 0 to change, 1 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
new resource required という、メッセージが表示されていることが分かります。これは、Terraformが対象のリソースを削除してから作り直す、ということを意味しています。また、「Plan: 1 to add, 0 to change, 1 to destroy.」というメッセージからも、「追加1件、変更0件、削除1件」ということが分かります。続けて、terraform applyコマンドを実行してみます。
コード
PS C:\workspace-terraform> terraform apply
google_compute_instance.apps-gcp-terraform: Refreshing state... (ID: apps-gcp-terraform)
...(省略)...
Do you want to perform these actions?

  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_compute_instance.apps-gcp-terraform: Destroying... (ID: apps-gcp-terraform)
google_compute_instance.apps-gcp-terraform: Still destroying... (ID: apps-gcp-terraform, 10s elapsed)
google_compute_instance.apps-gcp-terraform: Still destroying... (ID: apps-gcp-terraform, 20s elapsed)
google_compute_instance.apps-gcp-terraform: Still destroying... (ID: apps-gcp-terraform, 30s elapsed)
google_compute_instance.apps-gcp-terraform: Destruction complete after 37s
google_compute_instance.apps-gcp-terraform: Creating...
...(省略)...
  machine_type:                                        "" => "n1-standard-1"
...(省略)...
google_compute_instance.apps-gcp-terraform: Still creating... (10s elapsed)
google_compute_instance.apps-gcp-terraform: Creation complete after 16s (ID: apps-gcp-terraform)
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
メッセージから、Destroyの後にCreateが実行されていることが分かります。gcloud コマンドを見てみましょう。
コード
> gcloud compute instances describe apps-gcp-terraform --format=text
cpuPlatform:                                 Intel Broadwell
creationTimestamp:                           2017-11-23T23:49:02.044-08:00
...(省略)...
machineType:                                 https://www.googleapis.com/compute/v1/projects/apps-gcp-terraform-test/zones/asia-northeast1-a/machineTypes/n1-standard-1
...(省略)...
マシンタイプはn1-standard-1になりましたが、creationTimestampも更新されています。このことから、インスタンスが作り直されてしまったことが分かります。
Cloud Consoleやgcloudからマシンタイプを変更する場合は、インスタンスが停止状態であれば再作成なしに変更可能ですが、Terraformの仕様ではインスタンス再作成になってしまうようです。この点は注意しなければなりません。

プロパティ変更によるインスタンス削除と対策

さて、TerraformでGCEインスタンスを作成する場合、再作成を伴わずにに変更可能なプロパティは以下になります。

  • metadata … メタデータ
  • tags … ネットワークタグ
  • labels … ラベル

その他のプロパティを変更してterraform applyすると、インスタンス削除→再作成になってしまうため注意が必要です。ただ、修正ミスにより上記以外のプロパティを修正してterraform applyしてしまい、インスタンス再作成されてしまうと、Terraformをビジネスとして運用するには高いように思えます。Terraformでは、こうしたプロパティ変更による予期せぬ削除を防ぐため、lifecycle.prevent_destroy設定があります。この設定を追加することで、再作成が必要な変更が発生する場合にコマンドが停止するようにできます。
tfファイルのブートディスクサイズを30に変更し、lifecycle.prevent_destroy設定を追加してみます。ちなみに、ブートディスクサイズの変更も、Terraformではインスタンス再作成となります。

コード
provider "google" {
  project     = "apps-gcp-terraform-test"
  region      = "asia-northeast1"
}

resource "google_compute_instance" "apps-gcp-terraform" {
  name         = "apps-gcp-terraform"
  machine_type = "n1-standard-1"
  zone         = "asia-northeast1-a"
  
  boot_disk {
    initialize_params {
      size = 30
      type = "pd-standard"
      image = "debian-cloud/debian-9"
    }
  }

  network_interface {
    network = "default"
    access_config = {}
  }
  
  metadata {
    startup-script = "logger 'Test startup script'"
  }
  
  service_account = {
    scopes = [ "compute-ro", "logging-write", "monitoring-write", ]
  }
  
  lifecycle {
    prevent_destroy = true
  }
}
tfファイル修正後、terraform applyコマンドを実行します。
コード
> terraform apply

google_compute_instance.apps-gcp-terraform: Refreshing state... (ID: apps-gcp-terraform)

Error: Error running plan: 1 error(s) occurred:

* google_compute_instance.apps-gcp-terraform: 1 error(s) occurred:

* google_compute_instance.apps-gcp-terraform: google_compute_instance.apps-gcp-terraform: the plan would destroy this resource, but it currently has lifecycle.prevent_destroy set to true. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or adjust the scope of the plan using the -target flag.
terraformコマンドがエラー停止していることが分かります。英語のメッセージですが、apps-gcp-terraformインスタンスのリソースにlifecycle.prevent_destroy=trueが設定されているため、変更を継続できないことが記載されています。容易に削除されると困るリソースには、lifecycle.prevent_destroy=trueを追加すると良いと思います。

まとめ

本記事では、Terraformの導入方法と、GCEインスタンスを構築するtfファイルの作成について解説しました。構成管理ツールというと、導入や設定ファイルの書き方が難しい印象がありますが、Terraformは導入や設定ファイルの書き方も簡単です。Part2の記事も読んで頂ければ、Terraformのメリットをより実感頂けると思いますので、是非お読み下さい!

弊社クラウドエースでは、GCPのプロダクトのコンサルティングから構築支援、応用サービスまで幅広くご提案しております。GCPによるサービス構築のご相談もクラウドエースにおまかせください!!