我的 homelab(3): PVE 与 Terraform

前言

昨天晚上回宿舍后看到了那个视频,这不就是我想要的效果吗,于是就尝试着复现。

安装 PVE

是的,我还是选择 pve 了。

esxi 的 api 是只读的,也就是说不支持直接用 terraform 去操控,如果需要使用 terraform 操控 exsi 平台的话,需要上到 vsphere。先不说这玩意巨贵我不会买就是了,还吃资源,我就一台机器也搞 vCenter 着实没必要。虽然也有 方案 使用 ssh 去直接提供类似的 terraform 的 api,但是我看了,不支持 cdrom,所以只能作罢。(其实我在后台安装了一天的 vCenter)

然后,换虚拟化平台的话,就。懒,不想换,换平台好麻烦的。我 esxi 和我洋垃圾服务器主板超配的好不好,用的也很稳,很爽,干嘛换掉。

于是,那就在 exsi 里面安装 pve 吧。😁

熟练的下载 iso,熟练的创建虚拟机,选择 iso,下一步,下一步,下一步。就安装好了。

然后 开启嵌套虚拟化,就可以愉快的玩耍了。

方案选择

主要参考的教程是 这个

首先,我想了好几个方案

  • 一是,用了 pve 了,之前在 esxi 上面设置不了的 serial number 可以随意设置了,那我就可以实现我的“完美”方案了(见本系列第一篇)
  • 二是,可以用 terraform 渲染 cloud init 的配置,然后再调用命令生成 iso,把 iso 挂载到镜像上面就可以啦。这个方案就通用的一点,如果 vmware 家的平台可以用的话,迁移过去也不会有有很大的成本

不过,后来发现,pve 自带 cloudinit 了,甚至简单的配置项都可以直接在 ui 上面完成,配置文件都不用写了。

接着仔细研读pve 的 terraform provider 文档,发现,人家专门有一个叫做**Cloud Init Disk** 的 Resource。你只要给配置文件,他能够自动生成符合 cloudinit 规范的 iso,然后使用 nocloud 这个 datasource 的方式挂载上去,就能自己按照你给的 yaml 进行配置了。

再想想,也是哦,我是用户诶。我干嘛去管 要怎么搞让实例读到配置 等细节,我是平台的用户,平台给了这个接口,我干嘛不用,还想着自己造轮子?

于是乎,就改变策略,我就只把要的实例的信息和配置告诉 terraform,然后剩下的就让它帮我去做吧。

构建模板

首先,需要构建实例的模板。模板是镜像文件和配置的组合,我们把基础配置提前在模板里面配置好,接下来使用的时候直接克隆预先配置好的模板,就可以省很多事。

创建模板可以参考这个 官方文档。注意可以的话,尽量选择 virtio 的设备,性能会高一些。

Terraform 配置文件

这里真的踩了好多坑,先是 providers 版本不够,我用的 pve 太新了。升级之后 providers 的语法又改了,又要改配置文件。然后改完之后出现了克隆出来的硬盘用不了的情况,测试了好久,最后在 issue 里面了解到要加一个选项才能正常克隆。

怎么说呢。我确实不是很懂 pve,也不是很懂 terraform,可能折磨我几个小时的问题在行家的眼里就是基本操作吧。就好比看到别人学 c 语言犯的常见错误一样吧。

反正不管怎么说,我还是搞成功了,就这样吧,能用就行,反正我也不是搞这个的专家。

tf 配置文件在下面给出来了,记得改成你自己的参数。

简单的来说就是传入自己的 cloudinit 的配置文件,创建一个 cloud_init_disk。然后把这个 disk 挂载到从模板克隆出来的虚拟机上。这样虚拟机启动的时候就会自动读取配置文件了。

 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
terraform {
  required_providers {
    proxmox = {
      source  = "Telmate/proxmox"
      version = "3.0.1-rc3"
    }
  }
}

provider "proxmox" {
  pm_tls_insecure     = true
  pm_api_url          = "https://10.21.22.14:8006/api2/json"
  pm_api_token_id     = "root@pam!ewq1"
  pm_api_token_secret = "d3e5ec16-cb34-4417-9f5e-06315c7090ac"
  pm_debug            = true
}

locals {
  vm_name          = "ubuntu"
  pve_node         = "pve"
  iso_storage_pool = "local"
}

resource "proxmox_cloud_init_disk" "ci" {
  name     = local.vm_name
  pve_node = local.pve_node
  storage  = local.iso_storage_pool

  meta_data = yamlencode({
    instance_id    = sha1(local.vm_name)
    local-hostname = local.vm_name
  })

  user_data = file("user-data.yaml")

  network_config = file("network-config.yaml")
}
resource "proxmox_vm_qemu" "proxmox-ubuntu" {
  name = local.vm_name
  desc = "Ubuntu develop environment"
  # 节点名
  target_node = "pve"
  # cloud-init template
  clone      = "ubuntu-2404-cloudinit-template"
  full_clone = false
  # 关机 guest agent
  agent   = 0
  os_type = "ubuntu"
  onboot  = false
  # CPU
  cores   = 4
  sockets = 1
  cpu     = "host"
  # 内存
  memory   = 4096
  scsihw   = "virtio-scsi-pci"
  bootdisk = "virtio0"
  # boot   = "order=virtio0" # 不能用这个,用这个启动不了

  disks {
    virtio {
      virtio0 {
        disk {
          # 硬盘设置,因计算的方式 101580M 代替 100G
          size      = "101580M"
          storage   = "local-lvm"
          replicate = true
          # 这个 replicate 必须要加,不然识别不了克隆出来的硬盘
        }
      }
    }
    scsi {
      scsi0 {
        cdrom {
          iso = "${local.iso_storage_pool}:${proxmox_cloud_init_disk.ci.id}"
        }
      }
    }
  }
  # 网络
  network {
    model  = "virtio"
    bridge = "vmbr0"
  }
  lifecycle {
    ignore_changes = [
      network,
    ]
  }
}

Cloud init 配置文件

这个就因人而异了,自己想配置啥就写啥

给出我的作为参考吧,是打算练习安装 k8s 的初始配置,自用,写得很烂

  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
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
#cloud-config

# 判断是否运行
bootcmd:
  - echo "Hello World. The time is now $(date -R)!" | tee /userdata_bootcmd

# 换源
apt:
  primary:
    - arches: [default]
      uri: https://mirrors.tuna.tsinghua.edu.cn/ubuntu/

# 时区
timezone: Asia/Shanghai

# ntp 服务
ntp:
  enabled: true
  servers:
    - ntp1.aliyun.com

# 安装软件
# package_update: true
# package_upgrade: true

# hostname
hostname: ubuntu

# ssh 公钥
ssh_authorized_keys:
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC5E3GWFRw0aQrLNDIZ3E6b5VmvzXgFI5DOhnxCDtLqqYDhJ8WQIVnPvXqDJu0ZIhSLudMX5Fng/WPO5ES7OnmZOLqla5Tq26leV4MrvIWgHlZfJuBJNc2smFLf68yxZYm0QFjqsxOK3tg0Mc2Hb+93maOCDGUY/+IkiXtgCrHNUqvA3NlaMYmNUARDoUX/eAiGCn/M7nrEN7XAM885/GXAkdyMQxIiLJpj6HrTSXalTj8G6sPap/5IHb0+Jbx+NW8W69UDkOYDEy17yyJzb6jv3TU3Qm1mCHO4R4LMA/LxQOxsSxqXQMzyNRZyHPO2UI6zPlWojlcucsLHrpZ0RhsK8UmDeyRW9zN1J9TRngLykvC6TnkBPtKdQ5jx1kgN6KG4UUCPgqjncc1f7kF30V4kX+dXUDWggM1wd8ICH1BRgbTIvZdbl/X9OnVRbqSzMF6soTdNIEbTZMvPSfFrTEmt0G44ZLqWk8NKXeFSCQKey103KNyD4pKBCTLcpQHg7qE= suyiiyii@PC-5950x

# 写入文件
write_files:
  # 欢迎信息
  - path: /etc/issue
    content: |
      \  Welcome to \S, customized by suyiiyii.
      \  Hostname: \n
      \  IP Address: \4
      \  IP Address: \6
      \  \t
    append: true
  - path: /etc/motd
    content: The OS image is customized by suyiiyii.
    append: true
  - path: /etc/ssh/sshd_config
    content: |
      PermitRootLogin yes
    append: true

# 用户
user:
  name: suyiiyii

chpasswd:
  expire: False
  users:
    - name: root
      password: $6$As3IUoJEdk6ep5xx$mlgkdV4lSIUDqn6SqdghuIYT/dOIg4C038DdqCIRrEFRKvmIpKjN4MGZry0wSQ8RoKcwa6qjkUR6gDhc0I2W/.
    - name: suyiiyii
      password: $6$As3IUoJEdk6ep5xx$mlgkdV4lSIUDqn6SqdghuIYT/dOIg4C038DdqCIRrEFRKvmIpKjN4MGZry0wSQ8RoKcwa6qjkUR6gDhc0I2W/.


final_message: |
  cloud-init has finished
  version: $version
  timestamp: $timestamp
  datasource: $datasource
  uptime: $uptime


runcmd:
  # 禁用 swap
  - swapoff -a
  - sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
  # 关闭 selinux
  - setenforce 0
  - sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config
  # 关闭防火墙
  - systemctl stop ufw
  - systemctl disable ufw
  # 使用官方命令和清华源安装 docker
  - for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do apt-get remove $pkg; done
  - apt-get update -y
  - apt-get install ca-certificates curl gnupg -y
  - install -m 0755 -d /etc/apt/keyrings
  - curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  - sudo chmod a+r /etc/apt/keyrings/docker.gpg
  - |
    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu \
      $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
      tee /etc/apt/sources.list.d/docker.list > /dev/null
  # - apt-get update -y
  # - apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

  # 使用 ubuntu 源安装 docker
  - apt-get install docker.io -y
  # 给用户组添加权限
  - usermod -aG docker suyiiyii
  # 导入 k8s 源
  - curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/kubernetes/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
  - echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/kubernetes/core:/stable:/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
  - apt-get update -y
  - apt-get install -y kubelet kubeadm kubectl
  - apt-mark hold kubelet kubeadm kubectl
  # 安装 neofetch
  - apt-get install neofetch -y

配置文件的渲染

这里才算是重头戏。当我们同时创建多台机器的时候,如果使用同样的配置文件,那么他们将会有同样的 hostname,同样的 ip。着并不是我们所期望的。

所以需要对机器的配置文件进行一定的个性化,这里我是使用 tf 对 ci 的配置文件进行一定的模板替换。

user-data 内,将变量设置为被 ${} 的占位符

1
2
# hostname
hostname: ${hostname}

然后在 tf,通过 templatefile 命令进行模板替换

1
 user_data = templatefile("user-data.yaml", { "hostname" = local.vm_name })

最后系统中的 hostname就会和local.vm_name 保持一致了,非常愉快。

目前只会这个最简单的替换,更多的不会了,先这样吧。😁

总结

这几天搞了这么久,终于爽了一把了。敲一行命令就可以部署一台可以直接使用的机器,不想要了也是敲一行命令就可以销毁,并且想要多少条就可以创建多少台,不喜欢还可以进一步自定义,实在是太有幸福感了。

可以在家开 IDC 了,再加个界面就可以躺着赚钱了。

目前还是有很多不足的,例如 ip 地址的分配问题。这个要再研究研究 tf 的配置文件要怎么写。

还有 apt,几台机器都去源下载同样的文件太浪费了,docker 也是,还要再搞个本地的缓存加速。

果然,,挖坑容易填坑难,我就想快速方便的创建机器,怎么要这么多事情。

Licensed under CC BY-NC-SA 4.0