Provisioning a Linux VM with Ansible in VMware
Hi there , I am a Systems Engineer and DevOps enthusiast. And a tired person for creating and provisioning virtual machines. I want to get rid of all the challenging work, the repeating steps and the “customer” mumbling.
Because of the reasons above I mentioned, I want to let things go and watch them creating itself. So I thought it would be a great idea to use automation in my work life. In this way I will just need to do the automation planning, design and implementation. Yep, easy right ? Not everything is easy when you are *self-motivated*. But that’s not a problem for us at the moment, we got plenty of time because of the “COVID-19” pandemic.
So this will be fun, we just need to move one step at a time. In this article we are starting from the beginning. We will create a Linux virtual machine with “Ansible” in VMware infrastructure. Let’s begin.
Prerequisites
- You must have a working Linux virtual machine “template” inside vCenter. (I am using a CentOS 7 template)
- You must have a working ansible configuration
- You must have the proper ansible inventory
- A DNS record of vCenter in a DNS server or your local machine ( This will be needed for ansible to work. )
- Python ≥ 2.6
- PyVmomi (pip install pyvmomi)
I will be using my own lab environment. You should change every configuration according to your environment.
IMPORTANT NOTE: Be careful for indentation, medium automatically removed all the indentations of the yaml files.
Beginning Of A New Age
I will be using ansible-galaxy init “role” for creating a role template for me. I am using roles to “do this one job”. Well I also have a developer mode inside me, so I believe, even in the tiniest bit, what we create must do only it’s job and nothing more. For example, vm-create role will create a virtual machine, vm-remove role will remove a virtual machine and so on. For every action I have a role configuration and in every role configuration I can have different variables, templates etc. You might say that’s an overkill, but I believe it’s easy to write,remember and understand this way.
With the usage of the roles, I have only one yaml file to run. The “automate.yml”, this playbook will call out for our roles when given conditions are met.
So I will have something like this:
.
├── automate.yml
├── LICENSE
├── README.md
└── roles
And inside roles I have plenty but just for now:
roles/
├── vm-create
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
To create a structure like this, you just need to use
ansible-galaxy init vm-create
Of course the roles/ directory will not be present. You need to create some of the above yourself.
So with an understanding of the folder structure and design we can move forward.
Writing The Role Yaml
Because we will be using VMware infrastructure, there is a module in ansible for managing virtual machines via vCenter. I will be using https://docs.ansible.com/ansible/latest/modules/vmware_guest_module.html vmware_guest_module.
Just by looking the documentation, we have almost everything to create a virtual machine straightforward.
So my /roles/vm-create/tasks/main.yml will be something like this (with both dhcp and static IP configuration ):
# tasks file for vm-create
- name: Clone VM from template with dhcp
vmware_guest:
validate_certs: “{{ validate_certs | default(‘False’) }}”
hostname: “{{ vcenter_hostname }}”
username: “{{ vcenter_username }}”
password: “{{ vcenter_password }}”
datacenter: “{{ vm_datacenter }}”
name: “{{ vm_name }}”
folder: “{{ vm_folder }}”
template: “{{ vm_template }}”
state: poweredon
annotation: “{{ vm_notes | default(‘Provisioned by ansible’) }}”
cluster: “{{ vm_cluster }}”
hardware:
num_cpus: “{{ cpu }}”
memory_mb: “{{ mem_mb }}”
hotadd_cpu: “{{ hot_add_cpu | default(‘True’) }}”
hotremove_cpu: “{{ hot_remove_cpu | default(‘True’) }}”
hotadd_memory: “{{ hot_add_memory | default(‘True’) }}”
disk:
— size_gb: “{{ disk_size | default(‘16’) }}”
type: “{{ vm_disk_type | default(‘thin’) }}”
datastore: “{{ vm_datastore }}”
networks:
— name: “{{ vm_port_group | default(‘VM Network’) }}”
wait_for_ip_address: yes
register: dynamic_vm
when: network_type == ‘dhcp’- name: Clone VM from template with static IP
vmware_guest:
validate_certs: “{{ validate_certs | default(‘False’) }}”
hostname: “{{ vcenter_hostname }}”
username: “{{ vcenter_username }}”
password: “{{ vcenter_password }}”
datacenter: “{{ vm_datacenter }}”
name: “{{ vm_name }}”
folder: “{{ vm_folder }}”
template: “{{ vm_template }}”
state: poweredon
annotation: “{{ vm_notes | default(‘Provisioned by ansible’) }}”
cluster: “{{ vm_cluster }}”
hardware:
num_cpus: “{{ cpu }}”
memory_mb: “{{ mem_mb }}”
hotadd_cpu: “{{ hot_add_cpu | default(‘True’) }}”
hotremove_cpu: “{{ hot_remove_cpu | default(‘True’) }}”
hotadd_memory: “{{ hot_add_memory | default(‘True’) }}”
disk:
— size_gb: “{{ disk_size | default(‘16’) }}”
type: “{{ vm_disk_type | default(‘thin’) }}”
datastore: “{{ vm_datastore }}”
networks:
— name: “{{ vm_port_group }}”
type: static
ip: “{{ vm_ip }}”
netmask: “{{ netmask }}”
dns_servers:
— “{{ dns_server1 }}”
— “{{ dns_server2 }}”
gateway: “{{ network_gateway }}”
wait_for_ip_address: yes
register: static_vm
when: network_type == ‘static’
In this configuration I used extra variables to use with user forms. I will explain this in a later article.
NOTE: I set default values for some of the variables.
This .yml file could be looking so terrifying but all it’s doing is passing right arguments to vCenter API.
You could be using straightforward variable defining something like this:
# tasks file for vm-create
- name: Clone VM from template with dhcp
vmware_guest:
validate_certs: False
hostname: VeryFancyHostname
username: VeryFancyUser
password: WithItsVeryFancyPassword
But with this configuration you will be exposing your or administrator’s (even worse ) credentials and if you are using a SCM like Git, you would be sharing your credentials with everybody whom has access to this project. I don’t like that. So I will be using these variables as extra variables. We will see it’s usage later.
Alternatively you can keep your credentials as “secrets” and give a secret file to this yaml.
With the role being complete (you think so ?) now we can move forward to writing our main playbook, automate.yml.
Writing The Main Playbook
The main playbook word may seem too fancy and important. Although it’s very important there is a little inside it.
A manager’s job is to call for the right people when the time is right. Right ?
So our “manager” playbook is going to call the right roles, when the condition we gave to it met.
So automate.yml will be someting like this:
- name: Start VM automation
hosts: all
connection: local
roles:
— { role: vm-create, when: task == ‘Create’ or task == ‘create’ }
Remember when you developing something, you must develop it for the future developments. You must think of future and possible addings. That’s why I added “task” variable to choose between operations like create,delete,rename etc.
With our main playbook being complete, we can move to the next step.
Configuring Ansible Inventory
Ansible will search for your “designated destination host” in it’s default inventory. ( /etc/ansible/hosts)
Either you can manage and change this inventory or you can create a new inventory and say ansible to look for this inventory of yours. I will be using default inventory.
So our /etc/ansible/hosts will look like:
[Vcenter]
totally-accurate-vcenter-fqdn
NOTE: Your ansible machine must be resolving your vcenter FQDN, you can either use a DNS server record to do this, or you can simply add a new record in your host file. (/etc/hosts)
So /etc/hosts will look like:
IP-of-the-vcenter totally-accurate-vcenter-fqdn
You can control your configuration with a simple ping command, if you can resolve the address, it’s done for this part.
ping totally-accurate-vcenter-fqdn
The Moment Of Truth
After configuring ansible inventory and our plays, we can finally do some real work.
If you have every configuration correct (and this isn’t so hard) it’s now down to a single step: Calling our main playbook.
Now, if you remember, I used variables in both of the playbooks.
This is the trickiest part of all, we are going to pass arguments as variables from the command line. In this way, we are not going to expose any of our credentials and we won’t need to change the playbook every time we need something different. We will just pass different arguments. ( There will be a easy part of this )
So our command will be something like this (hold on tight):
ansible-playbook -l Vcenter automate.yml -e “vcenter_hostname=<vcenter_hostname> vcenter_username=<vcenter_username> vcenter_password=<vcenter_password> vm_datacenter=<vm_datacenter> vm_name=<vm_name> vm_folder=<vm_folder> vm_template=<vm_template_name> vm_notes=<vm_notes> vm_cluster=<vm_cluster> hot_add_cpu=<true_or_false> hot_remove_cpu=<true_or_false> hot_add_memory=<true_or_false> cpu=<cpu_in_numbers> mem_mb=<memory_in_mb> disk_size=<disk_size_in_gb> vm_disk_type=<vm_disk_type> vm_datastore=<vm_datastore> vm_port_group=<vm_port_group> network_type=<network_type> task=create”
Don’t be intimidated by the look of the command. We can easily set a default value to each variable. In my lab environment, I am using only:
ansible-playbook -l Vcenter automate.yml -e “vcenter_hostname=<vcenter_hostname> vcenter_username=<vcenter_username> vcenter_password=<vcenter_password> vm_name=<vm_name> vm_template=<vm_template> cpu=<cpu_in_numbers> mem_mb=<memory_in_mb> disk_size=<disk_size_in_gb> network_type=<network_type> task=create”
Don’t forget to pass the -e ( — extra-variables) flag.
Because I set default values for all the remaining variables. For example, in the “networks” section of the roles/vm-create/tasks/main.yml:
networks:
— name: “{{ vm_port_group | default(‘VM Network’) }}”
I set a default variable like this. You can do this too.
If you look at the extra variables, you can see the “task” variable I created earlier in the automate.yml, this variable will help ansible to call the right role.
After all the hard work, blood and some sweat, if you see this output, then it means that it has been successfully completed.
IMPORTANT NOTES
- In ansible-playbook command I am using -l ( — limit) flag to call only my Vcenter tag in the inventory. If you don’t use it, ansible will try to execute on every machine in your inventory.
- Again in ansible-playbook command I am using -e ( — extra-variables) flag to pass arguments from command line.
- If you want to use your own inventory and not the default, just create your inventory in the parent directory. Then call it like ansible-playbook -i <your_inventory>.
- Because you don’t have any more roles, the “task” variable is going to be always create for you. But this variable is a glance for the future, when you’ve added more and more roles into this structure, you will be able to call any role(and it’s playbook) with only one playbook.
- I’ve tried vmware_guest module on various vCenter and ESXi versions, and I’ve come across that this module is working on versions 5.5 U3 and later.
After The Truth
So, after successfully completing ansible provisioning, now you can create a custom spec virtual machine in your own VMware environment. Play around more with ansible and you will discover a lot more to do with it.
As for this article, we’ve come to an end. I enjoyed a lot when writing this, hope you enjoyed reading as much as I am. English is not my native language so, please excuse my misspelling. I plan to write about ansible in VMware more and I’d like to hear about your thoughts.
Have a good day and stay safe ! :)