自分用Terraformメモ(追記していく)

## HCL2 の書き方の勘所

* 一般的なプログラミング言語みたいに、scalar値として解決される変数についてはbare文字列で記述できるようになった。

```
これまで
attrib = "${local.unko[geri]}"
HCL2
attrib = local.unko[geri]
```
* ただし、引数として複数の変数や文字列を組み合わせた記述をする場合は、これまで通り `${}` を使った interpolationをする必要がある。

```
引き続きこれはこうする必要がある
attrib = "${local.unko[benpi]}-project-name-${local.unko[balium]}"
```

## なんかおかしい！ヤバい！って時

```
$ rm -rf ./.terraform
```

で、だいたいなんとかなる。

* 特に `terraform init` でworkspacesをremoteから取れないとき。
* 誤ってapplication default credentialsが未設定の状態で `init` やらかした時はコレ。

### 毎度毎度、modulesを足したりrenameするたびに `init` でprovider pluginをダウンロードし直すのを待つのはダルい！

ので、[なんとかするやつを作りました。](https://github.com/ykamenashi/tfreuse)

拙作 `tfreuse` は `./.terraform/plugins` をよそのディレクトリーからコピーして来るツールです。
ついでに `rm -rf ./.terraform` して、 `terraform init` までします。
moduleを大量に使うプロジェクトで特におすすめです。

## tfstate操作

* だいたい `terraform state mv` とか `terraform state rm` でなんとかなることが多い。
* 直接 `pull` して 手編集して `push` するより、コッチの方が確実、安全、そしてハヤイ。
* まず `*.tf` で mvしたい先の名前にリソースを変名(new)してから、stateに入ってるリソースの名前oldとして、 `terraform state mv old new` ってやる。
* moduleの場合はprefixが全部同じなので、bashの `while read VAR` 文を使うのが簡単。

```
$ terraform state list | grep TARGET-NAME | cut -d'.' -f 2 | while read LINE; do terraform state mv "module.OLD_NAME. ...${LINE}" "module.NEW_NAME. ...${LINE}"
```
みたいにやる。一瞬で確実に終わる。手でstate触らずに済む。

## お便利メモ
### クッソ長い `plan` を精査する、CI/CDに色は要らない、redirectしたい場合
* `terraform plan -no-color`
  * `-no-color` するだけでいい。

## importの仕方(provider-google compute engine instanceの場合)
> terraform import `google_compute_RESOURCE_TYPE`.`RESOURCE_NAME` `PROJECT-ID/TARGET-NAME`
### 覚え方
* 第一引数: importしたい種別と `.tf` ファイル上の対応する名前を指定
* 第二引数: 実際のGCP ProjectID内で、第一引数で特定されるリソース種別に基き、第二引数で特定される唯一のリソースを絞り込む

### 注意点
* importする対象のリソースごとに第二引数の内容は異なるので、その対象リソースのTerraform公式ドキュメントの `import` セクションをよく読む。

## modules
### moduleってなんだ
* (いわゆるクラスベースオブジェクト指向言語における)クラスを書ける。
* 書いたクラスを `(root)/modules/(module_dir)` みたいに配置し、(root) で次項のようにmodule宣言(new相当)することで、クラスのインスタンスを得るようなイメージでコードの再利用ができる。
    * つまりコンストラクタを指定してnewする感じ。
* オブジェクト指向めいてリソース名はカプセル化できる。
    * newするオブジェクトのnamespaceは、当然、分離されてくれる。
    * オブジェクトの中にアクセスしたい時のgetterはoutputで指定する。(未検証)
* なんでもかんでも `modules` にする必要はない。むしろ、するかどうかは慎重に考えたほうがいい。一度きりしか使わない、他で再利用しないような `resource` をmodule化することは手間を増やすだけ。

### 使い方
* terraform versionが `v0.12.0` 以降である必要がある
* moduleを利用側で参照するたびに `terraform init` で参照定義を生成させてやる必要がある
  * [そのための拙作 tfreuse](https://github.com/ykamenashi/tfreuse)
* 注意: 以下の例は、 `provider-google` を暗黙にしている。

#### 配置
```
(parent root)
+ main.tf
+ provider.tf
+ modules/
  + gce/ (module root)
    + main.tf
    + main_variable.tf

```

#### (root)/main.tf
```hcl
module "gce-instance-1" {
  source                = "./modules/gce"
  node_name             = "node-1"
  machine_type          = "n1-standard-1"
  zone_name             = "asia-northeast1-c"
  region_name           = "asia-northeast1"
  tags                  = ["network-tag-1"]
  boot_image_identifier = "centos-7"
  boot_disk_size_gb     = "100"
  boot_pd_type          = "pd-ssd"
  subnetwork_path_or_link  = "project-name/network-name/subnet-name"
  service_account_email = "serviceaccount@example.com"
  service_account_scope = ["cloud-platform"]
} 
```

#### (root)/modules/gce/main.tf
```hcl
resource "google_compute_instance" "template" {
  name         = "${var.node_name}"
  machine_type = "${var.machine_type}"
  zone         = "${var.zone_name}"
  tags         = "${var.tags}"
  boot_disk {
    auto_delete = false
    source      = "${google_compute_disk.template_boot_disk.self_link}"
  }

  network_interface {
    subnetwork = "${var.subnetwork_path_or_link}"
    network_ip = "${google_compute_address.template_address_internal.self_link}"
  }

  service_account {
    scopes = "${var.service_account_scope}"
    email  = "${var.service_account_email}"
  }
}

resource "google_compute_disk" "template_boot_disk" {
  name  = "${var.node_name}-boot"
  type  = "${var.boot_pd_type}"
  zone  = "${var.zone_name}"
  image = "${var.boot_image_identifier}"
  size  = "${var.boot_disk_size_gb}"
}

#resource "google_compute_disk" "template_data_disk" {
#  name = "${var.node_name}-data-0"
#  type = "${var.data_pd_type}"
#  zone = "${var.zone_name}"
#  size = "${var.data_disk_size_gb}"
#}
#

resource "google_compute_address" "template_address_internal" {
  name         = "${var.node_name}-internal-0"
  address_type = "INTERNAL"
  region       = "${var.region_name}"
  subnetwork = "${var.subnetwork_path_or_link}"
}

```

#### (root)/modules/gce/main_variables.tf
```
variable "node_name" {
}

variable "machine_type" {
}

variable "zone_name" {
}

variable "region_name" {
}

variable "tags" {
    type = list(string)
}

variable "boot_image_identifier" {
}

variable "boot_disk_size_gb" {
}

variable "boot_pd_type" {
}

#variable "data_disk_size_gb" {
#}
#
#variable "data_pd_type" {
#}

variable "subnetwork_path_or_link" {
}

variable "service_account_email" {
}

variable "service_account_scope" {
  type = list(string)
}
```

### 作り方
1. まず普通にresourceを使って `google_compute_instance` や `google_compute_disk` 、 `google_compute_address` やらを書く。(main.tf)
2. 抽象化したい部分を `main_variable.tf` みたいに切り出す。フォーマットは👆参照。
3. 呼び出し元(これを仮にrootと呼ぶ)の配下に `modules/` を掘り、 `ln -s path/to/module-dir path/to/root/modules/any-name` みたいな配置にする。
4. (root) で `main.tf`、`provider.tf` やらを書く。
4. (root) で `terraform init` し、validate / planしてエラーを叩く。
5. できあがり。

## リポジトリに生credentialを置きたくない！せめて暗号化、それも自動でやりたい！

* provider-googleのCloud KMS連携の機能を使えば実はできる。

## 編集環境

* IntelliJ IDEA Terraform Plug-inがまあ、HCL2のsuggestとかしてくれてありがたいんだけど、自分はvimとVisual Studio Codeの `dabbrev` (^X ^P) でリソース名を補完する癖がついているのでIntelliSenceをそもそも使わないのだった。
* なので、お金持ちの方はIDEAを。
* そうでない方はvscodeがいいと思います。わっちはvimもvscodeもIDEAも使います。(会社のカネで)

## ファイル分割粒度

これもgit repositoryと同じ話になるわけで、つまりdeploy単位で分けたほうがいいと思う。
関連するresourceはひとつの `.tf` ファイルにしてしまう。
たとえば、 `google_compute_address` というリソース。これが「IPアドレスはIPアドレスだから、 `ipaddress.tf` にしよう！なんてすると、

1. グローバルロードバランサーのglobal IP
2. GKEのIngress IP
3. bastion GCE InstanceのIP

みたいなやつを全部一個のtfにまとめているとすごくわかりづらい。
そもそもHCLは「読めば構成がわかるようになっている」のが最大の利点なのに、それを殺してしまう。
IDEの補完がなけりゃ読めないような書き方はクソだと思う。
変数名を連番にしてしまって、対応表を別途見ないと読めないクソソースコード変数命名と同じだと思う。

だから、自分が書く場合は「グローバルロードバランサーのIPの定義は、他のグローバルロードバランサー関連のリソースと一緒にまとめて、 `global_loadbalancer_for_SERVICE.tf` みたいに一個のファイルとして切り出す」ようにしている。

まあ、これもケースバイケースだけども。
たとえばCloud SQLとか。たとえばGKE Clusterとか。たとえばGlobal Loadbalancerとか。
GCPのリソースは複数のAPIで提供されるresourceを組み合わせて「ひとつのサービス」に見せ掛けているので、こういう書き方の方がいいのです。
AWS/Azureは知らない。