Terraform module to provision VMs (and their resources, included networks) on a bare metal Linux, using Terraform on top of opensource tools such as Libvirt, QEMU and KVM.
- OpenSSH
vx.x.x
- Terraform
v1.2.5+
All the following commands are executed from the root path of the repository
These can be declared as input vars inside a .tfvars
file too
export TF_VAR_SSH_HOST="XXX.XXX.XXX.XXX"
export TF_VAR_SSH_USERNAME="yourUsername"
export TF_VAR_SSH_PASSWORD="yourPassword"
export TF_VAR_SSH_KEY_PATH="~/.ssh/id_ed25519"
To build the ISOs we need to have installed
mkisofs
.
sudo apt install mkisofs
By the moment, only recent Ubuntu versions are supported. Feel free to extend the OS support by pushing your code to this repository.
# Copy current SSH key into the target host
echo ${TF_VAR_SSH_PASSWORD} | ssh-copy-id -f ${TF_VAR_SSH_USERNAME}@${TF_VAR_SSH_HOST}
# Give execution permissions to the helper scripts
chmod -R +x ./scripts
# Connect to the host machine by SSH and install the dependencies using passwordless authentication
# (user with sudo privileges required)
scp ./scripts/prepare-host-ubuntu.sh ${TF_VAR_SSH_USERNAME}@${TF_VAR_SSH_HOST}:/tmp
ssh ${TF_VAR_SSH_USERNAME}@${TF_VAR_SSH_HOST} "sudo bash ./tmp/prepare-host-ubuntu.sh ${TF_VAR_SSH_USERNAME}"
Using this module is pretty simple. It's only about including the module from git and passing data structures with the definition of your VMs composition
module "arm-virtual-machines" {
source = "git@github.com:achetronic/metal-cloud.git//terraform?ref=v1.0.0"
# Global configuration
globals = local.globals
# Configuration related to VMs directly
networks = local.networks
instances = local.instances
}
This module can be used to bootstrap VMs for any purpose, but we prepared a complete example for Kubernetes to show the most difficult scenario. The examples can be found inside examples directory
globals
structure is there for those variables that affects general aspects of your VMs composition:
locals {
# Globals definition
globals = {
# Configuration for SSH connection parameters
ssh_connection = {
host = var.SSH_HOST
username = var.SSH_USERNAME
password = var.SSH_PASSWORD
key_path = var.SSH_KEY_PATH
mode = "password"
}
# Parameters related to those files used/thrown at some point on VM creation
io_files = {
# Path to the folder containing SSH keys authorized in all the VMs
external_ssh_keys_path = "./files/input/external-ssh-keys"
# Path to the folder where instances' autogenerated SSH keys will be stored
instances_ssh_keys_path = "./files/output"
}
# Parameters related to the installable OS on VMs creation
os = {
# Distro version to use
# ATM, only ubuntu is supported, so the version is something like: 23.04
version = "23.04"
}
}
}
VMs are useless if they are not connected to anything. For this, you can include network
devices that can be invoked by VMs later. There are two type of devices supported by the module: nat
and macvtap
Let's see a macvtap
configuration.
Remember
macvtap
is intended to connect network interfaces from VMs, directly to some physical interface of the host
locals {
# Networks definition
networks = {
# Configuration for a macvtap device
external0 = {
mode = "macvtap"
# Host physical interface to attach
# the autogenerated virtual interface
interface = "eth0"
# Assignable IP address blocks in CIDR notation
dhcp_address_blocks = ["192.168.2.40/28"]
# Address to the gateway
gateway_address = "192.168.2.1"
}
}
}
What about crafting a NAT
device?
locals {
networks = {
# Configuration for a NAT
virnat0 = {
mode = "nat"
# Assignable IP address blocks in CIDR notation
dhcp_address_blocks = ["10.10.10.0/24"]
# Address to the gateway
gateway_address = "10.10.10.1"
}
}
}
The goal of this module is creating VMs, so let's create one of them:
locals {
instances = {
my-wonderful-vm-name = {
vcpu = 2
memory = 6144
disk = 20000000000
networks = [
{
name = "external0"
address = "192.168.2.41/24"
# Yes, MAC definition is mandatory
mac = "DA:C8:20:7A:37:BF"
}
]
}
}
}
Hey, I have a different architecture or a well known machine. Ok, let's define both fields:
hey, listen! At this moment, only
aarch64
andx86_64
architectures are supported. However, on ARM64 environments, the boards are not so standard as in x86_64. Because of that, it's possible you don't find the right value formachine
field. In that case, just setvirt
and some tweaks will be automatically applied under the hood to improve the support for them
locals {
instances = {
kube-master-0 = {
# Using 'OrangePi 5' as hypervisor. This SBC is quite new, so there is no specific machine type for it
# Use generic 'virt' to apply all needed patches for this kind of environments
# Ref: https://www.qemu.org/docs/master/system/target-arm.html
arch = "aarch64"
machine = "virt"
vcpu = 2
memory = 6144
disk = 20000000000
networks = [
{
name = "external0"
address = "192.168.2.41/24"
mac = "DA:C8:20:7A:37:BF"
}
]
}
}
}
Once you have defined them, just push the red button and create the chaos
terraform init && terraform apply
For security reasons, a random password and an SSH key-pair are auto-generated per instance.
This means that each instance has a different password and a different authorized SSH key.
They are stored in the tfstate
so execute a terraform state list
and then show the resource you need
by using terraform state show ···
When the terraform apply
is complete, all the SSH private key files are exported in order
to allow you to access or manage them.
There is a special directory located in the path defined with value globals.io_files.external_ssh_keys_path
.
This was created for the special case that several well-known SSH keys must be authorized
in all the instances at the same time.
This can be risky and must be used under your own responsibility. If you need it, place some .pub
key files
inside, and they will be directly configured and authorized in all the instances.
Of course, I'm open to external collaborations for this project. For doing it you must:
- Fork the repository
- Make your changes to the code
- Open a PR.
As an advise, the code will be reviewed and tested (always)