By David Dixon
November 24, 2024
Lately I have been thinking about how to leverage a common framework to deploy to both on premise, and cloud providers for a multi-tenancy deployment
approach. Organizations are increasingly pivoting to cloud providers such as AWS, GCP, or Azure. Additionally, organizations may have either sunk cost or a need to upkeep their on
premise hardware in paralell. In this series we'll look at deploying AMX to both on premise and cloud providers.
One of the core elements for how we will deploy is the creation of a custom ISO. The custom ISO will allow us to autoprovision the infrastructure without the need for
user interaction. We will pass in information regarding the network configuration, disk partitioning, ssh key, keyboard layout, packages, and user credentials. This approach
provides a cost savings versus manually inputting each piece of information. Let's take a look at the process overview image below.
We will be installing our dependencies used to build the iso in step 1. Then, we stage the build environment in step 2. Next, we will unpack the source ISO for modification in step 3.
Step 4 modifies the grub boot menu to add our new auto-install option. In step 5 we have the custom, user-defined data mentioned in the paragraph above. Step 6 deals with
generating the new ISO. And finally we will test our ISO using Terraform + Powershell in step 7.
I am using Ubuntu 22.04 for my ISO, but feel free to choose which ISO works for you. You can download it here if you would like: https://releases.ubuntu.com/jammy/ubuntu-22.04.5-live-server-amd64.iso
Ok, now that we have an overview, let's get going on the ISO creation!
1. ISO Dependency Installation
We will be installing the dependencies used to create the custom ISO image. We'll use the following:
sudo apt install p7zip-full p7zip -y
sudo apt install xorriso -y
We also use wget to pull the latest ISO if you don't have a web browser enabled. Next, we are going to set up the build environment for our ISO.
mkdir 22_04-auto-ISO
cd 22_04-auto-ISO
mkdir source-files
wget https://releases.ubuntu.com/jammy/ubuntu-22.04.5-live-server-amd64.iso
7z -y x jammy-live-server-amd64.iso -osource-files
mv '[BOOT] ../BOOT'
menuentry "Ubuntu Server 22.04 Auto Installation" {
set gfxpayload=keep
linux /casper/vmlinuz quiet autoinstall ds=nocloud\;s=/cdrom/server/ ---
initrd /casper/initrd
}
Note the menuentry line; This can be whatever you want it to be in the quotes. For simplicity, I have named mine "Ubuntu Server 22.04 Auto Installation". The
entry above does the following:
mkdir source-files/server
If you want to, you can add more folders to contain alternative user-data configs, or extra grub configs pointing to the directories.
#cloud-config
autoinstall:
version: 1
# Remove interactive sections to avoid user input
storage:
layout:
name: lvm
match:
size: largest
locale: en_US.UTF-8
keyboard:
layout: us
identity:
hostname: amx-000
#Change ME! I use: openssl passwd -6
password: $6$gK6xB150l.......
username: ubuntu
ssh:
allow-pw: true
install-server: true
apt:
primary:
- arches: [default]
uri: http://us.archive.ubuntu.com/ubuntu/
packages:
- build-essential
- network-manager
- dkms
- emacs-nox
package_update: true
package_upgrade: true
late-commands:
# Changing from networkd to NetworkManager
# move existing config out of the way
- find /target/etc/netplan/ -name "*.yaml" -exec sh -c 'mv "$1" "$1-orig"' _ {} \;
# Create a new netplan and enable it
- |
cat <<EOF | sudo tee /target/etc/netplan/01-netcfg.yaml
network:
version: 2
renderer: NetworkManager
EOF
- curtin in-target --target /target netplan generate
- curtin in-target --target /target netplan apply
- curtin in-target --target /target systemctl enable NetworkManager.service
# Install NVIDIA driver (with apt-get flags)
- curtin in-target -- apt-get -y install --no-install-recommends nvidia-driver-520
You will need to update the password at minimum. You can use openssl passwd -6 to generate the credential. The rest of the example you're free to modify as needed.
This configuration file will allow us to perform an installation without requiring user input during the Distro installation.
xorriso -as mkisofs -r \
-V 'Ubuntu 22.04 LTS AUTO (EFIBIOS)' \
-o ../ubuntu-22.04-autoinstall.iso \
--grub2-mbr ../BOOT/1-Boot-NoEmul.img \
-partition_offset 16 \
--mbr-force-bootable \
-append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b ../BOOT/2-Boot-NoEmul.img \
-appended_part_as_gpt \
-iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7 \
-c '/boot.catalog' \
-b '/boot/grub/i386-pc/eltorito.img' \
-no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info \
-eltorito-alt-boot \
-e '--interval:appended_partition_2:::' \
-no-emul-boot \
.
Great, at this point you should have an ISO that is all ready to be deployed. Like I said in the title, we're going to be deploying on
premise using Hyper-V and Terraform. To deploy we'll be writing some powershell to do a bulk of the lifting since our target hypervisor is Hyper-V. We'll wrap
the powershell with Terraform so we can spin up quickly. Sound good, ok let's jump in on our powershell.
We'll start with the variable definition for what defines our VM parameters. Feel free to modify these to fit your needs depending on your paths, hardware resources,
and networking.
#This allows HyperV to create a virtual machine with multiple user input parameters below.
#The changeme comment should be used by the user to specify resources in their environment.
#Allow different values to be passed when running the script
param (
[string]$action
)
# VM parameters
$vmName = "CustomAMXUbuntuVM" #changeme
$vmPath = "D:\Virtual Machines\AMX_TEST" #changeme
$vhdPath = "$vmPath\$vmName.vhdx"
$image = "D:\OS\ubuntu-22.04-autoinstall.iso" #changeme
$vmswitch = "QLogic BCM5709C Gigabit Ethernet (NDIS VBD Client) #3 - Virtual Switch" #changeme
$cpu = 1 #changeme
$ram = 6GB #changeme
$vhdSize = 80GB #changeme
# Handle actions
if ($action -eq "create") {
# Create VM
Write-Output "Creating VM: $vmName"
New-VM -Name $vmName -Path $vmPath
Set-VM -Name $vmName -ProcessorCount $cpu -MemoryStartupBytes $ram
New-VHD -Path $vhdPath -SizeBytes $vhdSize
Add-VMHardDiskDrive -VMName $vmName -Path $vhdPath
Set-VMDvdDrive -VMName $vmName -Path $image
Connect-VMNetworkAdapter -VMName $vmName -SwitchName $vmswitch
Start-VM -Name $vmName
Write-Output "VM $vmName created and started successfully."
} elseif ($action -eq "destroy") {
# Destroy VM
Write-Output "Destroying VM: $vmName"
if (Get-VM -Name $vmName -ErrorAction SilentlyContinue) {
Stop-VM -Name $vmName -Force
Remove-VM -Name $vmName -Force
if (Test-Path -Path $vhdPath) {
Remove-Item -Path $vhdPath -Force
}
Write-Output "VM $vmName and associated resources have been removed."
} else {
Write-Output "VM $vmName does not exist. Nothing to destroy."
}
} else {
Write-Output "Invalid action specified. Use 'create' or 'destroy'."
exit 1
}
Ok, if that doesn't make sense and you need a reference I've put the script on github at the following location on
Github.
#TF file used with create_vm.ps1 on HyperV.
#Passes either the create or destroy action to the create_vm.ps1 script.
provider "local" {}
resource "null_resource" "ubuntu_amx_vm" {
# Provisioner to create the VM
provisioner "local-exec" {
command = "PowerShell -File D:\\Terraform_create_VM\\create_vm.ps1 -action create"
when = "create"
}
# Provisioner to destroy the VM
provisioner "local-exec" {
command = "PowerShell -File D:\\Terraform_create_VM\\create_vm.ps1 -action destroy"
when = "destroy"
}
}
terraform init
terraform apply
If all jobs completed without error, you should see your new VM initializing in Hyper-V. Double click the instance in the
Hyper-V manager.
Great job! We should have a resource that is building by itself using an unattended method. What happens if we want to remove the resource? Well, from our directory
with the .tf file we created issue we can issue the following:
terraform destroy