terraformでgcpリソース定義ルール

#


ハードコード記法から、変数定義の記法まで試して、
terraform記法を勉強しよう。！

ちなみにterraform環境の作り方はこちらで教えないので、０から環境構築したい場合は以下のリンクを参照して頑張ろう。


terraformのCDには以下のコマンドを使う。

```
# カレントディレクトリのTFファイルを見つけて解析し、tfデプロイ可能な環境を作る、コードがおかしいと文句を言われる
terraform init


# クラウドでなく、ローカルに空デプロイする、planで成功すればapplyもうまくいく
terraform plan


# クラウドにTFをデプロイする。
terraform apply


# クラウドにTFをデプロイする。
terraform apply
```

これよりコードのサンプルに入ります、使用時コメントは削除してください。

---
## GCSのterraform記法

参考：
https://qiita.com/yagince/items/c2ef99e770f559720eec

まずは練習としてterraformでバケットでも作ってみよう。

gsutil mb -c multi_regional -l Asia gs://19870331-tf-state


```
#pro
provider "google" {
  project     = "xxx"  #あなたのプロジェクト名を入れて
  region      = "us-central1"  #公式のおすすめはアイオワ(us-central1)
}

resource "google_storage_bucket" "private-bucket" {
  name          = "private-bucket-abc19870331"
  location      = "us-central1-a"
  storage_class = "REGIONAL"
  labels = {
    app = "test-app"
    env = "test"
  }
}
```



## 2. create service account

- **gcloudコマンドはインストール済み**
- terraform実行用のサービスアカウトを作成します。
- 権限は「Project > 編集者」をつけました
- 鍵をjson形式でダウンロード
  - `credentials.json` として置いておきます
  - 名前は適当です
- service accounntをactivateします

  ```shell
  gcloud auth activate-service-account --key-file=credentials.json
  ```
- projectをセットしておきます

  ```shell
  $ gcloud config set project xxxxx
  ```
  
## 3. create GCS bucket

`xxx.tfstate` ファイルを保存する為のバケットをGCSに作成しておきます。
※管理対象のインフラの状態を保存するファイル

```shell
$ gsutil mb -c multi_regional -l Asia gs://xxxx-tf-state
```

# Providerの設定を書く

```terraform:provider.tf
provider "google" {
  credentials = "${file("credentials.json")}"
  project     = "${var.project}"
  region      = "${var.region}"
}
```

```terraform:variables.tf
variable "project" {
  default = "xxx"
}

variable "region" {
  default = "asia-northeast1"
}
```

- project,regionはvariables.tfに外だししました
- credentials.jsonはさっきつくったservice accountの鍵ファイルです


# backend設定を書く

tfstateファイルをGCS上で管理するための設定

```terraform:backend.tf
terraform {
  backend "gcs" {
    bucket      = "xxxx-tf-state"
    path        = "practice.tfstate"
    credentials = "credentials.json"
  }
}
```

# terraform initする


```shell
$ gsutil ls gs://xxxx-tf-state
gs://xxxx-tf-state/practice.tfstate
```

ちゃんとtfstateファイルも出来てます。

# VPCを作ってみる

試しに何か作ってみたいのでVPCを作ってみることにします

## リソース定義

```terraform:network.tf
resource "google_compute_network" "vpc" {
  name = "terraform-practice-network"
}

resource "google_compute_subnetwork" "vpc_subnet1" {
  name          = "terraform-practice-network-subnet1"
  ip_cidr_range = "${var.subnet_cidr_range}"
  network       = "${google_compute_network.vpc.name}"
  description   = "example.subnet1"
  region        = "${var.region}"
}
```

```terraform:variables.tf
variable "project" {
  default = "xxxx"
}

variable "region" {
  default = "asia-northeast1"
}

variable "subnet_cidr_range" {
  default = "192.168.10.0/24"
}
```

## terraform planする

```shell
$ terraform plan
```

## apply

とりあえず行けそうなので、applyしてみます

```shell
$ terraform apply


結構時間かかりましたが、ちゃんと出来たようです。

```shell
$ gcloud compute networks list
NAME                        SUBNET_MODE  BGP_ROUTING_MODE  IPV4_RANGE  GATEWAY_IPV4
default                     AUTO         REGIONAL
terraform-practice-network  AUTO         REGIONAL
$ gcloud compute networks subnets list | grep subnet1
terraform-practice-network-subnet1  asia-northeast1          terraform-practice-network  192.168.10.0/24
```

## nameをvariablesにしてみる

```terraform:network.tf
resource "google_compute_network" "vpc" {
  name = "${var.vpc_name}"
}

resource "google_compute_subnetwork" "vpc_subnet1" {
  name          = "${var.subnetwork_name}"
  ip_cidr_range = "${var.subnet_cidr_range}"
  network       = "${google_compute_network.vpc.name}"
  description   = "example.subnet1"
  region        = "${var.region}"
}
```

```terraform:variables.tf
variable "project" {
  default = "xxxx"
}

variable "region" {
  default = "asia-northeast1"
}

variable "subnet_cidr_range" {
  default = "192.168.10.0/24"
}

variable "vpc_name" {
  default = "terraform-practice-network"
}

variable "subnetwork_name" {
  default = "terraform-practice-network-subnet1"
}
```

```shell
$ terraform plan
```

変化ないですね。

# 環境毎の構成を考える

production,staging など環境毎の構成をどう実現するか考えてみます。
今回は
[Terraform 運用ベストプラクティス 2019 ~workspace をやめてみた等諸々~ - 長生村本郷Engineers'Blog](http://kenzo0107.hatenablog.com/entry/2019/04/17/103558)
こちらの記事がとても納得感があったので、参考にさせて頂きました :bow:

今はproductionしかないですが、こんな感じにしてみました。

```
$ tree
.
├── credentials.json
└── environments
    └── production
        ├── backend.tf
        ├── network.tf
        ├── provider.tf
        └── variables.tf
```

# module化してみる

せっかくvariablesに変数を定義しているので、各リソース定義をstagingでも使えるようにして共通化したいですね。
networkの設定をmoduleに分離してみたいと思います。

## ディレクトリ構成

```
$ tree
.
├── credentials.json
├── environments
│   └── production
│       ├── backend.tf
│       ├── main.tf
│       ├── provider.tf
│       └── variables.tf
└── modules
    └── network
        └── main.tf
```

## リソース定義を書き直す

```terraform:modules/network/main.tf
ariable "vpc_name" {}
variable "subnetwork_name" {}
variable "subnet_cidr_range" {}
variable "region" {}

resource "google_compute_network" "vpc" {
  name = "${var.vpc_name}"
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "vpc_subnet1" {
  name          = "${var.subnetwork_name}"
  ip_cidr_range = "${var.subnet_cidr_range}"
  network       = "${google_compute_network.vpc.name}"
  description   = "example.subnet1"
  region        = "${var.region}"
}
```

```terraform:environments/production/main.tf
module "network" {
  source = "../../modules/network"

  vpc_name          = "${var.vpc_name}"
  subnetwork_name   = "${var.subnetwork_name}"
  subnet_cidr_range = "${var.subnet_cidr_range}"
  region            = "${var.region}"
}
```

## plan


destroy -> create になってしまいました。

## terraform state mv

[Terraform の state mv を使ってべた書きしたリソースをモジュールに移す tips | Cry for the Moon](https://mozami.me/2018/05/12/terraform_state_mv.html)

ベタ書きしていたリソースをモジュール化する時は `terraform state mv` で移動させるらしい。
やってみます。

```shell
$ terraform state mv google_compute_network.vpc module.network.google_compute_network.vpc

Error: Invalid target address

Cannot move to module.network.google_compute_network.vpc: module.network does
not exist in the current state.
```

エラーになってしまいました。
`module.network` なんて無いよ、と。

[Unable to move resource to new module · Issue #21346 · hashicorp/terraform](https://github.com/hashicorp/terraform/issues/21346)

こんなIssueを発見。

[allow moving resource to new modules not in state by jbardin · Pull Request #22299 · hashicorp/terraform](https://github.com/hashicorp/terraform/pull/22299)

これで直ったのかな？
2019/08/02にマージされているようです。
が、まだリリースされていないようでした。※2019/08/06 22:32時点

https://github.com/hashicorp/terraform/issues/21346#issuecomment-498701941

## `import` and `state rm`

workaroundとして、importしてから古いの消せば行けるよ、と書いてあったのでやってみます

```shell
$ terraform import module.network.google_compute_network.vpc terraform-practice-network


$ terraform state rm google_compute_network.vpc

$ terraform plan
```

VPCのdestroyとcreateが消えました！
なるほど。
一旦これでsubnetworkも対応してしまいます。

```shell
$ terraform import module.network.google_compute_subnetwork.vpc_subnet1 terraform-practice-network-subnet1

$ terraform state rm google_compute_subnetwork.vpc_subnet1

$ terraform plan
```

行けました。

# GCP上にある既存リソースをterraformに入れる

すでにstate mvが出来なかったところでつかってしまいましたが、 `import` を使うと既存のリソースをtfstateに入れることが出来るようです。
既存のリソース管理をTerraformに移行する時には必要になりそうなのでやってみます。

## GCEインスタンスを立てる

まずは、GCEインスタンスを手動で立てておきます。
VPCはさっき作ったやつにします。

- マシンタイプ
  - f1-micro（vCPU x 1、メモリ 0.6 GB）
- ゾーン
  - asia-northeast1-a
- network
  - terraform-practice-network
- subnetwork
  - terraform-practice-network-subnet1
- 内部IP
  - 192.168.10.2
- boot disk image
  - ubuntu-1804-bionic-v20190722a

## リソース定義を書く

[Google: google_compute_instance - Terraform by HashiCorp](https://www.terraform.io/docs/providers/google/d/datasource_compute_instance.html)

せっかくなのでmodulesに書いていきます。（productionとstagingで同じmoduleを使う想定）
今回は踏み台サーバを想定したインスタンスにしたいと思いますので、名前は `bastion` にします。

```terraform:modules/bastion/main.tf
variable "name" {}
variable "subnetwork_name" {}
variable "machine_type" {}
variable "region" {}
variable "zone" {}
variable "boot_disk_image" {}
variable "private_ip" {}
variable "service_account" {}

resource "google_compute_address" "bastion" {
  name         = "${var.name}"
  region       = "${var.region}"
}

resource "google_compute_instance" "bastion" {
  name         = "${var.name}"
  machine_type = "${var.machine_type}"
  zone         = "${var.zone}"
  tags         = ["server", "bastion"]

  boot_disk {
    initialize_params {
      image = "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/${var.boot_disk_image}"
    }
  }

  network_interface {
    network_ip = "${var.private_ip}"
    subnetwork = "${var.subnetwork_name}"
    access_config {
      # static external ip
      nat_ip   = "${google_compute_address.bastion.address}"
    }
  }

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

```terraform:main.tf
module "network" {
  source = "../../modules/network"

  vpc_name          = "${var.vpc_name}"
  subnetwork_name   = "${var.subnetwork_name}"
  subnet_cidr_range = "${var.subnet_cidr_range}"
  region            = "${var.region}"
}

module "bastion" {
  source = "../../modules/bastion"

  name            = "${var.bastion_name}"
  subnetwork_name = "${var.subnetwork_name}"
  machine_type    = "f1-micro"
  region          = "${var.region}"
  zone            = "${var.region_zone}"
  boot_disk_image = "ubuntu-1804-bionic-v20190722a"
  private_ip      = "192.168.10.2"
  service_account = "xxx-compute"
}
```

```terraform:variables.tf
variable "project" {
  default = "xxxx"
}

variable "region" {
  default = "asia-northeast1"
}

variable "region_zone" {
  default = "asia-northeast1-a"
}

variable "subnet_cidr_range" {
  default = "192.168.10.0/24"
}

variable "vpc_name" {
  default = "terraform-practice-network"
}

variable "subnetwork_name" {
  default = "terraform-practice-network-subnet1"
}

variable "bastion_name" {
  default = "terraform-practice-instance-1"
}
```

variablesに完全に外だしすべきなのか、main.tfに直接書いてしまうのかが微妙なラインですが、一旦他でも使うかもしれないような値はvariablesに出すくらいにしてます。

## import

```shell
$ terraform import module.bastion.google_compute_instance.bastion xxxx/asia-northeast1-a/terraform-practice-instance-1

```

インスタンスを作成すると自動的にGCEインスタンスのデフォルトサービスアカウントが作成されているので、サービスアカウントもimportしてresourceに定義しようと思ったんですが、
自動で作成されるサービスアカウントはaccount名が数値から始まっていて、
Terraformのgoogle_service_accountのaccount_idは `[a-z]` ではじまる必要があるようで一旦諦めました。
([Google: google_service_account - Terraform by HashiCorp](https://www.terraform.io/docs/providers/google/r/google_service_account.html#account_id))

※ importする場合は `Identity and Access Management (IAM) API` を有効にする必要があります。Developer Consoleから有効化しました

## plan

```shell
$ terraform plan


## apply

```shell
$ terraform apply
```

# 次回

- 既存の手動で作ったリソースをすべて手で書き起こしていくのはしんどいので、terraformerをつかってみたい
    - [GoogleCloudPlatform/terraformer: CLI tool to generate terraform files from existing infrastructure (reverse Terraform). Infrastructure to Code](https://github.com/GoogleCloudPlatform/terraformer)


---
## サーバレス用途なGCFのterraform記法

参考：
https://qiita.com/donko_/items/6289bb31fecfce2cda79


```
main.tf
provider "google" {
  credentials = "${file("${var.credential.data}")}"
  project     = "${lookup(var.project_name, "${terraform.workspace}")}"
  region      = "asia-northeast1"
}

data "archive_file" "function_zip" {
  type        = "zip"
  source_dir  = "${path.module}/../src"
  output_path = "${path.module}/files/functions.zip"
}

resource "google_storage_bucket" "slack_functions_bucket" {
  name          = "${lookup(var.project_name, "${terraform.workspace}")}-scheduler-bucket"
  project       = "${lookup(var.project_name, "${terraform.workspace}")}"
  location      = "asia"
  force_destroy = true
}

resource "google_storage_bucket_object" "functions_zip" {
  name   = "functions.zip"
  bucket = "${google_storage_bucket.slack_functions_bucket.name}"
  source = "${path.module}/files/functions.zip"
}

resource "google_pubsub_topic" "slack_notify" {
  name    = "slack-notify"
  project = "${lookup(var.project_name, "${terraform.workspace}")}"
}

resource "google_cloudfunctions_function" "slack_notification" {
  name        = "SlackNotification"
  project     = "${lookup(var.project_name, "${terraform.workspace}")}"
  region      = "asia-northeast1"
  runtime     = "go111"
  entry_point = "SlackNotification"

  source_archive_bucket = "${google_storage_bucket.slack_functions_bucket.name}"
  source_archive_object = "${google_storage_bucket_object.functions_zip.name}"

  environment_variables = {
    SLACK_WEBHOOK_URL = "${var.webhook.url}"
  }

  event_trigger {
    event_type = "providers/cloud.pubsub/eventTypes/topic.publish"
    resource   = "${google_pubsub_topic.slack_notify.name}"
  }
}

resource "google_cloud_scheduler_job" "slack-notify-scheduler" {
  name        = "slack-notify-daily"
  project     = "${lookup(var.project_name, "${terraform.workspace}")}"
  schedule    = "0 8 * * *"
  description = "suggesting your morning/lunch/dinner"
  time_zone   = "Asia/Tokyo"

  pubsub_target {
    topic_name = "${google_pubsub_topic.slack_notify.id}"
    data       = "${base64encode("{\"mention\":\"channel\",\"channel\":\"random\"}")}"
  }
}
variable.tf
variable "project_name" {
  default = {
    tf-sample = "<your-project>"
  }
}

variable "credential" {
  default = {
    data = "<your-credential-path>"
  }
}

variable "webhook" {
  default = {
    url = "<your-webhook-url>"
  }
}

```



---
## 公開設定のNWとGCEのterraform記法

参考：
https://qiita.com/y-uemurax/items/4376e27ccc0b2dcc85f0







---
## 限定公開設定のNWとGCEのterraform記法

実用性を加味して外部アクセスが出来ないNWとGCEインスタンスを構築してみよう。






resource "google_compute_instance" "default" {
  name         = "test"
  machine_type = "n1-standard-1"
  zone         = "us-central1-a"
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }

  // Local SSD disk
  scratch_disk {
    interface = "SCSI"
  }

  network_interface {
    network = "default"

    access_config {
      // Ephemeral IP
    }
  }

  service_account {
    scopes = ["userinfo-email", "compute-ro", "storage-ro"]
  }
}





---
## プライベート環境有効なCloudComposerのterraform記法




```terraform:versions.tf
# 記載時点での最新版
terraform {
  required_version = "0.14.6"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "google v3.59.0"
    }
    google-beta = {
      source  = "hashicorp/google-beta"
      version = "google v3.59.0"
    }
  }
}
```


```terraform:main.tf
#pro
provider "google" {
  project     = "xxx"
  region      = "asia-northeast1"
}


terraform {
  required_version = "0.14.6"
}

resource "google_composer_environment" "test" {
  name   = "my-composer-env"
  region = "asia-northeast1"
  config {
    node_count = "3"                            #最小設定
    node_config {
      network    = google_compute_network.vpc.self_link
      subnetwork = google_compute_subnetwork.vpc-sub.self_link
      zone         = "asia-northeast1-a"
      machine_type = "n1-standard-1"  #最小設定
      disk_size_gb = "20"                        #最小設定

      #VPCネイティブ有効化
      ip_allocation_policy {
        use_ip_aliases = true
        #cluster_secondary_range_name = google_compute_subnetwork.test.secondary_ip_range[0].range_name
        #services_secondary_range_name = google_compute_subnetwork.test.secondary_ip_range[1].range_name
      }
    }
    #プライベート環境有効化
    private_environment_config {
        enable_private_endpoint = "true"
    }
  
      software_config {
      airflow_config_overrides = {
        core-load_example = "True"
      }

      pypi_packages = {
        numpy = ""
        scipy = "==1.1.0"
      }

      env_variables = {
        FOO = "bar"
      }
      image_version = "composer-1.11.3-airflow-1.10.6"
      python_version = "3"
    }  # fin_software_config
    
    
    
  }
}
```


-----
以下からvalues化した記法



```

resource "google_compute_network" "vpc" {
  name                    = "sharedvpc"
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "vpc-sub" {
  name                     = google_compute_network.vpc.name
  network                  = google_compute_network.vpc.name
  ip_cidr_range            = "10.0.36.0/24"
  region                   = "us-central1"
  private_ip_google_access = true

  secondary_ip_range {
    range_name    = "pod"
    ip_cidr_range = "10.0.0.0/19"
  }

  secondary_ip_range {
    range_name    = "svc"
    ip_cidr_range = "10.0.32.0/22"
  }
}

resource "google_composer_environment" "test" {
  name   = "test-env"
  region = "us-central1"
  config {
    node_config {
      network    = google_compute_network.vpc.self_link
      subnetwork = google_compute_subnetwork.vpc-sub.self_link
      zone       = "us-central1-a"

      ip_allocation_policy {
        use_ip_aliases = true
        cluster_secondary_range_name = google_compute_subnetwork.test.secondary_ip_range[0].range_name
        services_secondary_range_name = google_compute_subnetwork.test.secondary_ip_range[1].range_name
      }
    }
  }
}


```