Terraform and Cloud-init for VM deployment in VMware

Terraform seems to be the right choice when you speak about infrastructure as a code and many companies want to gain the advantage of the powerfull automation it can provide.

Lately I was playing with Terraform and cloud-init to deploy Ubuntu and RHEL VMs in a vSphere environment. In this article I will show you how to use Terraform and Cloud-init to deploy a VM and customize its network settings. You can do much more with it, like install packages or configure users, these being only few example. For a full list of the available features, refer to cloud-init documentation.

You will need a machine where you have installed Terraform. I won’t cover the installation steps here. For the purpose of this article, I am asuming you already have installed Terraform in a machine. For installation steps refer to this oficial documentation.

Step 1. Prepare a VM template.

I will show you the steps to prepare a template with cloud-init for both Ubuntu and RHEL VMs along with cloud-init installation and configuration.

I used Ubuntu version 22.04 to create a template for Ubuntu server. I won’t cover here the steps to install Ubuntu server in a VM but I will cover the steps required to prepare the OS for template along with installation and configuration of cloud-init.

Once you have created and installed Ubuntu server in a VM, you need to run some command to make it template ready.

1. Set hostname to localhost

hostnamectl set-hostname localhost

2. Check if cloud-init is installed

apt list ---installed | grep cloud-init
ubuntuuser@cloud-test:~$ apt list --installed | grep cloud-init

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

cloud-init/jammy-updates,now 24.3.1-0ubuntu0~22.04.1 all [installed]

2.1 Install cloud-init if is not already installed

sudo apt-get install cloud-init -y

3. Configure cloud-init to accept VMware source data

Run below command, select VMware and press “OK” to save the configuration.

sudo dpkg-reconfigure cloud init

4. Clean cloud-init to make sure it will run on next boot

sudo cloud-init clean

5. Delete netplan files

ls /etc/netplan/

Delete all files under netplan directory.

rm /etc/netplan/FILENAME

6. Delete shell history

history -c

7. Shutdown VM

shutdown -h now

These are very basic steps. Depending on your requirements you might need aditional steps and better hardening for your template image.

For RHEL, I installed version 9.4 in a VM and did follow the steps below before I converted to template.

1. Set hostname to localhost

hostnamectl set-hostname localhost

2. Install cloud init

sudo yum install cloud-init -y 

3. Update or delete default cloud.cfg file

After installing cloud-init, it will create a default cloud.cfg file locate in /etc/cloud/ directory. This will have some default configuration, like disable root account and disable password login. You can change “disable_root” to “false” and “ssh_pwdauth” to “true” or delete the file.

disable_root: false
ssh_pwauth: true

For my example, I just deleted the file.

rm /etc/cloud/cloud.cfg

4. Delete shell history

history -c

5. Shutdown VM

shutdown -h now

These are very basic steps. Depending on your requirements you might need aditional steps and better hardening for your template image.

Step 2. Convert VM to template

In vCenter ( or ESXi webclient ) right click on VM > Template and click on Convert to template.

This will convert your VM into a template which we will see later to deploy a VM.

Step 3. Prepare cloud-init yaml input file

You can use cloud-init to configure networking for a VM, create users, install packages and many more. For more details you can refer to cloud-init documentation.

In this example I will use cloud-init to only configure network details on the VM.( Check line 7 if you use RHEL )

The above code will be used during the boot to configure the network for the VM. Copy the code, update it according to your needs and save it into a file called “cloud-init.yaml”. Put this file into a separate folder where we will also create a file with terraform code.

Step 4. Prepare terraform code

Copy the code above and place it into a file called “deployvm.tf” inside same folder with “cloud-init.yaml” file.

In the terraform file, check and replace the values to match your environment. I added comment on all lines which you should pay attention to.

Once you have both files ready, run “terraform init” to install all the requirements for the code to run. You should have a similar output to the one below.

Once everything is initialized, you can run “terraform plan” to see what will be changed in your infrastructure. This command will just show you the plan which will help you understand what changes will be done in your infrastructure. At this stage, nothing is going to be changed in the infrastructure so plan can be run safely without worrying of unwanted changes.

Best practice is to check the plan properly to understand each update which will be done in your infrastructure. You will see a summary in the end of the plan, where you can see how many objects will be create, changed or destroyed. ( I didn’t include the plan details in the code below, yours will look different )

Once you checked the plan, you can run “terraform apply”. This command will ask you if you want to apply the changes or not. ( I didn’t include plan details in the code below, yours will look different )

Now this is going to create, change or destroy objects in your infrastructure. In our case, we have only 1 object to add as you can see in the plan.

Lines 61 and 62 from terraform code are the important ones for cloud-init implementation. Terraform will encode the file using base64 format and add it as a parameter in VM configuration file. You can look for it after VM is deployed. VM needs to be powered off to see the guestinfo.metadata, if the VM is powered on, it will show only “TRUE”.

Troubleshooting

If you encounter problems with cloud-init and you don’t see the configuration applied, you might need to do some troubleshooting.

First, you can check cloud-init status by running the command “cloud-init status”

Status “done” will mean that cloud init did run at boot and it completed the configuration. If you see status “not run” it means there was an issue and cloud-init didnt run.

Logs for cloud init are located in /var/log/. You will find 2 log files, cloud-init.log and cloud-init-output.log which you can analyze.

Leave a Reply

Your email address will not be published. Required fields are marked *

I’m Stefan

I work in IT industry for almost 9 years already. I have been pasionated about computers since forever, therefore I decided to open a blog where I will write about this hobby.