Another great tool to automate configurations in vSphere is Red Hat Ansible Automation Platform. Red Hat delivers two versions, a Community version and a Enterprise Version. The innovation happens in the community and the best features are perfected and added to the Enterprise version. This article uses the community version, however I would recommend your enterprise to look at the other version, as there is support available which can be really useful.
Ansible being a code language developed by a Linux developer I would say the tool works best in Unix based OS. So I am skipping showing how it is installed on Windows.
Installation on macOS
Install Homebrew (if not installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Install Python & Ansible
brew install python pip3 install ansible
Verify Installation
ansible --version
Creating a VM on vSphere with Ansible
To create a VM on vSphere, we’ll use the community.vmware
collection, which provides modules for interacting with VMware vSphere.
Install community.vmware
Collection
ansible-galaxy collection install community.vmware
Create Inventory File (inventory.yml
)
all:
hosts:
localhost:
ansible_connection: local
Create Playbook (create_vm.yml
)
A simple playbook:
- name: Create VM on vSphere
hosts: localhost
gather_facts: no
tasks:
- name: Create a virtual machine
community.vmware.vmware_guest:
hostname: "vcenter.example.com"
username: "administrator@vsphere.local"
password: "YourPassword"
validate_certs: no
datacenter: "DatacenterName"
cluster: "ClusterName"
name: "NewVM"
folder: "/DatacenterName/vm"
state: powered_on
guest_id: "otherGuest64" # Example for a Linux VM
disk:
- size_gb: 20
type: thin
datastore: "DatastoreName"
hardware:
memory_mb: 4096
num_cpus: 2
networks:
- name: "VM Network"
type: dhcp
- Run the Playbook
ansible-playbook -i inventory.yml create_vm.yml
Now we have deployed a VM that is running.
Let us look at adding some useful parameters to the code so we can deploy a more final VM.
Deploy a production VM
Instead of using the community version of the OS and Ansible, which may work well for a development scenario but probably not so well for production, we will be using the Enterprise Editions from Red Hat.
This Ansible Playbook is designed to automate the deployment of a virtual machine (VM) running Red Hat Enterprise Linux 9, utilizing a VMware template. The VM has 4 CPUs, 16GB of RAM, and a 250GB hard drive. Networking is set up to use DHCP from VLAN 200, and an SSH key is added for secure access. Additionally, PostgreSQL is installed and configured on the VM.
Key Features:
- Modular Structure: The playbook is structured to allow parts to be reused for other deployments.
- Improved Error Handling: The playbook is configured to terminate if any critical steps fail.
- PostgreSQL Installation: PostgreSQL is installed on the RHEL9 after its successful creation.
- Service Validation: Ensures that PostgreSQL is running.
- Port Check Validation: Confirms that PostgreSQL is listening on port 5432
- PostgreSQL Configuration: Modifies PostgreSQL.conf to allow connections from any address and sets standard parameters.
- Detailed Report Generation: A summary report is generated and saved at /tmp/rhel9_vm_deployment_report.txt.
- Logging: Outputs related to the PostgreSQL installation are logged to /tmp/postgres_install.log for troubleshooting.
- Cleanup Routine: After successful installation, the playbook removes the file /tmp/postgres_install.log. (if the installation succeeds this file is not needed)
- Service Restart: Restarts the PostgreSQL service to apply the new configuration.
Technical Details:
- Utilizes Red Hat Ansible Collections, including redhat_cop.vmware, ansible.posix, and community.general.
- Ensures compatibility with enterprise-grade Ansible modules.
- Employs dnf and subscription-manager modules for patching and registration.
- Utilizes the systemd module for managing the PostgreSQL service.
---
- name: Create RHEL 9 VM on vSphere
hosts: localhost
gather_facts: no
collections:
- redhat_cop.vmware
- ansible.posix
- community.general
tasks:
- name: Check if VMware modules are installed
ansible.builtin.assert:
that:
- "'redhat_cop.vmware.vmware_guest' in ansible_collections"
fail_msg: "VMware modules not installed. Install using: ansible-galaxy collection install redhat_cop.vmware"
- name: Create a virtual machine from Template
redhat_cop.vmware.vmware_guest:
hostname: "vcenter.example.com"
username: "administrator@vsphere.local"
password: "YourPassword"
validate_certs: no
datacenter: "DatacenterName"
cluster: "ClusterName"
name: "RHEL9VM"
folder: "/DatacenterName/vm"
state: powered_on
template: "RHEL9_VMware"
networks:
- name: "VM Network"
vlan: 200
type: dhcp
customization:
hostname: "rhel9-vm"
domain: "example.com"
timezone: "Etc/UTC"
- name: Wait for VM to be ready for SSH
ansible.posix.wait_for:
host: "rhel9-vm"
port: 22
delay: 60
timeout: 300
state: started
- name: Add SSH key for firstname.lastname
ansible.builtin.copy:
src: /path/to/firstname.lastname.pub
dest: /home/username/.ssh/authorized_keys
owner: username
mode: '0600'
become: yes
remote_user: root
- name: Register the VM with Red Hat Subscription Management
ansible.builtin.shell: |
subscription-manager register --username=your_rh_username --password=your_rh_password
subscription-manager attach --auto
subscription-manager repos --enable=rhel-9-for-x86_64-baseos-rpms --enable=rhel-9-for-x86_64-appstream-rpms
become: yes
remote_user: root
- name: Update the VM to the latest version
ansible.builtin.dnf:
name: '*'
state: latest
become: yes
remote_user: root
- name: Install PostgreSQL
ansible.builtin.dnf:
name:
- postgresql-server
- postgresql-contrib
state: present
become: yes
remote_user: root
- name: Initialize PostgreSQL Database
ansible.builtin.command: postgresql-setup --initdb
become: yes
remote_user: root
- name: Start and Enable PostgreSQL Service
ansible.builtin.systemd:
name: postgresql
enabled: yes
state: started
become: yes
remote_user: root
- name: Configure PostgreSQL
ansible.builtin.copy:
dest: /var/lib/pgsql/data/postgresql.conf
content: |
# PostgreSQL Configuration
listen_addresses = '*'
port = 5432
max_connections = 100
owner: postgres
mode: '0644'
become: yes
remote_user: root
- name: Restart PostgreSQL Service
ansible.builtin.systemd:
name: postgresql
state: restarted
become: yes
remote_user: root
- name: Confirm PostgreSQL is listening on port 5432
ansible.builtin.shell: |
ss -ltn | grep ':5432'
register: postgres_port_check
become: yes
remote_user: root
failed_when: postgres_port_check.stdout == ""
ignore_errors: no
- name: Generate Summary Report
ansible.builtin.copy:
dest: /tmp/rhel9_vm_deployment_report.txt
content: |
RHEL 9 VM Deployment Report
===========================
VM Name: RHEL9VM
Hostname: rhel9-vm.example.com
PostgreSQL Status: Active
PostgreSQL Listening on Port: 5432
Deployment Status: Successful
Generated On: {{ ansible_date_time.date }} {{ ansible_date_time.time }}
become: yes
remote_user: root
- name: Cleanup Temporary Files
ansible.builtin.file:
path: /tmp/postgres_install.log
state: absent
become: yes
remote_user: root
- name: Print success message
ansible.builtin.debug:
msg: "RHEL 9 VM successfully created from template 'RHEL9_VMware', patched, registered with Red Hat, PostgreSQL installed and validated. Report saved to /tmp/rhel9_vm_deployment_report.txt"
handlers:
- name: Restart VM if needed
redhat_cop.vmware.vmware_guest_powerstate:
hostname: "vcenter.example.com"
username: "administrator@vsphere.local"
password: "YourPassword"
validate_certs: no
datacenter: "DatacenterName"
name: "RHEL9VM"
state: powered_on
execution_environment:
name: rhel9-vm-deployment
What now?
The code above will let you deploy a VM. But we can make some improvements to the code. Lets dive into that.
Secure Password Handling
Hardcoding passwords into files that are accessible by anyone is a bad practice. Many hacks have happened due to passwords being stored on Git Hub. Ansible has a feature called Ansible Vault that would solve this for us.
First, create a separate file where we store the passwords for the services we are using in this playbook. We will call the file vars/main.yaml
# vars/main.yml
vcenter_hostname: "vcenter.example.com"
vcenter_username: "administrator@vsphere.local"
vcenter_password: "{{ vault_vcenter_password }}"
datacenter_name: "DatacenterName"
cluster_name: "ClusterName"
vm_name: "RHEL9VM"
vm_folder: "/DatacenterName/vm"
vm_template: "RHEL9_VMware"
network_name: "VM Network"
network_vlan: 200
vm_custom_hostname: "rhel9-vm"
vm_domain: "example.com"
vm_timezone: "Etc/UTC"
Then we will encrypt the file using Ansible Vault
ansible-vault encrypt vars/main.yml
When running the playbook, you’ll need to provide the vault password:
ansible-playbook playbook.yml --ask-vault-pass
Variables
A variable represents a named entity that holds data, which can be referenced and altered across playbooks and roles. They enable dynamic adjustments to values and settings in your automation scripts without needing to modify the code structure. Consequently, variables enhance flexibility and reusability, allowing for customization of tasks based on various environments and configurations inputs.
Idempotent
Ensuring idempotency in Ansible means writing playbooks and tasks such that running them multiple times will have the same effect as running them once. Idempotency is a key principle in configuration management, ensuring that systems reach and maintain a desired state without unintended side effects.
Roles
A role is a method for organizing and encapsulating related tasks, variables, files, templates, and handlers within a standardized file structure. Roles aim to simplify complex playbooks and enhance reusability by offering a framework for managing configurations and automations in a clear, modular manner.
Updated
We should consider the importance of not writing passwords in clear text but using Vault, using variables and roles to ensure that it is reusable, and ensuring that the code is idempotent.
Error Handling: Added `register`, `failed_when`, and `ignore_errors` to capture task results and handle errors gracefully.
Documentation: Added comments to describe the purpose of tasks and roles.
Idempotency Considerations:
VM Creation: The `vmware_guest` module is used to ensure the VM is in the desired state, it is checking if the VM already exists.
Package Installation: The `dnf` module with `state: present` ensures that packages are installed only if they are not already present.
Database Initialization: The `command` module utilizes the `creates` parameter to guarantee that the database is initialized only if it is not already set up.
Service Management: The `systemd` module ensures services are started and enabled.
Configuration Deployment: The `template` module deploys configuration files only if there is a change, triggering handlers when necessary.
# Directory Structure:
# roles/
# ├── rhel_vm/
# │ ├── tasks/
# │ │ ├── main.yml
# │ ├── vars/
# │ │ ├── main.yml
# │ ├── templates/
# │ ├── files/
# │ ├── handlers/
# │ │ ├── main.yml
# ├── postgresql/
# │ ├── tasks/
# │ │ ├── main.yml
# │ ├── vars/
# │ │ ├── main.yml
# │ ├── templates/
# │ │ ├── postgresql.conf.j2
# │ ├── handlers/
# │ │ ├── main.yml
# Playbook: site.yml
---
- name: Deploy RHEL 9 VM and Configure PostgreSQL
hosts: localhost
gather_facts: no
roles:
- role: rhel_vm
- role: postgresql
# roles/rhel_vm/tasks/main.yml
---
- name: Create a virtual machine from Template
redhat_cop.vmware.vmware_guest:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vault_vcenter_password }}"
validate_certs: no
datacenter: "{{ datacenter }}"
cluster: "{{ cluster }}"
name: "{{ vm_name }}"
folder: "{{ vm_folder }}"
state: powered_on
template: "{{ vm_template }}"
networks:
- name: "{{ vm_network }}"
vlan: "{{ vm_vlan }}"
type: dhcp
customization:
hostname: "{{ vm_custom_hostname }}"
domain: "{{ vm_domain }}"
timezone: "{{ vm_timezone }}"
vars:
vcenter_hostname: "vcenter.example.com"
vcenter_username: "administrator@vsphere.local"
datacenter: "DatacenterName"
cluster: "ClusterName"
vm_name: "RHEL9VM"
vm_folder: "/DatacenterName/vm"
vm_template: "RHEL9_VMware"
vm_network: "VM Network"
vm_vlan: 200
vm_custom_hostname: "rhel9-vm"
vm_domain: "example.com"
vm_timezone: "Etc/UTC"
register: vm_creation_result
failed_when: vm_creation_result.failed
ignore_errors: no
# roles/postgresql/tasks/main.yml
---
- name: Ensure PostgreSQL is installed
ansible.builtin.dnf:
name:
- postgresql-server
- postgresql-contrib
state: present
become: yes
remote_user: root
register: postgresql_install_result
failed_when: postgresql_install_result.failed
ignore_errors: no
- name: Initialize PostgreSQL Database if not already initialized
ansible.builtin.command: postgresql-setup --initdb
become: yes
remote_user: root
args:
creates: /var/lib/pgsql/data/PG_VERSION
register: postgresql_init_result
failed_when: postgresql_init_result.failed
ignore_errors: no
- name: Ensure PostgreSQL service is enabled and running
ansible.builtin.systemd:
name: postgresql
enabled: yes
state: started
become: yes
remote_user: root
register: postgresql_service_result
failed_when: postgresql_service_result.failed
ignore_errors: no
- name: Deploy PostgreSQL configuration
ansible.builtin.template:
src: postgresql.conf.j2
dest: /var/lib/pgsql/data/postgresql.conf
owner: postgres
mode: '0644'
become: yes
remote_user: root
notify: Restart PostgreSQL
register: postgresql_config_result
failed_when: postgresql_config_result.failed
ignore_errors: no
# roles/postgresql/handlers/main.yml
---
- name: Restart PostgreSQL
ansible.builtin.systemd:
name: postgresql
state: restarted
become: yes
remote_user: root
# Use templates, handlers, and other tasks as needed
# Encrypt sensitive data using ansible-vault
# ansible-vault encrypt group_vars/all/vault.yml
Conclusion
In the latest example, we have seen how to maintain the state of one VM using Ansible. We can extend the code to maintain the state of several hundred or thousands of VMs. IaC tools offer a clear advantage over manual processes in this regard.