TerraformでvSphereのVMを作成する


今回は完全オンプレです。
あんまり需要はないと思われますが全然情報が転がってなかったので公開します。

たぶんオンプレだとVMWare使ってる人多いですよね。
今回はうちがvsphereを使っていて環境構築するのにこれを操作するチームがボトルネックになってるのを解消したいがためにTerraformを使いました。

## Terraform

vSphereのプロバイダがデフォルトで用意されています。
非常に便利です。
ただしほぼインスタンスの管理以外には使えません。
対応してるのフォルダとVMとファイルと仮想ディスクだけだし。

## 構成

```
├── README.md
├── chef           # Chef関連のファイルを置いてます
│   └── attributes # ChefのAttributesをJSONファイルで格納する場所
│       └── default.json
├── modules        # Module置き場
│   └── instance   # インスタンス(これしか管理してない)
│       ├── instance.tf  # 一番キモ
│       ├── outputs.tf
│       ├── variables.tf # 変わりそうにない変数定義はここで。適当にファイル分割してます。
│       ├── variables_chef.tf
│       └── variables_network.tf
├── sample.tf      # サンプルのVMインスタンス
├── variables.tf   # 共通の変数定義
└── vsphere.tf     # vsphereの変数定義
```

## 設定

### vsphere.tf

別にファイル名は何でもいいんですが。
とりあえずvcenterに接続するための情報をここで定義しときます。
別に分割しなくても1ファイルで管理しても構いません。

```vsphere.tf
variable "vsphere_user" { }

variable "vsphere_password" { }

variable "vsphere_server" { }

provider "vsphere" {
  user           = "${var.vsphere_user}"
  password       = "${var.vsphere_password}"
  vsphere_server = "${var.vsphere_server}"

  allow_unverified_ssl = true
}

```

ここら辺の接続情報は多少センシティブなので実行するサーバの環境変数で値を定義してます。

### sample.tf


```
module "SAMPLE01" {
  source      = "./modules/instance"
  vm_name     = "SAMPLE01"
  cpu         = 1
  memory      = 512
  front_ip    = "192.168.9.10"
  front_label = "${var.network_front["ap"]}"
  back_ip     = "192.168.10.10"
  back_label  = "${var.network_back["ap"]}"
  storage     = "${var.datastore["storage1"]}"
}
```

これが作成するVMの定義です。
大体ほとんど同じような記述になるので、Moduleを使って共通化しています。
必要なところだけ変数にして渡してる感じです。
ネットワークインターフェースを2つ固定で付けるようにしています。

### Module

VMインスタンスの定義自体大量に同じようなのが量産されるのでModuleを使いました。
Moduleについては[公式ドキュメント](https://www.terraform.io/docs/configuration/modules.html)を見てください。

コアな部分はこの`instance.tf`が担っています。

```instance.tf
resource "vsphere_virtual_machine" "vm" {
  name   = "${var.vm_name}"
  vcpu   = "${var.cpu}"
  memory = "${var.memory}"

  datacenter  = "${var.vcenter["datacenter"]}"
  cluster     = "${var.vcenter["cluster"]}"
  dns_servers = "${var.dns}"
  time_zone   = "${var.server["timezone"]}"
  domain      = "${var.server["domain"]}"

  network_interface {
    ipv4_address       = "${var.front_ip}"
    label              = "${var.front_label}"
    ipv4_gateway       = "${var.network["gateway_fr"]}"
    ipv4_prefix_length = "${var.network["prefix_len"]}"
  }
  network_interface {
    ipv4_address       = "${var.back_ip}"
    label              = "${var.back_label}"
    ipv4_prefix_length = "${var.network["prefix_len"]}"
  }

  disk {
    template  = "${var.vcenter["template"]}"
    datastore = "${var.storage}"
    bootable  = true
    type      = "thin"
  }

  # Provisioning
  ## Chef Bootstrap
  provisioner "chef" {
    attributes_json = "${file(var.chef_options["attribute_file"])}"
    run_list        = "${var.chef_run_list}"

    # Static Configs
    node_name       = "${self.name}"
    environment     = "${var.chef_server["environment"]}"
    server_url      = "${var.chef_server["url"]}"
    user_name       = "${var.chef_server["user"]}"
    user_key        = "${file(var.chef_server["key"])}"
    ssl_verify_mode = "${var.chef_options["ssl_verify"]}"
    http_proxy      = "${var.chef_options["proxy"]}"
    https_proxy     = "${var.chef_options["proxy"]}"
    version         = "${var.chef_options["version"]}"
    client_options  = "${var.chef_other_option}"
    secret_key      = "${file(var.chef_options["data_bag_secret"])}"
    log_to_file     = true
    recreate_client = true
  }

  # ssh connection
  connection {
    type        = "ssh"
    host        = "${self.network_interface.1.ipv4_address}"
    user        = "hoge"
    private_key = "${file(var.server["ssh_key"])}"
  }
}

variable "vm_name" {}
variable "cpu" {
  default = 2
}
variable "memory" {
  default = 2048
}
variable "front_ip" {}
variable "back_ip" {}
variable "front_label" {}
variable "back_label" {}
variable "storage" {}
```

`variables*.tf`に色々変数定義してます。
ほとんどどのサーバも変わらないはずなのでサーバによって変わるものは`sample.tf`で渡すようにしています。

### `disk`について

ここはtemplateが鬼門でした。
vsphereのテンプレート使えると思ってたんだけどなぜかNot Foundで使えず…。
指定の仕方知ってる人いたら教えてください。
`TEMPLATE01`みたいなリファレンスインスタンス作ってそいつの名前を指定するようにしました。

### `connection`について

これがクッソ便利でした。
provisionerはこのサンプルでは`chef`しかないのですが、それ以外の`remote-exec`とかで他の管理サーバーとかに情報をプッシュしたい場合とかにユーザーとか鍵は同じままで`host`だけ変えたいって場合、冗長に書かなくていいんです。
書いたところだけ上書き。

```
provisioner 'remote-exec' {
  host "another-server"
}

connection {
  host "default-server"
  user "default-user"
}
```

### `provisioner chef`について

今回ネットワークインターフェースを2本にしてたせいで設定間違えてChefの認証が最後だけ通らないとかハマりまくってたんですが、結局そこのIPが違うってのに気付いて解決しました。
まあ普通はハマらないッス。

## 運用環境

VM増やしたいときは`sample.tf`に新しく`module`追加したりファイル増やしたりして追加します。
provisionerとかほとんどAttributesとrun_listくらいしか変わらないのでこれでいいんです。
これでVM作るのが楽になりました。
ありがとうございます。