Ansible Part 3 - Run Ansible from an Azure DevOps Pipeline
Integrate Ansible into your CI/CD workflow
MEDIUM X-POST: This blog is cross-posted on Medium. If you are a Medium member and want to read on Medium please click here LINK
What's included in this tutorial?
In this tutorial, you will write an Azure DevOps pipeline to execute Ansible on target VMs in Azure.
Key topics:
- Create a basic Azure Devops pipeline to execute an Ansible Playbook
- Securely store the VM connection key in an Azure Key Vault
- Create a self hosted pipeline agent in Azure
In this tutorial we will be using a self hosted pipeline agent. This can be used to connect into private networks. The alternative is to use the Microsoft Hosted Agent, but at the time of writing free devops projects have to request access to the free agent, and this takes some time to setup.
Dependencies
- This tutorial requires the following:
- A linux virtual machine (self-hosted-agent-vm) in Azure to act as the self hosted agent.
- A linux virtual machine (target-vm) in Azure to act as the target host.
- Ensure you have access to the private SSH keys for both VMs - this will be stored in a key vault.
- You can follow the steps in this blog to help you get prepared. Preparing for Ansible
- by following step 0.5 you can create the two VMs you require for this tutorial.
- An Azure DevOps organisation and project
- Correct Role to create Service Principals on Microsoft Entra
Tutorial
For this tutorial we are going to:
- Create a Service Principal
- Set the Service Principle up in DevOps with the correct permissions
- Create your self-hosted DevOps Agent
- Create a repo in DevOps
- Create your Ansible Playbook and Inventory
- Store your SSH key in Azure Key Vault
- Create your pipeline
- Run the pipeline and see Ansible work in DevOps!
Step 1 - Create a Service Principal
This Service Principal will be used to:
- Connect to the Azure Subscription
- Pull secrets from the Key Vault
- Authorise the self-hosted agent (Optional)
For simplicity we are using one service principal here, but in production it is more secure to use multiple service principals for each function and ensure you are only assigning the lowest level of privilege needed. It is also possible to use Managed Identities and other authentication solutions but for simplicity we will use service principals.
Create a Service Principal using this Microsoft guide. Steps summarised below:
- Browse to Microsoft Entra
- you can also click through to it from the Azure Portal.
- Click on App Registrations
- Click on New Registrations
- Give application a name
- for this tutorial I am using the name
ansible-azure-devops-pipeline
- for this tutorial I am using the name
- Supported Account types should be from this org only
- No redirect URI necessary.
- Click Register
Now create a client secret
and get the relevant details
- For the created App Registration click on Certificates & Secrets.
- Click create new client secret, give it a name and an expiry date
- SAVE the secret value in a secure area
- Now click on
Overview
in the right hand blade and record several key details (see below).
You should have the below information to hand for the rest of the tutorial:
- Display Name
- Application (Client) ID
- Object ID
- Tenant ID
- Client Secret Value
Step 2 - Set the Service Principle up as a DevOps User, Grant Access to the Default Agent Pool, and set up a Serivce Connection
This will be used to authorise the self hosted agents.
- Connect your
Microsft Entra ID
to the DevOps project.- Use the steps in this guide
- Add your
service principal
as auser
into your organisation.- NOTE this is at the
organisation level
not the project level. - Follow Step 2 only of this guide
- Add the SP as a user to your project
- Add the SP to the Project Administrators group
- NOTE this is at the
- Give your SP authorisation over the
Default Agent Pool
.- Follow this guide
- NOTE: this needs to be at the organisation level not the project level!
- Select the
Default
Agent Pool - Set the SP as an Administrator
- Don’t forget to click
save
- Create a DevOps Service Connection using your existing Service Principle Credentials
- Go to your DevOps Project
- Click on Project Settings
- Click on Service Connections (under Pipelines)
- Click Create Service Connection
- Select Azure Resource Manager
- Select following options
- Type: App Registration (Manual) - Credential: Secret - Environment: Azure Cloud - Scope: Subscription - Subscription ID: <insert-subscription-ID> - Subscription Name: <insert-subscription-name> - Application ID: <insert-sp-client-app-id> - Tenant ID: <insert-tenant-id> - Credential: Service Principal Key - Client Secret: <insert-client-secret> - Service Connection Name: mySvcConnection - Security: tick grant access permission to all pipelines
- Click verify & save
Step 3 - Create a Self Hosted Agent
- Download the agent config
- Follow this Microsoft Guide
- Use the Default Pool
- Linux, x64
-
Copy the downloaded zip file from the first step to your self-hosted agent vm.
1
scp -i <path to ssh key> <path to downloaded zip file> <username>@<public-ip-self-hosted-agent-vm>:~/
- Connect via SSH to your self-hosted-agent-vm from your local machine
ssh <username>@<public-ip-self-hosted-agent-vm>
- Run the commands as per the agent config guide:
- Enter the following key information
- Azure DevOps URL = `https://dev.azure.com/{your-organization}` - Authentication type = sp - Client (App) ID = `<your-service-principal-id>` - Tenant Id = `<your-tenant-id>` - Client Secret = `<your-service-principal-key>` - agent pool = `default` - agent name = `mySelfHostedAgent` # you can enter your own name - work folder = `_work`
1 2 3 4 5
mkdir myagent && cd myagent tar zxvf <path to copied over zip file> ./config.sh # Enter information
- Set up the agent as a service so that it runs and starts on VM restart
1 2 3 4 5
cd ~/myagent sudo ./svc.sh install <username> sudo ./svc.sh start
- (Optional) Secure the network
- Firewall requirements
- Set up inbound rule:
- Name: AllowTagCustomAnyInbound - Priority: 1010 - Port: 443 - Protocol: TCP - Source: AzureDevOps (ServiceTag) - Destination: Any
- Set up two outbound rules (one for IPV4 and one for IPV6)
- Name: AllowAzureDevOpsHTTPSOutbound - Priority: 1020 - Port: 443 - Protocol: TCP - Source: Any - Destination: [IP addresses in Outbound Connections in this section](https://learn.microsoft.com/en-us/azure/devops/organizations/security/allow-list-ip-url?view=azure-devops&tabs=IP-V6#outbound-connections){:target="_blank"}
- Validate the agent is connected
- Click on Azure DevOps
- Project Settings
- Agent Pools
- Agents
- You should see the self hosted agent listed as being online (with a green circle next to it)
Step 4 - Create a new repo in your DevOps Project and Clone to your Machine
- Create a new Repo
- Microsoft Guide
- Go to repos
- Click on drop down repo names at the top
- Click on new repository
- Enter name -
ansible-devops-pipeline
- Click Create
- Clone to your machine
1
2
3
git clone ~/<repo-http-address>
# It should create a folder called ansible-devops-pipeline
- Open the repo in vscode or a editor/IDE of your choice
Step 5 - Create your Ansible Playbook
The below ansible playbook installs Nginx on the target host. The below instructions assume you are using vscode
Create a folder called
playbooks
Create a file called
myPlaybook.yml
-
Add the following content into the playbook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
---
- name: My first playbook
hosts: test_group
become: true
gather_facts: true
tasks:
- name: Update apt
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Start Nginx
ansible.builtin.service:
name: nginx
state: started
- name: Visit the NGINX homepage
ansible.builtin.uri:
url: http://localhost
status_code: 200
return_content: true
register: output
- name: Print the output of the homepage
ansible.builtin.debug:
var: output
Step 6 - Create your Ansible Inventory
Create a file called
myInventory.yml
-
Add the following content into the inventory:
1
2
3
4
5
6
7
8
9
10
11
---
test_group:
hosts:
node1:
ansible_host: <Insert IP Address of the target VPN>
vars:
ansible_ssh_private_key_file: ~/.ssh/id_rsa <this will be replaced in the pipeline>
ansible_user: adminuser
ansible_python_interpreter: /usr/bin/python3
SSH Private Key File: one issue when automating is ensuring that credentials are properly secured. In the next step, when we build pipeline, we will download the private key from a key vault and store it in a temporary file while it is being used. After the pipeline is run it will be cleaned up.
Dynamic Inventories: With cloud
Step 7 - Store the SSH Private Key in an Azure Key Vault
- Create a keyvault if you don’t have one.
- Microsoft Guide
- name: myKeyVault-
- location:
- permission model: azure role based access
- networks: All networks (for simplicity)
- Assign access policies to the key vault.
- Microsoft Guide
- Assign the service principle
Key Vault Secrets User
- Assign yourself
Key Vault Secrets Officer
- Create a secret via the az cli:
- upload the secret via az cli to preserve the formatting of the key file, otherwise copying it in manually can cause formatting issues.
1 2
az login az keyvault secret set --vault-name <insert-keyvault-name> --name myPrivateKey --file "<path-to-your-private-ssh-key>" --encoding ascii
Step 8 - Create your pipeline
Create a file called
myPipeline.yml
-
Add the following content into the pipeline, insert the following info:
- self-hosted agent name
- service principal (SP) service connection name
- key vault name
- private key secret name
- Pipeline Code Snippet - Template
- Pipeline Code Snippet - Completed Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
---
name: Ansible Playbook Pipeline
# This section controls the triggering of the pipeline, it is set to be manually triggered only
trigger:
- none
# This section specifies the self-hosted agent pool to use
pool:
name: Default
demands: Agent.Name -equals <insert-your-agent-name>
steps:
- checkout: self
displayName: 'Checkout source code'
# This sets up the Ansible dependencies for each run of the pipeline
- task: Bash@3
displayName: 'Install Ansible Dependencies'
inputs:
targetType: 'inline'
script: |
set -e
echo "Setting up Ansible"
sudo apt update
sudo apt install python3 -y
sudo apt install -y \
python3-pip \
python3-venv
echo -e "\nCreating python virtual environment - ansible-env"
python3 -m venv ansible-env
echo -e "\nActivating venv - ansible-env"
source ansible-env/bin/activate
echo -e "\nInstalling ansible-core"
pip install ansible-core --upgrade
echo -e "\nExporting PATH to include local bin"
export PATH=~/.local/bin:$PATH
echo -e "\nAnsible Version"
ansible --version
# The below code will install collections and python requirements defined in file, for this tutorial we do not use them, but they are there for reference.
if [ -s "./ansible-requirements.yml" ]; then
echo "Installing Ansible Galaxy requirements from ansible-requirements.yml"
ansible-galaxy install -r ./ansible-requirements.yml
fi
if [ -s "./python-requirements.txt" ]; then
echo -e "\nInstalling Python requirements"
pip install -r ./python-requirements.txt
fi
echo -e "\Deactivating venv - ansible-env"
deactivate
workingDirectory: '$(System.DefaultWorkingDirectory)'
# https://learn.microsoft.com/en-us/azure/devops/pipelines/release/azure-key-vault?view=azure-devops&tabs=managedidentity%2Cyaml
- task: AzureKeyVault@2
inputs:
azureSubscription: <Insert SP Service Connection Name> # string. Alias: ConnectedServiceName. Required. Azure subscription.
KeyVaultName: <insert key vault name> # string. Required. Key vault.
SecretsFilter: <insert secret name> # string. Required. Secrets filter. Default: *.
RunAsPreJob: false # boolean. Make secrets available to whole job. Default: false.
- task: Bash@3
displayName: 'Save SSH key in a temp file'
env:
PRIVATE_KEY: "$(<Insert secret name>)"
inputs:
targetType: 'inline'
script: |
echo "$PRIVATE_KEY" > /tmp/azure_vm_id_rsa
chmod 600 /tmp/azure_vm_id_rsa
workingDirectory: '$(System.DefaultWorkingDirectory)'
# NOTE 1: this will override the key file path for all target hosts, if you want to be more specific you will need to alter the individual hostvars or predefine the paths.
# NOTE 2: we disable host key checking here as the agent is running in a non-interactive mode
- task: Bash@3
displayName: 'Execute Ansible Playbook to configure target VM'
env:
ANSIBLE_HOST_KEY_CHECKING: False
inputs:
targetType: 'inline'
script: |
set -e
source ansible-env/bin/activate
ansible-playbook -i myInventory.yml playbooks/myPlaybook.yml -e "ansible_ssh_private_key_file=/tmp/azure_vm_id_rsa"
deactivate
workingDirectory: '$(System.DefaultWorkingDirectory)'
- task: Bash@3
displayName: 'Remove SSH key'
condition: always()
inputs:
targetType: 'inline'
script: |
rm -f /tmp/azure_vm_id_rsa
workingDirectory: '$(System.DefaultWorkingDirectory)'
- Commit and push your code up to your DevOps project. (For this demo if you are just using main it is not an issue, but otherwise remember to follow good branching and pull request strategies)
Step 9 - Create and Run your pipeline in Azure DevOps
Go to your DevOps Project
Click on Pipelines
Click on Create Pipeline
Select Azure Repos Git
Select your repository with your pipeline code
Select
Existing Azure Pipelines YAML file
Select the YAML file you created earlier (e.g.,
myPipeline.yml
) in the dropdown box for PathClick Create
Click Run
The DevOps Screen should change to show the pipeline run
Click on Job with status Waiting, you will need to approve it.
Click on View
Click on Permit, then Permit again when the access request pops up
Let the pipeline run
-
Once its complete, you should now be able to enter the Public ip address of the VM and get the Nginx homepage!
- This is over port 80 as there is no TLS certificate.
Step 10 - Clean up your resources
- Go and delete any VMs you created for the lesson - see section 4 of Preparing for Ansible
- If you have exposed any VMs on the public internet, then consider removing the connection if it is no longer required.
- Delete your devops organisation and project (if you created them for this tutorial)
- If keeping your devops organisation/project, then consider removing the service credentials and self-hosted agents if they are no longer required.
- Delete any unused Service Principals.
Next Steps
- Code Snippets
- Next tutorial coming soon…