-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.tf
398 lines (350 loc) · 14.5 KB
/
main.tf
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
terraform {
required_version = "> 1.9.0"
required_providers {
/*
API provisioning support for Proxmox
see
- https://registry.terraform.io/providers/Telmate/proxmox/latest
*/
proxmox = {
// https://developer.hashicorp.com/terraform/cli/config/config-file#provider-installation
source = "Telmate/proxmox"
version = "3.0.1-rc4" // can't say '>= 3.0.1-rc4'
}
/*
Convert a butane configuration to an ignition JSON configuration
WARNING: The current flatcar stable release requires ignition v3.3.0 configurations, which
are supported by the v0.12 provider. The v0.13 CT provider generated v3.4.0 ignition
configurations which are not supported with Flatcar v3510.2.6. This is all clearly documented in
the git [README.md](https://github.com/poseidon/terraform-provider-ct)
see
- https://github.com/poseidon/terraform-provider-ct
- https://registry.terraform.io/providers/poseidon/ct/latest
- https://registry.terraform.io/providers/poseidon/ct/latest/docs
- https://www.flatcar.org/docs/latest/provisioning/config-transpiler/
*/
ct = {
source = "lucidsolns/ct"
version = ">= 0.13.1"
}
/*
see
- https://registry.terraform.io/providers/hashicorp/null
*/
null = {
source = "hashicorp/null"
version = ">= 3.2.2"
}
}
}
provider "proxmox" {
pm_api_url = var.pm_api_url
pm_tls_insecure = var.pm_tls_insecure
pm_user = var.pm_user
pm_password = var.pm_password
}
locals {
has_butane = var.butane_conf != null && var.butane_conf != ""
has_virtiofs = var.virtiofs != null && length(var.virtiofs) > 0
# The base path used for loading butane snippet files
butane_snippet_path = var.butane_path != null ? var.butane_path : dirname(var.butane_conf)
}
/**
Provision a VM with an ignition configuration file.
see
- https://registry.terraform.io/providers/Telmate/proxmox/latest/docs/resources/vm_qemu
- https://pve.proxmox.com/wiki/Manual:_qm.conf
- https://github.com/qemu/qemu/blob/master/docs/specs/fw_cfg.rst
- https://www.flatcar.org/docs/latest/installing/vms/libvirt/
- https://austinsnerdythings.com/2021/09/01/how-to-deploy-vms-in-proxmox-with-terraform/
- https://www.flatcar.org/
- https://github.com/flatcar/ignition
- https://www.qemu.org/docs/master/specs/fw_cfg.html
*/
resource "proxmox_vm_qemu" "proxmox_flatcar_vm" {
count = var.vm_count # just want 1 for now, set to 0 and apply to destroy VM
vmid = var.vm_count > 1 ? var.vmid + count.index : var.vmid
name = var.vm_count > 1 ? "${var.name}-${count.index + 1}" : var.name
target_node = var.target_node
pool = var.pool
# Create a VM using the flatcar qemu image, and give it a version. This will mean
# a linked clone can be used to reduce the storage requirements when a large number
# of clones are created.
#
# Download the flatcar_qemu image from https://www.flatcar.org/releases and
clone = var.template_name
full_clone = var.full_clone
clone_wait = 0
#
# The description for the VM is set in the 'notes' field of the VM. This field
# supports markdown.
#
# This field# is hijacked by the provisioning process as a place to store data
# that can be configured via the API and can be read from the hook scripts.
#
# Note: Terraform (at least on Windows) loves putting CRLF sequences to the
# string, which just causes problems (so remove the CR's).
#
desc = replace(<<EOT
%{if var.description != null && var.description != ""~}
${var.description}
%{endif~}
A VM provisioned with Terraform from template ${var.template_name} on ${timestamp()}
## Configuration
```
%{if local.has_butane ~}
hook-script: local:snippets/cloudinit-to-ignition
cloud-init: ${proxmox_cloud_init_disk.ignition_cloud_init[count.index].id}
%{endif ~}
%{if local.has_virtiofs ~}
hook-script: local:snippets/virtiofsd.pl
%{for i, fs in var.virtiofs[*]~}
virtiofs: --path "${fs.dirid}" --socket /run/virtiofs/vm${var.vmid + count.index}-fs${i}
%{endfor~}
%{endif ~}
```
EOT
, "\r", "")
/*
The QEMU arguments are being used where Proxmox doesn't have first class support
for a QEMU feature. This includes:
1. Ignition support via the fw_cfg interface
2. virtiofs support
To make the args generate code more readable, the argument are created with line
breaks in a multiline format, then the linebreaks are replaced with spaces so that
the generated options are machine readable.
fw_cfg
======
The 'arguments' parameter for QEMU is parsed in such a way that the string is not
treated opaquely. The options are split at comma's (',') causing ambiguity with
options without a value.
The doubling up of comma's in the fw_cfg configuration overcomes this limitation.
This is documented in the Qemu documentation in the 'blockdev drive -file' section.
Setting this args parameter when creating a VM requires local root access
with password authentication. The ignition file is created in the `/etc/pve/...`
directory by the helper hook script.
virtio
======
Note: Each virtiofs declaration also requires a directive in the description
to get the hookscript to run an instance of the virtiofsd (rust) daemon. See
the generation of the description field.
*/
args = trimspace(replace(
<<-EOT
%{if local.has_butane}
-fw_cfg name=opt/org.flatcar-linux/config,file=/etc/pve/local/ignition/${var.vmid + count.index}.ign
%{endif}
%{if local.has_virtiofs}
-object memory-backend-file,id=virtiofs-mem,size=${var.memory}M,mem-path=/dev/shm,share=on
-machine memory-backend=virtiofs-mem
%{for i, fs in var.virtiofs[*]}
-chardev socket,id=virtfs${i},path=/run/virtiofs/vm${var.vmid + count.index}-fs${i}
-device vhost-user-fs-pci,queue-size=1024,chardev=virtfs${i},tag=${fs.tag}
%{endfor}
%{endif}
%{for i, fs in var.plan9fs[*]}
-virtfs local,path=${fs.dirid},mount_tag=${fs.tag},security_model=${fs.security_model},id=p9-vm${var.vmid}-fs${i}${fs.readonly ? ",readonly":""},multidevs=${fs.multidevs}
%{endfor}
EOT
,
"/[\r\n]+/",
" "))
/*
Enable serial port support. Where the guest provides serial console
support (which flatcar does out of the box), diagnostics around booting
and crashing are vastly simplified.
Use the following command to access the serial port/terminal:
qm terminal <vm_id>
This should create QEMU equivalent option:
-serial unix:/var/run/qemu-server/101.serial,server,nowait
For ignition errors, look in /run/initramfs/rdsosreport.txt
# grep CRITICAL /run/initramfs/rdsosreport.txt
see:
- https://pve.proxmox.com/wiki/Serial_Terminal
*/
serial {
id = 0
type = "socket"
}
# The qemu agent must be running in the flatcar instance so that Proxmox can
# identify when the VM is up (see https://github.com/flatcar/Flatcar/issues/737)
agent = var.agent ? 1 : 0
timeouts {
# use terraform timeouts instead of 'guest_agent_ready_timeout'
create = "60s"
update = "60s"
default = "120s"
}
# The connection info is obtained via the guest agent once the VM is
# up and running. This requires the ignition configuration to 'work' and the
# agent to be running. This will provide feedback that the VM is operational.
#
# If this fails, the following error is produced:
# Warning: define_connection_info is %t, no further action.
define_connection_info = true
# Generally use UEFI ("ovmf") where possible - the default is seabios
bios = var.bios
# There seems to be an issue here with duplication
os_type = var.os_type # qemu identifier
qemu_os = var.os_type # qemu identifier
cores = var.cores
sockets = 1
cpu = var.cpu
memory = var.memory
tags = join(";", sort(var.tags)) # Proxmox sorts the tags, so sort them here to stop change thrash
onboot = var.onboot
startup = var.startup
boot = var.boot
scsihw = "virtio-scsi-single"
// Support an array of virtio network adapters.
//
// If multiple VMs are created at the same time then the macaddr may be
// duplicated due to the time epoch being used to initialise the random
// seed (seen on Windows). Default to using the Telmate provider 'repeatable'
// option to generate a non-time based MAC address.
dynamic "network" {
for_each = var.networks
content {
model = "virtio"
bridge = network.value.bridge
tag = network.value.tag
mtu = network.value.mtu
// The generated mac address is not unique if multiple VMs are created
// at the same time (best guess is that the same time epoch is used to seed
// a random number).
//
// This code takes a similar approach to the terraform API with the 'repeatable'
// strategy used in `config_qemu.go`. However the Linux MAC vendor prefix ('00:18:59')
// is replaced with the 16 bits of hash generated from the vm name.
//
// In the future a repeatable address with the proxmox registered 'bc:24:11'
// address should be used.
//
// The overall MAC address is:
// - a zero for the top 8 bits
// - 16 bits of md5 of the vm name
// - 19 bits of the VM id
// - 5 bits of the interface index
//
// see:
// - https://maclookup.app/macaddress/BC2411
macaddr = network.value.macaddr != null && network.value.macaddr != "" ? network.value.macaddr : format(
"%2.2x:%s:%s:%2.2x:%2.2x:%2.2x",
0,
substr(md5(var.name), 1, 2),
substr(md5(var.name), 3, 2),
floor(((var.vmid + count.index) * 32 + index(var.networks, network.value)) / 65536) % 256,
floor(((var.vmid + count.index) * 32 + index(var.networks, network.value)) / 256) % 256,
((var.vmid + count.index) * 32 + index(var.networks, network.value)) % 256)
}
}
// Support a list of optional disks (in addition to those inherited from the cloned
// template). Getting these expressed so they don't conflict with disks inherited
// from the template can be problematic (use slots).
//
// see
// - https://github.com/Telmate/terraform-provider-proxmox/blob/master/docs/resources/vm_qemu.md#disks-block
// - https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
dynamic "disk" {
for_each = var.disks
content {
slot = disk.value.slot
storage = disk.value.storage
type = disk.value.type
id = disk.value.id
asyncio = disk.value.asyncio
backup = disk.value.backup
cache = disk.value.cache
discard = disk.value.discard
disk_file = disk.value.disk_file
emulatessd = disk.value.emulatessd
format = disk.value.format
iothread = disk.value.iothread
iso = disk.value.iso
linked_disk_id = disk.value.linked_disk_id
passthrough = disk.value.passthrough
readonly = disk.value.readonly
replicate = disk.value.replicate
serial = disk.value.serial
size = disk.value.size
wwn = disk.value.wwn
}
}
lifecycle {
prevent_destroy = false # this resource should be immutable **and** disposable
create_before_destroy = false
ignore_changes = [
disks, # the disk is provisioned in the template and inherited (but not defined here]
desc # the description on the first start, then the user can change it in the UI
]
replace_triggered_by = [
null_resource.node_replace_trigger[count.index].id
]
}
}
/**
Convert a butane configuration to an ignition JSON configuration. The template supports
multiple instances (a count) so that each configuration can be slightly changed.
see
- https://github.com/poseidon/terraform-provider-ct
- https://registry.terraform.io/providers/poseidon/ct/latest
- https://registry.terraform.io/providers/poseidon/ct/latest/docs
- https://www.flatcar.org/docs/latest/provisioning/config-transpiler/
- https://developer.hashicorp.com/terraform/language/functions/templatefile
*/
data "ct_config" "ignition_json" {
count = local.has_butane ? var.vm_count : 0
content = templatefile(var.butane_conf, {
"vm_id" = var.vm_count > 1 ? var.vmid + count.index : var.vmid
"vm_name" = var.vm_count > 1 ? "${var.name}-${count.index + 1}" : var.name
"vm_count" = var.vm_count,
"vm_index" = count.index,
})
strict = true
pretty_print = true
files_dir = local.butane_snippet_path
snippets = [
for s in var.butane_conf_snippets : templatefile("${local.butane_snippet_path}/${s}", {
"vm_id" = var.vm_count > 1 ? var.vmid + count.index : var.vmid
"vm_name" = var.vm_count > 1 ? "${var.name}-${count.index + 1}" : var.name
"vm_count" = var.vm_count,
"vm_index" = count.index,
})
]
}
/*
Create a cloud-init ISO with the ignition configuration as the `meta data` file.
This is a blatant hack/hijack of the meta-data, as the ignition file is not
a cloud-init configuration. The ISO will not be attached to the VM and will have
a lifecycle that is independent of the VM (i.e. if the VM is deleted, then a manual
deletion of the cloud-init ISO will be required).
The ignition configuration is put into the ISO as plain text (it can be formatted/pretty
or in a canonical form). No escaping, or base64 encoding is performed.
This strategy is used as it allows an arbitrary size configuration file to
be provisioned into the proxmox nodes. Using fields in the main configuration for
a VM have size restrictions.
see:
- https://registry.terraform.io/providers/Telmate/proxmox/latest/docs/guides/cloud_init
*/
resource "proxmox_cloud_init_disk" "ignition_cloud_init" {
count = local.has_butane ? var.vm_count : 0
name = var.vm_count > 1 ? "${var.name}-${count.index + 1}" : var.name
pve_node = var.target_node
storage = var.cloud_init_storage
meta_data = data.ct_config.ignition_json[count.index].rendered
}
/**
A null resource to track changes, so that the immutable VM is recreated.
*/
resource "null_resource" "node_replace_trigger" {
count = var.vm_count
# Changes to any instance of the cluster requires re-provisioning
triggers = {
"ignition" = local.has_butane ? data.ct_config.ignition_json[count.index].rendered : ""
"virtiofs" = <<EOT
%{for fs in var.virtiofs[*]~}
fs.devid,fs.tag
%{endfor~}
EOT
}
}