diff --git a/README.md b/README.md index edb6ee7..5d0bebf 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,16 @@ The folder *multiple* contains two subfolders, one with shared modules and the o The idea is to reuse modules across multiple virtual machines and operating systems. ``` -./multiple: +.: environments shared_modules -./multiple/environments: +./environments: cloud_init.yaml ubuntu-cloud-server-2404-bios -./multiple/environments/ubuntu-cloud-server-2404-bios: +./environments/ubuntu-cloud-server-2404-bios: ubuntu-cloud-server-2404-bios.tf -./multiple/shared_modules: +./shared_modules: cloud-init.tf domain.tf network.tf outputs.tf pool.tf provider.tf variables.tf volume.tf ``` @@ -29,320 +29,92 @@ cloud-init.tf domain.tf network.tf outputs.tf pool.tf provider.tf variable - [QEMU](https://www.qemu.org/) - [libvirt](https://libvirt.org/) - [Terraform provider for Libvirt](https://github.com/dmacvicar/terraform-provider-libvirt) +- An SSH key pair to connect to machines that are deployed using cloud-init ## Assumptions -Your Linux x86_64-based machine has at least 4 GB of available memory and 2 CPUs. +- Your Linux x86_64-based machine has at least 4 GB of available memory and 2 CPUs ## How to use it - Clone this repository -- Go to folder *example* -- Execute the following commands, which will download and install the required Terraform provider if not already present +- Run the following to generate a public key pair ``` -$ terraform init - -Initializing the backend... -Initializing provider plugins... -- Reusing previous version of dmacvicar/libvirt from the dependency lock file -- Using previously-installed dmacvicar/libvirt v0.8.3 - -Terraform has been successfully initialized! - -[...] +$ ssh-keygen -t rsa -b 4096 -f ~/.ssh/terraform_key -C "terraform-deployment" ``` -- The following command will plan the deployment, describing actions that will be taken when applied +- Make the script executable +``` +$ chmod +x update_ssh_keys.sh +``` + +- Run the script (it will use terraform_key by default), and will update all `main.tf` file so that they use the previously generated key: + +``` +$ ./update_ssh_keys.sh +``` + +> Alternatively, you can use your own public key and update it manually in the `main.tf` deployment file + +- Navigate to one of the available deployment + +``` +$ cd environments/ubuntu-cloud-server-2404-bios/ +``` + +- Initialize your terraform environment + +``` +$ terraform init +``` +- Plan the deployment ``` $ terraform plan - -[...] - -Terraform will perform the following actions - -# A cloud-init ISO disk is created, which provides pre-configured settings and scripts that are applied to a cloud-native disk image during its initial boot. Without it, no user would be created and it would not be possible to log into the virtual machine - - + resource "libvirt_cloudinit_disk" "commoninit" { - + name = "commoninit.iso" - + pool = "ubuntu-bios" - -[...] - - } - -# The libvirt domain or virtual machine will be created - - + resource "libvirt_domain" "domain" { - + cloudinit = (known after apply) - -[...] - - } - -# Here, a libvirt pool to store the virtual machine disk image will be created. It should be possible to use the default one - - + resource "libvirt_pool" "ubuntu-bios" { - -[...] - - + name = "ubuntu-bios" - + type = "dir" - - + target { - + path = "/tmp/ubuntu-bios" - } - } - -# A qcow2 disk volume will be created and stored in the previously created pool, based on a Ubuntu noble (24.04) hosted cloud image - - + resource "libvirt_volume" "ubuntu-qcow2" { - + format = "qcow2" - -[...] - -# The plan summaries the action to be taken, which in this case is about creating resources - -Plan: 4 to add, 0 to change, 0 to destroy. ``` -- The last command will carry out the plan +- Deploy ``` -$ terraform apply - -[...] - -Do you want to perform these actions? - Terraform will perform the actions described above. - Only 'yes' will be accepted to approve. - - Enter a value: yes - -# The actions are carried out -libvirt_pool.ubuntu-bios: Creating... -libvirt_pool.ubuntu-bios: Creation complete after 0s - -[...] - -Apply complete! Resources: 4 added, 0 changed, 0 destroyed. +$ terraform deploy ``` -- Identify the created machine +- Identify the name of the machine, which requires elevated privileges ``` -$ sudo virsh list --all - - Id Name State ---------------------------------------------- - 10 ubuntu-cloud-server-2404-0 running +# virsh list --all + Id Name State +-------------------------------------------- + 2 u24-bios-0 running ``` -- Determine its IP address +- Fetch IP address ``` -$ sudo virsh domifaddr ubuntu-cloud-server-2404-0 +# virsh domifaddr u24-bios-0 +``` + +- Connect to the machine with the user `groot` + +``` +$ ssh groot@10.17.3.107 ``` ``` - Name MAC address Protocol Address ----------------------------------------------------------------- - vnet3 52:54:00:e2:51:c0 ipv4 192.168.122.24/24 +groot@ubuntu:~$ ``` -- Connect to the machine +- Logout ``` -$ ssh root@192.168.122.24 - -[...] - -# Use the password defined in the cloud-init.cfg file - -root@192.168.122.24's password: - -Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.8.0-71-generic x86_64) -[...] - System information as of Tue Aug 26 10:40:49 UTC 2025 - - System load: 0.0 Processes: 113 - Usage of /: 67.4% of 2.35GB Users logged in: 0 - Memory usage: 5% IPv4 address for ens3: 192.168.122.24 - Swap usage: 0% +$ exit ``` -- Exit the virtual machine - -``` -root@ubuntu$ exit -``` - -- To destroy the virtual machine, execute the following command +- Destroy the machine ``` $ terraform destroy - -[...] - -Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: - - destroy - -Terraform will perform the following actions: - - # libvirt_cloudinit_disk.commoninit will be destroyed - - resource "libvirt_cloudinit_disk" "commoninit" { - -[...] - - # libvirt_domain.domain[0] will be destroyed - - resource "libvirt_domain" "domain" { - - arch = "x86_64" -> null - -[...] - - } - } - - # libvirt_pool.ubuntu2 will be destroyed - - resource "libvirt_pool" "ubuntu2" { - - allocation = 798310400 -> null - - available = 16019255296 -> null - -[...] - - } - } - - # libvirt_volume.ubuntu-qcow2 will be destroyed - - resource "libvirt_volume" "ubuntu-qcow2" { - -[...] - - } - -Plan: 0 to add, 0 to change, 4 to destroy. - -Do you really want to destroy all resources? - Terraform will destroy all your managed infrastructure, as shown above. - There is no undo. Only 'yes' will be accepted to confirm. - - Enter a value: yes - -libvirt_domain.domain[0]: Destroying... [id=611d5ede-e4b4-4ca5-ad83-83030942a6b5] -libvirt_domain.domain[0]: Destruction complete after 0s -libvirt_cloudinit_disk.commoninit: Destroying... [id=/tmp/cluster_storage2/commoninit.iso;5f4e08ef-ad51-484f-a9f2-c926f582974a] -libvirt_volume.ubuntu-qcow2: Destroying... [id=/tmp/cluster_storage2/ubuntu-qcow2] -libvirt_cloudinit_disk.commoninit: Destruction complete after 0s -libvirt_volume.ubuntu-qcow2: Destruction complete after 0s -libvirt_pool.ubuntu2: Destroying... [id=dbd62f8b-5d09-4e96-87e2-88e95c582896] -libvirt_pool.ubuntu2: Destruction complete after 0s - -Destroy complete! Resources: 4 destroyed. -``` - - - -## Explanations - -Let's take a look inside the *ubuntu-cloud-server-2404-bios* folder, which contains two files, *ubuntu-cloud-server-2404-bios.tf* and *cloud_init.cfg* - -The first file *ubuntu-cloud-server-2404-bios.tf* contains the main configuration for the Terraform deployment. - -- It starts by defining the required Terraform version and provider - -``` -terraform { - required_version = ">= 0.13" - required_providers { - libvirt = { - source = "dmacvicar/libvirt" - version = "0.8.3" - } - } -} -``` - -- The specific provider is defined here - -``` -provider "libvirt" { - uri = "qemu:///system" -} -``` - -> The [connection URI](https://libvirt.org/uri.html#qemu-qemu-and-kvm-uris) of the libvirt instance can be defined. One could for instance specific a libvirt instance that is hosted remotely - -- A libvirt pool, to store the virtual machine image, is created: - -``` -resource "libvirt_pool" "ubuntu-bios" { - name = "ubuntu-bios" - type = "dir" - target { - path = "/tmp/ubuntu-bios" - } -} -``` - -- The cloud-init user data will be fetched from a specific file whose path has to be declared: - -``` -data "template_file" "user_data" { - template = file("${path.module}/cloud_init.cfg") -} -``` - -- The ISO cloud-init disk will be created: - -``` -resource "libvirt_cloudinit_disk" "commoninit" { - name = "commoninit.iso" - user_data = data.template_file.user_data.rendered - pool = libvirt_pool.ubuntu-bios.name -} -``` - -- Perhaps the most important, the domain will be created: - -> Values can be adjusted, such as memory or vCPU counts. In the examples, multiple virtio-based device hardware are created, such as a virtio-gpu. - -``` -resource "libvirt_domain" "domain" { - count = 1 - name = "ubuntu-cloud-server-2404-${count.index}" - memory = "4092" - vcpu = 2 - cloudinit = libvirt_cloudinit_disk.commoninit.id - - cpu { - mode = "host-model" - } - - disk { - volume_id = libvirt_volume.ubuntu-qcow2.id - } - - console { - type = "pty" - target_port = "0" - target_type = "virtio" - } - - video { - type = "virtio" - } - - tpm { - backend_type = "emulator" - backend_version = "2.0" - } - - network_interface { - network_name = "default" - } - -} ``` ## Resources diff --git a/update_ssh_keys.sh b/update_ssh_keys.sh new file mode 100755 index 0000000..e1233b0 --- /dev/null +++ b/update_ssh_keys.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# Script to automatically update SSH keys in all main.tf files +# This script looks for terraform_key (or terraform_key.pub) in ~/.ssh directory + +# Function to display usage +usage() { + echo "Usage: $0 [ssh_key_name]" + echo " ssh_key_name: Name of the SSH key pair (default: terraform_key)" + echo "" + echo "Example:" + echo " $0 # Uses default 'terraform_key'" + echo " $0 my_custom_key # Uses 'my_custom_key' and 'my_custom_key.pub'" + exit 1 +} + +# Set the SSH key name (default to terraform_key) +SSH_KEY_NAME="${1:-terraform_key}" + +# Expand the home directory properly +HOME_DIR="${HOME:-/home/$(whoami)}" +SSH_KEY_PATH="$HOME_DIR/.ssh/$SSH_KEY_NAME" +SSH_KEY_PUB_PATH="$HOME_DIR/.ssh/$SSH_KEY_NAME.pub" + +# Check if SSH key exists +if [ ! -f "$SSH_KEY_PATH" ] && [ ! -f "$SSH_KEY_PUB_PATH" ]; then + echo "Error: SSH key '$SSH_KEY_NAME' not found in $HOME_DIR/.ssh/" + echo "Please generate your SSH key first:" + echo " ssh-keygen -t rsa -b 4096 -f $HOME_DIR/.ssh/$SSH_KEY_NAME" + exit 1 +fi + +# Check if public key exists specifically (required for reading) +if [ ! -f "$SSH_KEY_PUB_PATH" ]; then + echo "Error: SSH public key '$SSH_KEY_NAME.pub' not found in $HOME_DIR/.ssh/" + exit 1 +fi + +# Get the public key content (remove any trailing whitespace) +PUBLIC_KEY=$(cat "$SSH_KEY_PUB_PATH" | tr -d '\n') + +# Validate that we got a valid SSH key +if [[ ! "$PUBLIC_KEY" =~ ^ssh-[a-z]+[[:space:]]+[A-Za-z0-9+/]*[=]{0,3} ]]; then + echo "Error: Invalid SSH public key format detected" + exit 1 +fi + +echo "Found SSH public key:" +echo "$PUBLIC_KEY" +echo "" + +# Find all main.tf files and update them +MAIN_TF_FILES=$(find . -name "main.tf" -type f) + +if [ -z "$MAIN_TF_FILES" ]; then + echo "No main.tf files found!" + exit 1 +fi + +echo "Updating SSH key in the following files:" +echo "$MAIN_TF_FILES" +echo "" + +# Replace the ssh_key line in all main.tf files using # as delimiter +echo "Replacing SSH key in all main.tf files..." +for file in $MAIN_TF_FILES; do + sed -i "s#ssh_key = \".*\"#ssh_key = \"$PUBLIC_KEY\"#g" "$file" +done + +# Verify the replacement worked +echo "" +echo "Verification:" +for file in $MAIN_TF_FILES; do + echo "File: $file" + grep "ssh_key =" "$file" | head -1 +done + +echo "" +echo "SSH key has been successfully updated in all main.tf files!" +echo "Backup files are saved with timestamp suffixes."