我的 homelab: 使用 Terraform 管理 vSphere 虚拟机以及 Cloud-init 初始化

前言

正如之前所说的,在有了 vCenter 之后,我们可以尝试把 Terraform 的部署平台从套娃虚拟化的 pve 迁移到 esxi 上来,方便管理,减小性能损耗。

Terraform 上 vSphere provider 的文档 真的难看,和 vm 有关的全部挤在一个页面,还只有一级目录,一点重点都没有,还有不同版本的配置放在一起的。最后还是找 cloud-init 上 VMware datasource 的文档 才知道要怎么搞的。本文就总结一下实际要做的步骤,外加一点个人的理解。

目标

  • 支持使用 Terraform 在 esxi 平台上面全自动创建实例
  • 新创建的实例自动初始化

创建虚拟机模板

下载 cloud image

先到 镜像站,下载最新的 ova 镜像(因为 vSphere 可以直接导入 ova 镜像),对应的应该是这个链接:https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/noble/current/noble-server-cloudimg-amd64.ova

关于 cloud image 是什么,可以看我之前的博客。

导入 ova 镜像

这里在 vSphere 的界面里,选择**部署 OVF 模板**这个功能,把刚刚下载的镜像导入进去,然后就能得到一台崭新的虚拟机了。

这个时候先不要着急启动,到配置里面,去把 vApp 选项给关掉,不然可能会影响后面的 cloud-init。

image.png

这个 vApp 呢,好像是 VMware 自己搞的一套配置项,具体是和 ovf/ova 里面的配置文件配合使用的,vApp 里面的每一项配置需要 ovf/ova 镜像提供支持。参考

不过由于我们是使用 cloud-init 从外界的 datasource 传入配置信息的,所以就不需要这个镜像自己的配置了。而 vApp 的设置可能会覆盖掉我们从 datasource 传入的配置,所以要禁用 vApp。

关闭 vApp 之后呢,我们把这台虚拟机转换成模板,防止意外更改配置。

至此,模板就准备好了。

使用 Terraform 创建实例

有了模板之后,我们就可以创建实例了。

写 tf 文件

先是很多引用,引用目前已有的资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
provider "vsphere" {
  user                 = var.vsphere_user
  password             = var.vsphere_password
  vsphere_server       = var.vsphere_server
  allow_unverified_ssl = true
  api_timeout          = 10
}

data "vsphere_datacenter" "datacenter" {
  name = "Datacenter"
}

data "vsphere_datastore" "datastore" {
  name          = "0590"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_host" "esxi_host" {
  name          = "10.21.22.13"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

然后引用模板

1
2
3
4
data "vsphere_virtual_machine" "template" {
  name          = "ubuntu-noble-24.04-cloudimg_240822"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

最后再声明我们需要的资源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
resource "vsphere_virtual_machine" "vm" {
  name             = "clone_test"
  resource_pool_id = data.vsphere_host.esxi_host.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id
  num_cpus         = 8
  memory           = 4096
  guest_id         = data.vsphere_virtual_machine.template.guest_id
  scsi_type        = data.vsphere_virtual_machine.template.scsi_type
  network_interface {
    network_id   = data.vsphere_network.network.id
    adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]
  }
  disk {
    label            = "disk0"
    size             = data.vsphere_virtual_machine.template.disks.0.size
    thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
  }
  cdrom {
    client_device = true
  }
  clone {
    template_uuid = data.vsphere_virtual_machine.template.id
  }
  extra_config = {
    "guestinfo.userdata"          = local.userdata
    "guestinfo.userdata.encoding" = local.userdata-encoding
    "guestinfo.metadata"          = local.metadata
    "guestinfo.metadata.encoding" = local.metadata-encoding
  }
}

重点讲一下 extra_config 这个选项。参考 cloud-init 文档

这几个选项是 VMware datasource 的配置方法。guestinfo.*.encoding传入的是具体内容的编码,而 guestinfo.* 传入的是编码过后的内容。

只支持 metadatauserdatavendordata。关于如何配置网络,也就是network 配置,是通过直接写在 metadata 里面配置的。

接下来就是对 tf 和 user-data 做进一步的个性化了,具体根据需求决定吧。

我的代码文件 在这里

总结

说难其实也不难,就是看 vSphere 的 provider 文档看了好久,有好几种配置的方式,也没有研究出来到底应该用哪一种,在这里耽误了好多时间。后面看到 cloud-init 的文档,方向就清晰很多了。

然后是感叹,pve 的 provider 的 bug 是真的多,比如说创建了机器之后就不能改配置了,不然逼着你重启,关键是重启也是有问题的,重启会卡死。现在换到 vSphere 就一点问题都没有了,爽到飞起,这就是金钱的力量吗。XD