既存のGCPプロジェクトをterraformで管理できるようにする

## 経緯

- 共有VPCの機能検証のためにとりあえずコンソールからプロジェクト作って設定とかしてた
- ただ、今後考えるとコードで管理してCIで反映とかしたくなったので、手で作った分をコードとして管理できるようにしたかった

## 注意

- Terraformの設定ファイルの記述の細かい説明等はしません
    - 適宜公式ドキュメントを参照ください([Provider: Google Cloud Platform - Terraform by HashiCorp](https://www.terraform.io/docs/providers/google/index.html))

## 2019/05/07追記

GCP版Terraformingが来たのでもうこんなめんどくさいことやらんでもいいんやで

https://github.com/GoogleCloudPlatform/terraformer

## 前提条件

- 以下のような環境がコンソールを用いて作成されている(細かい設定は省略)
    - VPC
        - name: wordpress
        - mode: カスタム
        - subnet:
            - wordpress-asia-northeast1
                - region: asia-northeast1
                - range: 10.0.0.0/9
    - Firewall rule
        - wordpress-allow-icmp
            - icmpでの通信を許可
        - wordpress-allow-ssh
            - tcp:22での通信を許可
        - wordpress-allow-https
            - tcp:443での通信を許可
            - target tag: wordpress
    - GCE
        - name: wordpress
        - region: asia-northeast1
        - zone: asia-northeast1-b
        - machine type: micro
        - network: wordpress
        - subnetwork: wordpress-asia-northeast1
        - network tags: wordpress

## 事前準備

- terraformの設定ファイル格納用のGCSバケット作成(権限あればなんでもいい)
- 対象プロジェクトからcloud shellを起動(作業環境)
- 作業用ディレクトリ切ってterraform実行用のスクリプト配置

```terraform.sh
#!/bin/bash
docker run -it --rm -v $(pwd):/workspace -v ~/.config/gcloud:/root/.config/gcloud --workdir /workspace hashicorp/terraform:light $@
```

## 実作業

### 1. コード書く

- まずはコードかしたい部分のterraformのコードを書きます。
- ちなみにこの時点ではコードと実態が完全一致しなくても大丈夫です。
    - `./terraform.sh validate` で何も表示されなければOK。
- 書き方がわからない場合は[公式ドキュメント](https://www.terraform.io/docs/providers/google/index.html)を探すと吉。

```wordpress.tf
# 基本設定
provider "google" {
  project = "terraform-practice-220905"
  region  = "asia-northeast1"
  version = "~> 1.19"
}

terraform {
  backend "gcs" {
    bucket  = "terraform-practice-220905"
    path    = "terraform.tfstate"
    project = "terraform-practice"
  }
}

# VPC設定
resource "google_compute_network" "wordpress-vpc" {
  name                    = "wordpress" # 実際のvpcネットワーク名に合わせる
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "wordpress-subnet" {
  name                     = "wordpress-asia-northeast1"
  ip_cidr_range            = "10.0.0.0/9"
  network                  = "${google_compute_network.wordpress-vpc.self_link}"
  enable_flow_logs         = "false"
  private_ip_google_access = "false"
}

# Firewall Rule設定
resource "google_compute_firewall" "icmp" {
  name    = "wordpress-allow-icmp"
  network = "${google_compute_network.wordpress-vpc.self_link}"

  allow {
    protocol = "icmp"
  }
}

resource "google_compute_firewall" "ssh" {
  name    = "wordpress-allow-ssh"
  network = "${google_compute_network.wordpress-vpc.self_link}"

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }
}

resource "google_compute_firewall" "https" {
  name    = "wordpress-allow-https"
  network = "${google_compute_network.wordpress-vpc.self_link}"

  allow {
    protocol = "tcp"
    ports    = ["443"]
  }

  target_tags = ["wordpress"]
}

# GCE設定
resource "google_compute_instance" "wordpress" {
  name         = "wordpress"
  machine_type = "f1-micro"
  zone         = "asia-northeast1"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }

  network_interface {
    network    = "${google_compute_network.wordpress-vpc.self_link}"
    subnetwork = "${google_compute_subnetwork.wordpress-subnet.self_link}"

    access_config {
      // Ephemeral IP
    }
  }
}
```

### 2. 既存の設定内容をimportする

- terraformには既存の設定内容をimportするためのコマンドが用意されています。
- `terraform import #{データソース名}.#{設定ファイル上の名前} #{インポート対象のリソースの名前}`
    - インポート対象のリソース名の指定の仕方が一部特殊なものもあります。
    - 公式ドキュメントの各データソースごとの説明ページにインポートに関する記載もありますので、うまくいかない場合はそちらを参照ください。
    - もしかしたら先に`terraform init`しないといけないかも。

```import.sh
$ ./terraform.sh import google_compute_network.wordpress-vpc wordpress
$ ./terraform.sh import google_compute_subnetwork.wordpress-subnet wordpress-asia-northeast1
$ ./terraform.sh import google_compute_firewall.icmp wordpress-allow-icmp
$ ./terraform.sh import google_compute_firewall.ssh wordpress-allow-ssh
$ ./terraform.sh import google_compute_firewall.https wordpress-allow-https
$ ./terraform.sh import google_compute_instance.wordpress terraform-practice-220905/asia-northeast1-b/wordpress
```

#### importにミスったら

例えば、ssh用のfirewallの設定をインポートするつもりが、icmp用のfirewallの設定をインポートしてしまった場合、一度stateを削除してインポートし直せばOKです。

```
# ミスってインポート
$ ./terraform.sh import google_compute_firewall.ssh wordpress-allow-icmp

$ ./terraform.sh state list
google_compute_firewall.ssh # 間違ってインポートしたものを確認
google_compute_network.wordpress-vpc
google_compute_subnetwork.wordpress-subnet

# 削除
./terraform.sh state rm google_compute_firewall.ssh

# 再インポート
$ ./terraform.sh import google_compute_firewall.ssh wordpress-allow-ssh
```

### 3. コードと実態の差分確認・修正

`terraform plan`を実行するとコードと実態の差分が確認できるので、差分に合わせてコードを修正していく。

```
$ ./terraform.sh plan

-------------- 省略 --------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

-/+ google_compute_instance.wordpress (new resource required)
      id:                                                  "wordpress" => <computed> (forces new resource)
      boot_disk.#:                                         "1" => "1"
      boot_disk.0.auto_delete:                             "true" => "true"
      boot_disk.0.device_name:                             "wordpress" => <computed>

-------------- 省略 --------------

      tags.#:                                              "1" => "0"
      tags.678694731:                                      "wordpress" => ""
      tags_fingerprint:                                    "AG-OvsszYew=" => <computed>
      zone:                                                "asia-northeast1-b" => "asia-northeast1" (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.
```

> `zone:                                                "asia-northeast1-b" => "asia-northeast1" (forces new resource)`

とりあえずzoneが違うので強制再作成しようとしているので、コードを直します。

```
$ diff wordpress.tf.org wordpress.tf
65c65
<   zone         = "asia-northeast1"
---
>   zone         = "asia-northeast1-b"
```

修正後、再度差分を確認します。

```
$ ./terraform.sh plan
-------------- 省略 --------------

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.wordpress
      create_timeout:                      "" => "4"
      service_account.#:                   "1" => "0"
      service_account.0.scopes.#:          "6" => "0"
      service_account.0.scopes.1632638332: "https://www.googleapis.com/auth/devstorage.read_only" => ""
      service_account.0.scopes.172152165:  "https://www.googleapis.com/auth/logging.write" => ""
      service_account.0.scopes.316356861:  "https://www.googleapis.com/auth/service.management.readonly" => ""
      service_account.0.scopes.3663490875: "https://www.googleapis.com/auth/servicecontrol" => ""
      service_account.0.scopes.3859019814: "https://www.googleapis.com/auth/trace.append" => ""
      service_account.0.scopes.4177124133: "https://www.googleapis.com/auth/monitoring.write" => ""
      tags.#:                              "1" => "0"
      tags.678694731:                      "wordpress" => ""


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.
```

今度はcreate_timeout,scopeとtagsに差分があるので修正します。(create_timeoutはどうでもいいので、差分あっても無視するように設定することで対処)

```
65a66
>   tags         = ["wordpress"]
79a81,97
>   }
>
>   service_account {
>     scopes = [
>       "https://www.googleapis.com/auth/devstorage.read_only",
>       "https://www.googleapis.com/auth/logging.write",
>       "https://www.googleapis.com/auth/service.management.readonly",
>       "https://www.googleapis.com/auth/servicecontrol",
>       "https://www.googleapis.com/auth/trace.append",
>       "https://www.googleapis.com/auth/monitoring.write",
>     ]
>   }
>
>   lifecycle {
>     ignore_changes = [
>       "create_timeout"
>     ]
```

最後にもう一度差分確認。

```
$ ./terraform.sh plan
-------------- 省略 --------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
```

`No changes.`って出れば修正完了です。お疲れ様でした。

おまけで修正後のTFファイルも載せます。

```wordpress.tf
# 基本設定
provider "google" {
  project = "terraform-practice-220905"
  region  = "asia-northeast1"
  version = "~> 1.19"
}

terraform {
  backend "gcs" {
    bucket  = "terraform-practice-220905"
    path    = "terraform.tfstate"
    project = "terraform-practice"
  }
}

# VPC設定
resource "google_compute_network" "wordpress-vpc" {
  name                    = "wordpress" # 実際のvpcネットワーク名に合わせる
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "wordpress-subnet" {
  name                     = "wordpress-asia-northeast1"
  ip_cidr_range            = "10.0.0.0/9"
  network                  = "${google_compute_network.wordpress-vpc.self_link}"
  enable_flow_logs         = "false"
  private_ip_google_access = "false"
}

# Firewall Rule設定
resource "google_compute_firewall" "icmp" {
  name    = "wordpress-allow-icmp"
  network = "${google_compute_network.wordpress-vpc.self_link}"

  allow {
    protocol = "icmp"
  }
}

resource "google_compute_firewall" "ssh" {
  name    = "wordpress-allow-ssh"
  network = "${google_compute_network.wordpress-vpc.self_link}"

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }
}

resource "google_compute_firewall" "https" {
  name    = "wordpress-allow-https"
  network = "${google_compute_network.wordpress-vpc.self_link}"

  allow {
    protocol = "tcp"
    ports    = ["443"]
  }

  target_tags = ["wordpress"]
}

resource "google_compute_instance" "wordpress" {
  name         = "wordpress"
  machine_type = "f1-micro"
  zone         = "asia-northeast1-b"
  tags         = ["wordpress"]

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }

  network_interface {
    network = "${google_compute_network.wordpress-vpc.self_link}"
    subnetwork       = "${google_compute_subnetwork.wordpress-subnet.self_link}"

    access_config {
      // Ephemeral IP
    }
  }

  service_account {
    scopes = [
      "https://www.googleapis.com/auth/devstorage.read_only",
      "https://www.googleapis.com/auth/logging.write",
      "https://www.googleapis.com/auth/service.management.readonly",
      "https://www.googleapis.com/auth/servicecontrol",
      "https://www.googleapis.com/auth/trace.append",
      "https://www.googleapis.com/auth/monitoring.write",
    ]
  }

  lifecycle {
    ignore_changes = [
      "create_timeout"
    ]
  }
}
```

## あとがき

以前からimportコマンドの存在は知ってたのですが、昔はインポートできるリソースが限られてて使えなかったですが、最近だと割とインポートできる項目が増えてて実用的になったかなーと思ってます。

正直インポートするのがめんどくさい感じもしますが、一度コード化してgit運用すれば、変更履歴が追いやすくなったり、変更依頼をプルリクで出してもらったり、プルリク承認したら自動反映したりなど、DevOpsっぽいことしやすくなるので、めんどくさいだけのメリットもあるかも。