Automating Azure VM Deployment with Terraform and Ansible in Azure DevOps Pipelines
Nicholas Osi
Posted on November 18, 2024
Deploying virtual machines (VMs) in Azure using a combination of Terraform for infrastructure provisioning and Ansible for configuration management is a powerful and efficient approach. By integrating these tools within an Azure DevOps pipeline, you can achieve automated, repeatable, and scalable deployments. Below is a comprehensive, step-by-step guide to setting up this automation.
- Prerequisites
Before you begin, ensure you have the following:
- Azure Account: Access to Azure with permissions to create resources.
- Azure DevOps Account: Access to Azure DevOps Services with permissions to create projects and pipelines.
- Git Repository: A repository in Azure Repos or any other Git service where your Terraform and Ansible code will reside.
- Terraform Installed Locally: For initial testing and development.
- Ansible Installed Locally: For initial testing and development.
- Basic Knowledge: Familiarity with Terraform, Ansible, and Azure DevOps pipelines.
- Repository Setup
Organize your codebase by separating Terraform and Ansible configurations, typically in different directories within the same repository.
Terraform Configuration
Create a Directory: Create a
terraform
directory in your repository.Define
main.tf
: This file will contain the Terraform configuration for deploying Azure VMs. Here’s a basic example:
provider “azurerm” {
features = {}
}
resource “azurerm_resource_group” “rg” {
name = “myResourceGroup”
location = “East US”
}
resource “azurerm_virtual_network” “vnet” {
name = “myVNet”
address_space = [“10.0.0.0/16”]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
resource “azurerm_subnet” “subnet” {
name = “mySubnet”
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [“10.0.1.0/24”]
}
resource “azurerm_network_interface” “nic” {
name = “myNIC”
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = “internal”
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = “Dynamic”
}
}
resource “azurerm_virtual_machine” “vm” {
name = “myVM”
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
network_interface_ids = [azurerm_network_interface.nic.id]
vm_size = “Standard_DS1_v2”
storage_image_reference {
publisher = “Canonical”
offer = “UbuntuServer”
sku = “18.04-LTS”
version = “latest”
}
storage_os_disk {
name = “myOSDisk”
caching = “ReadWrite”
create_option = “FromImage”
managed_disk_type = “Standard_LRS”
}
os_profile {
computer_name = “myVM”
admin_username = “azureuser”
admin_password = “P@ssw0rd1234!” # Consider using SSH keys for better security
}
os_profile_linux_config {
disable_password_authentication = false
}
}
output “public_ip” {
value = azurerm_network_interface.nic.private_ip_address
}
3. **Variables and Outputs**: Customize variables and outputs as needed. For security, sensitive variables should be managed via Azure DevOps variables or Azure Key Vault.
### **b. Ansible Playbook**
1. **Create a Directory**: Create an `ansible` directory in your repository.
2. **Define `inventory.ini`**: This file will list the VMs to configure. Since Terraform outputs the VM IPs, you can use Terraform to generate the inventory or use dynamic inventory scripts.
ini
[azure_vms]
myVM ansible_host= ansible_user=azureuser ansible_ssh_pass=P@ssw0rd1234!
*Note: For enhanced security, consider using SSH keys instead of passwords.*
3. **Define `playbook.yml`**: This playbook will perform configuration tasks on the deployed VMs. Here’s an example that installs Nginx:
yaml
— name: Configure Azure VMs
hosts: azure_vms
become: yes
tasks:
— name: Update apt cache
apt:
update_cache: yes
name: Install Nginx
apt:
name: nginx
state: presentname: Ensure Nginx is running
service:
name: nginx
state: started
enabled: true
4. **Ansible Configuration**: Optionally, add an `ansible.cfg` file to define inventory paths, roles paths, etc.
— -
## **3. Azure DevOps Pipeline Configuration**
Set up the Azure DevOps pipeline to execute Terraform and Ansible steps sequentially.
### **a. Service Connections**
1. **Azure Service Connection**:
— Navigate to your Azure DevOps project.
— Go to **Project Settings** > **Service connections**.
— Click **New service connection** > **Azure Resource Manager**.
— Choose the appropriate authentication method (e.g., via Azure CLI, Service Principal).
— Name the connection (e.g., `AzureServiceConnection`).
2. **SSH Service Connection for Ansible** (if using SSH keys):
— If you’re using SSH keys for Ansible, store the private key as a secret variable or use Azure DevOps’ secure files.
### **b. Pipeline YAML Definition**
Create a YAML pipeline that defines the stages for Terraform and Ansible.
1. **Create a New Pipeline**:
— Navigate to **Pipelines** > **New Pipeline**.
— Select your repository.
— Choose **Starter pipeline** or **Existing YAML file**.
2. **Define the Pipeline**: Below is an example YAML pipeline integrating Terraform and Ansible.
yaml
trigger:
— main
variables:
Terraform variables
TF_VAR_location: “East US”
Add other Terraform variables as needed
stages:
— stage: Terraform
displayName: “Terraform: Infrastructure Provisioning”
jobs:
— job: Terraform
displayName: “Terraform Apply”
pool:
vmImage: ‘ubuntu-latest’
steps:
— checkout: self
task: TerraformInstaller@0
displayName: “Install Terraform”
inputs:
terraformVersion: ‘1.3.0’ # Specify desired Terraform versiontask: AzureCLI@2
displayName: “Azure Login”
inputs:
azureSubscription: ‘AzureServiceConnection’
scriptType: ‘bash’
scriptLocation: ‘inlineScript’
inlineScript: |
echo “Logging into Azure…”script: |
cd terraform
terraform init
displayName: “Terraform Init”script: |
cd terraform
terraform plan -out=tfplan
displayName: “Terraform Plan”script: |
cd terraform
terraform apply -auto-approve tfplan
displayName: “Terraform Apply”task: PublishPipelineArtifact@1
displayName: “Publish Terraform Outputs”
inputs:
targetPath: ‘terraform’
artifact: ‘terraform’stage: Ansible
displayName: “Ansible: Configuration Management”
dependsOn: Terraform
jobs:
— job: Ansible
displayName: “Run Ansible Playbook”
pool:
vmImage: ‘ubuntu-latest’
steps:
— download: current
artifact: terraform
displayName: “Download Terraform Artifact”
task: UsePythonVersion@0
inputs:
versionSpec: ‘3.x’
addToPath: true
displayName: “Use Python”script: |
python -m pip install — upgrade pip
pip install ansible
displayName: “Install Ansible”script: |
cd ansible
ansible-playbook -i inventory.ini playbook.yml
displayName: “Run Ansible Playbook”
*Note*: This is a simplified example. Depending on your specific requirements, you may need to adjust the pipeline, especially for handling variables, SSH keys, and dynamic inventories.
— -
## **4. Pipeline Steps Breakdown**
Let’s delve deeper into each step of the pipeline to understand their purpose and configuration.
### **Stage 1: Terraform**
1. **Checkout Code**:
— Retrieves the latest code from the repository.
2. **Install Terraform**:
— Uses the `TerraformInstaller` task to install the specified Terraform version.
3. **Azure Login**:
— Authenticates to Azure using the service connection. This is necessary for Terraform to interact with Azure resources.
4. **Terraform Init**:
— Initializes the Terraform working directory, downloads necessary providers, and sets up the backend.
5. **Terraform Plan**:
— Generates and displays an execution plan, showing what actions Terraform will take.
6. **Terraform Apply**:
— Applies the planned changes to Azure, creating or updating resources as defined.
7. **Publish Terraform Outputs**:
— Publishes Terraform outputs as pipeline artifacts. This can be used in subsequent stages or jobs.
### **Stage 2: Ansible**
1. **Download Terraform Artifact**:
— Retrieves the outputs from the Terraform stage, which may include VM IP addresses needed for Ansible inventory.
2. **Use Python**:
— Ensures that Python is available, as Ansible is a Python-based tool.
3. **Install Ansible**:
— Installs Ansible via `pip`.
4. **Run Ansible Playbook**:
— Executes the Ansible playbook to configure the deployed VMs.
— -
## **5. Testing and Validation**
After setting up the pipeline, it’s crucial to test and validate each component to ensure successful deployments.
1. **Validate Terraform Configuration**:
— Run `terraform validate` locally to check for syntax errors.
— Use `terraform plan` to ensure that the infrastructure changes are as expected.
2. **Validate Ansible Playbook**:
— Run the playbook locally against test VMs to ensure it performs the desired configurations without errors.
3. **Run the Pipeline**:
— Commit and push changes to trigger the pipeline.
— Monitor the pipeline execution in Azure DevOps for any failures or issues.
4. **Verify Deployment in Azure**:
— Check the Azure Portal to confirm that the resources (e.g., VMs) have been created as defined.
— Ensure that Ansible has successfully configured the VMs (e.g., Nginx is installed and running).
5. **Implement Logging and Monitoring**:
— Integrate logging mechanisms to capture Terraform and Ansible logs.
— Set up alerts for pipeline failures or deployment issues.
— -
## **6. Best Practices and Tips**
- **State Management**:
— Use remote state backends for Terraform, such as Azure Storage Account with state locking to prevent conflicts.
- **Secrets Management**:
— Store sensitive information (e.g., passwords, SSH keys) securely using Azure DevOps Pipeline variables, Azure Key Vault, or other secret management solutions.
- **Modular Code**:
— Structure Terraform and Ansible code into modules and roles for reusability and better organization.
- **Idempotency**:
— Ensure that Terraform and Ansible configurations are idempotent, meaning they can run multiple times without causing unintended changes.
- **Error Handling**:
— Implement error handling and notifications within the pipeline to promptly address failures.
- **Version Control**:
— Use version control best practices, such as branching strategies and pull requests, to manage changes to your infrastructure and configuration code.
- **Dynamic Inventory for Ansible**:
— Consider using Terraform outputs to dynamically generate the Ansible inventory, enhancing flexibility and scalability.
- **Use SSH Keys**:
— Prefer SSH key-based authentication over passwords for enhanced security in Ansible.
— -
## **7. Conclusion**
By integrating Terraform and Ansible within an Azure DevOps pipeline, you establish a robust automation workflow for deploying and configuring Azure VMs. Terraform handles the infrastructure provisioning, ensuring consistent and repeatable deployments, while Ansible manages the configuration of those resources, applying the desired state efficiently.
This setup not only accelerates deployment times but also enhances reliability and scalability, allowing your infrastructure to grow seamlessly with your application’s needs. Adhering to best practices in code organization, security, and pipeline management further ensures that your deployments remain maintainable and secure over time.
Feel free to customize and expand upon this foundation to suit your specific project requirements and organizational standards.
Posted on November 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024