Skip to content
This repository was archived by the owner on Jun 29, 2022. It is now read-only.

Commit ff32e74

Browse files
committed
baremetal: integrate automated (re-)provisioning logic
The bare-metal platform lacked any understanding of whether an instance is actually running the configuration that lokoctl put into matchbox, because, when the configuration was updated, there was no notification to the user that PXE booting has to be done again for this instance. Also, it was not clear to the user when to boot from PXE because the PXE boot must happen after lokoctl populated matchbox with the (new) configuration but before any other steps are timing out. The goal of this patch is to bring the baremetal platform to an actually usable level and support automated provisioning and reprovisioning, in a configurable way regardless if IPMI is used or VMs are created. In addition, we don't want to require a complicated PXE boot for each configuration update because it is slow, fragile and needs a special DHCP infrastructure. Add user-defined commands to perform automated PXE provisioning at the right time, i.e. initally at the first run or when recreating a node. However, PXE booting is a long and maybe even manual process, or even impossible at production side due to the lack of an appropriate DHCP server. We can rely on Ignition to simulate reprovisioning by creating the first_boot flag file via SSH and issuing a reboot, which makes Ignition fetch the configuration from matchbox, and if we make sure to clean the root filesystem by formatting it, the result is the same as if reprovisioned was done with a PXE boot. This is achieved by a null resource in Terraform that executes a helper script which either does a PXE boot or uses SSH to trigger a reprovisioning with Ignition. It also handles the case of ignoring userdata changes for controller nodes to prevent losing etcd state. Since there is no notion of a baremetal node on the Terraform level (reminder: all this exercise here is done because we don't have a Terraform provider doing this for us) a local flag file is created under the asset folder. If it exists, the node was provisioned with PXE and SSH will be used for reprovisioning, if it does not exist, it will be provisioned with PXE during inital setup and for the next reprovisioning because the user forced recreating the node by deleting the flag file. Another flag file on the node is used to check whether a node was successfully reprovisioned. When SSH is used to reprovision, the kernel parameters for GRUB are updated directly because they are not part of the Ignition configuration. The copy-controller-secrets step is run after recreating a controller node, again since there is no notion of a node object by depending on the variables itself which define the node state. Also add a user-defined command to run after the PXE OS installation and before booting into the final OS. This is needed to set up persistent booting from disk after the PXE booting was configured. The whole patch is used by Racker, and can be tested either with the bootstrap/prepare.sh script to create VMs with lokoctl or by running Racker in the QEMU IPMI simulator environment through the racker-sim/ipmi-env.sh script and a Racker Docker image built with installer/conf.yaml pointing to this Lokomotive branch.
1 parent e5f0458 commit ff32e74

File tree

16 files changed

+253
-18
lines changed

16 files changed

+253
-18
lines changed

assets/terraform-modules/bare-metal/flatcar-linux/kubernetes/controller.tf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,22 @@ module "controller" {
1313
set_standard_hostname = false
1414
clc_snippets = concat(lookup(var.clc_snippets, var.controller_names[count.index], []), [
1515
<<EOF
16+
filesystems:
17+
- name: root
18+
mount:
19+
device: /dev/disk/by-label/ROOT
20+
format: ext4
21+
wipe_filesystem: true
22+
label: ROOT
1623
storage:
1724
files:
25+
- path: /ignition_ran
26+
filesystem: root
27+
mode: 0644
28+
contents:
29+
inline: |
30+
Flag file indicating that Ignition ran.
31+
Should be deleted by the SSH step that checks it.
1832
- path: /etc/hostname
1933
filesystem: root
2034
mode: 0644

assets/terraform-modules/bare-metal/flatcar-linux/kubernetes/controller_profiles.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
module "controller_profile" {
22
source = "../../../matchbox-flatcar"
33
count = length(var.controller_names)
4+
asset_dir = var.asset_dir
45
node_name = var.controller_names[count.index]
56
node_mac = var.controller_macs[count.index]
7+
node_domain = var.controller_domains[count.index]
68
download_protocol = var.download_protocol
79
os_channel = var.os_channel
810
os_version = var.os_version
@@ -17,4 +19,7 @@ module "controller_profile" {
1719
ignition_clc_config = module.controller[count.index].clc_config
1820
cached_install = var.cached_install
1921
wipe_additional_disks = var.wipe_additional_disks
22+
ignore_changes = true
23+
pxe_commands = var.pxe_commands
24+
install_pre_reboot_cmds = var.install_pre_reboot_cmds
2025
}

assets/terraform-modules/bare-metal/flatcar-linux/kubernetes/ssh.tf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ resource "null_resource" "copy-controller-secrets" {
8181
]
8282
}
8383

84+
85+
# Triggered when the Ignition Config changes (used to recreate a controller)
8486
triggers = {
87+
clc_config = module.controller[count.index].clc_config
88+
kernel_console = join(" ", var.kernel_console)
89+
kernel_args = join(" ", var.kernel_args)
8590
etcd_ca_cert = module.bootkube.etcd_ca_cert
8691
etcd_server_cert = module.bootkube.etcd_server_cert
8792
etcd_peer_cert = module.bootkube.etcd_peer_cert

assets/terraform-modules/bare-metal/flatcar-linux/kubernetes/variables.tf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,15 @@ variable "wipe_additional_disks" {
227227
description = "Wipes any additional disks attached, if set to true"
228228
default = false
229229
}
230+
231+
variable "pxe_commands" {
232+
type = string
233+
default = "echo 'you must (re)provision the node by booting via iPXE from http://MATCHBOX/boot.ipxe'; exit 1"
234+
description = "shell commands to execute for PXE (re)provisioning, with access to the variables $mac (the MAC address), $name (the node name), and $domain (the domain name), e.g., 'bmc=bmc-$domain; ipmitool -H $bmc power off; ipmitool -H $bmc chassis bootdev pxe; ipmitool -H $bmc power on'"
235+
}
236+
237+
variable "install_pre_reboot_cmds" {
238+
type = string
239+
default = "true"
240+
description = "shell commands to execute on the provisioned host after installation finished and before reboot, e.g., docker run --privileged --net host --rm debian sh -c 'apt update && apt install -y ipmitool && ipmitool chassis bootdev disk options=persistent'"
241+
}

assets/terraform-modules/bare-metal/flatcar-linux/kubernetes/worker.tf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,22 @@ module "worker" {
1212
set_standard_hostname = false
1313
clc_snippets = concat(lookup(var.clc_snippets, var.worker_names[count.index], []), [
1414
<<EOF
15+
filesystems:
16+
- name: root
17+
mount:
18+
device: /dev/disk/by-label/ROOT
19+
format: ext4
20+
wipe_filesystem: true
21+
label: ROOT
1522
storage:
1623
files:
24+
- path: /ignition_ran
25+
filesystem: root
26+
mode: 0644
27+
contents:
28+
inline: |
29+
Flag file indicating that Ignition ran.
30+
Should be deleted by the SSH step that checks it.
1731
- path: /etc/hostname
1832
filesystem: root
1933
mode: 0644

assets/terraform-modules/bare-metal/flatcar-linux/kubernetes/worker_profiles.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
module "worker_profile" {
22
source = "../../../matchbox-flatcar"
33
count = length(var.worker_names)
4+
asset_dir = var.asset_dir
45
node_name = var.worker_names[count.index]
56
node_mac = var.worker_macs[count.index]
7+
node_domain = var.worker_domains[count.index]
68
download_protocol = var.download_protocol
79
os_channel = var.os_channel
810
os_version = var.os_version
@@ -17,4 +19,6 @@ module "worker_profile" {
1719
ignition_clc_config = module.worker[count.index].clc_config
1820
cached_install = var.cached_install
1921
wipe_additional_disks = var.wipe_additional_disks
22+
pxe_commands = var.pxe_commands
23+
install_pre_reboot_cmds = var.install_pre_reboot_cmds
2024
}

assets/terraform-modules/matchbox-flatcar/profiles.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ data "ct_config" "install-ignitions" {
3333
kernel_console = join(" ", var.kernel_console)
3434
kernel_args = join(" ", var.kernel_args)
3535
wipe_additional_disks = var.wipe_additional_disks
36+
install_pre_reboot_cmds = var.install_pre_reboot_cmds
3637
# only cached-container-linux profile adds -b baseurl
3738
baseurl_flag = ""
3839
mac_address = var.node_mac
@@ -80,6 +81,7 @@ data "ct_config" "cached-install-ignitions" {
8081
kernel_console = join(" ", var.kernel_console)
8182
kernel_args = join(" ", var.kernel_args)
8283
wipe_additional_disks = var.wipe_additional_disks
84+
install_pre_reboot_cmds = var.install_pre_reboot_cmds
8385
# profile uses -b baseurl to install from matchbox cache
8486
baseurl_flag = "-b ${var.http_endpoint}/assets/flatcar"
8587
mac_address = var.node_mac
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# (executed in-line, #!/... would be ignored)
2+
# Terraform template variable substitution:
3+
name=${name}
4+
domain=${domain}
5+
mac=${mac}
6+
asset_dir=${asset_dir}
7+
ignore_changes=${ignore_changes}
8+
kernel_args="${kernel_args}"
9+
kernel_console="${kernel_console}"
10+
ignition_endpoint="${ignition_endpoint}"
11+
# From now on use $var for dynamic shell substitution
12+
13+
if test -f "$asset_dir/$mac" && [ "$(cat "$asset_dir/$mac")" = "$domain" ]; then
14+
echo "found $asset_dir/$mac containing $domain, skipping PXE install"
15+
node_exists=yes
16+
else
17+
echo "$asset_dir/$mac does not contain $domain, forcing PXE install"
18+
node_exists=no
19+
fi
20+
21+
if [ $node_exists = yes ]; then
22+
if $ignore_changes ; then
23+
echo "Keeping old config because 'ignore_changes' is set."
24+
exit 0
25+
else
26+
# run single commands that can be retried without a side effect in case the connection got disrupted
27+
count=30
28+
while [ $count -gt 0 ] && ! ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 core@$domain sudo touch /boot/flatcar/first_boot; do
29+
sleep 1
30+
count=$((count - 1))
31+
done
32+
if [ $count -eq 0 ]; then
33+
echo "error reaching $domain via SSH, please remove the $asset_dir/$mac file to force a PXE install"
34+
exit 1
35+
fi
36+
echo "created the first_boot flag file to reprovision $domain"
37+
count=5
38+
while [ $count -gt 0 ] && ! ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 core@$domain "printf 'set linux_append=\"$kernel_args ignition.config.url=$ignition_endpoint?mac=$mac&os=installed\"\\nset linux_console=\"$kernel_console\"\\n' | sudo tee /usr/share/oem/grub.cfg"; do
39+
sleep 1
40+
count=$((count - 1))
41+
done
42+
if [ $count -eq 0 ]; then
43+
echo "error reaching $domain via SSH, please retry"
44+
exit 1
45+
fi
46+
count=5
47+
while [ $count -gt 0 ] && ! ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 core@$domain sudo systemctl reboot; do
48+
sleep 1
49+
count=$((count - 1))
50+
done
51+
if [ $count -eq 0 ]; then
52+
echo "error reaching $domain via SSH, please reboot manually"
53+
exit 1
54+
fi
55+
echo "rebooted the $domain"
56+
fi
57+
else
58+
# the user may provide ipmitool commands or any other logic for forcing a PXE boot
59+
${pxe_commands}
60+
fi
61+
62+
echo "checking that $domain comes up"
63+
count=600
64+
# check that we can reach the node and that it has the flag file which we remove here, indicating a reboot happened which prevents a race when issuing the reboot takes longer (both the systemctl reboot and PXE case)
65+
# Just in case the connection breaks and SSH may report an error code but still execute successfully, we will first check file existence and then delete with "rm -f" to be able to rerun both commands.
66+
# This sequence gives us the same error reporting as just running "rm" once.
67+
while [ $count -gt 0 ] && ! ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 core@$domain test -f /ignition_ran; do
68+
sleep 1
69+
count=$((count - 1))
70+
done
71+
if [ $count -eq 0 ]; then
72+
echo "error: failed verifying with SSH if $domain came up by checking the /ignition_ran flag file"
73+
exit 1
74+
fi
75+
count=5
76+
while [ $count -gt 0 ] && ! ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 core@$domain sudo rm -f /ignition_ran; do
77+
sleep 1
78+
count=$((count - 1))
79+
done
80+
if [ $count -eq 0 ]; then
81+
echo "error: failed to remove the /ignition_ran flag file on $domain"
82+
exit 1
83+
else
84+
echo "$domain came up again"
85+
fi
86+
# only write the state file once the system is up, this allows to rerun lokoctl if the first PXE boot did not work and it will try again
87+
echo $domain > "$asset_dir/$mac"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
resource "null_resource" "reprovision-node-when-ignition-changes" {
2+
# Triggered when the Ignition Config changes
3+
triggers = {
4+
ignition_config = matchbox_profile.node.raw_ignition
5+
kernel_args = join(" ", var.kernel_args)
6+
kernel_console = join(" ", var.kernel_console)
7+
}
8+
# Wait for the new Ignition config object to be ready before rebooting
9+
depends_on = [matchbox_group.node]
10+
# Trigger running Ignition on the next reboot (first_boot flag file) and reboot the instance, or, if the instance needs to be (re)provisioned, run external commands for PXE booting (also runs on the first provisioning)
11+
provisioner "local-exec" {
12+
command = templatefile("${path.module}/pxe-helper.sh.tmpl", { domain = var.node_domain, name = var.node_name, mac = var.node_mac, pxe_commands = var.pxe_commands, asset_dir = var.asset_dir, kernel_args = join(" ", var.kernel_args), kernel_console = join(" ", var.kernel_console), ignition_endpoint = format("%s/ignition", var.http_endpoint), ignore_changes = var.ignore_changes })
13+
}
14+
}

assets/terraform-modules/matchbox-flatcar/templates/install.yaml.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ storage:
6767
echo 'set linux_append="${kernel_args} ignition.config.url=${ignition_endpoint}?mac=${mac_address}&os=installed"' >> /tmp/oemfs/grub.cfg
6868
echo 'set linux_console="${kernel_console}"' >> /tmp/oemfs/grub.cfg
6969
umount /tmp/oemfs
70+
${install_pre_reboot_cmds}
7071
systemctl reboot
7172
passwd:
7273
users:

0 commit comments

Comments
 (0)