Tutorial: CI/CD for Azure using Terraform, Ansible and VSTS
ByElena Neroslavskaya, Elena Neroslavskaya is Cloud Solution Architect at Microsoft. She is interested in all things open source and cloud, and is always looking for new ways to help and learn from OSS community and do great things on Azure or Cloud Foundry. When she’s not working, she’s usually learning, watching stars or hanging out with her family.
This is part 1 of a 2-part series demonstrating how to continuously build and deploy Azure infrastructure for the applications running on Azure. The first article will show how open source tools, such as Terraform and Ansible, can be leveraged to implement Infrastructure as Code. The second article in the series will enhance the infrastructure deployment to build immutable infrastructure for the applications and adding Packer into the set of tools.
In part 1, we will walk though how to continually build and deploy a Java Spring Boot application and its required infrastructure and middleware using Visual Studio Team Services. We will apply software development practices to infrastructure build and configuration. To demonstrate Infrastructure as a Code principle we will use Terraform to codify and provision infrastructure, and Ansible to automate configuration and middleware.
Here is a picture of the flow:
Code or configuration change is committed to Git
VSTS Build builds and packages Spring Boot application using Gradle
VSTS Release provisions Infrastructure using Terraform
VSTS Release configures JDK, Tomcat and application on the provisioned servers
Service Principal with Contributor access to the subscription. Refer to this documentation for instructions.
Storage account and container to save Terraform state in (update “backend.tfvars” with the names). Terraform must store state about your managed infrastructure and configuration. This state is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.
All code and pipelines for this article could be found on GitHub.
Spring Boot Application Build
In this example, we first build and package a Spring Boot application using Gradle. You can import the full build definition from GitHub repository or create a Java Gradle project from scratch by following steps provided in documentation “Build your Java app with Gradle.” Here is outline of the steps and commands customizations:
Clone GitHub repo from this example or import to VSTS
Create a build definition (Build & Release tab > Builds)
Search and use “Gradle” definition. In the repository tab of build definition make sure the repository selected is the one where you pushed (Git). It will create the template of the steps required.
In ”Copy Files” – customize the step to copy built war file and “iac” directory with configuration
On the Triggers tab, enable continuous integration (CI). This tells the system to queue a build whenever new code is committed. Save and Queue the build.
Infrastructure Provisioning
Release is built on the VSTS Agent that has the required Terraform and Ansible installed.
Terraform versions, plans and build infrastructure. Ansible automation provides agentless way of managing servers. All it requires is SSH connection and python installed. We will configure VSTS agent with the ssh key that is provided by Terraform during provisioning VMs on Azure and by Ansible to communicate to provisioned servers.
For Ansible to be able to communicate to VMs it has to know server IPs, provided to it in the form of inventory file. Once Terraform completes provisioning, we will output servers IPs into a file which is used by Ansible.
Here is the Release pipeline definition (it could be imported from GitHub as well):
Start by defining Empty Release Definition, and link the build prepared above as an artifact.
Use custom VSTS Agent from “ACI-Pool”
Define Variable Group with environment variables (see documentation) that provide connectivity to subscription:
ARM_SUBSCRIPTION_ID, ARM_TENANT_ID : Subscription (run ` az account list` in Azure CLI to find out tenantId and subscriptionId )
ARM_CLIENT_ID, ARM_CLIENT_SECRET: Service Principal is and password obtained when creating SP
a. Install SSH Key – Installs SSH key on the agent. Add public (SSH_PUB_KEY env variable) and private part of the previously generated keypair. And set Known hosts entry to “default.”
b. Shell Script – Terraform Init – point to Terraform init.sh script and pass environment variables $(ARM_CLIENT_ID) $(ARM_CLIENT_SECRET) $(ARM_SUBSCRIPTION_ID) $(ARM_TENANT_ID) $(ARM_ACCESS_KEY)
Terraform must initialize Azure Resource provider and configured backend for keeping the state (Azure storage in this example) before the use. Here is the snippet doing it from our Terraform template:
terraform {
required_version = ">= 0.11"
backend "azurerm" {}
}
# Configure the Microsoft Azure Provider
provider "azurerm" {}
Terraform initialization can be done by simply running “terraform init” command.
To avoid hard coding backend storage in terraform template, we are using partial configuration and providing the required backend configuration in variables file – “backend.tfvars”. Here is a configuration example that uses Storage account we created as part of prerequisites:
Upon successful run it will have following output indication terraform has been initialized.
c. Shell Script – Terraform apply Terraform apply will apply the changes required to reach the desired state of the configuration as defined by “main.tf” Add $(SSH_PUB_KEY) for Terraform to provision VM’s with it.
Terraform generates an execution plan describing what it will do to reach the desired state, and then executes it to build the described infrastructure. As the configuration changes, Terraform is able to determine what changed and create incremental execution plans that can be applied.
In the example below, Terraform detected that some changes are required in the infrastructure:
The shell file executes terraform build and generates inventory file with the details of the provisioned VMs. Ansible will use it to configure the application.
Template defines the following output variables that are used by pipeline files:
output "vm_ip" {
value = "${azurerm_public_ip.demo_public_ip.fqdn}"
}
output "vm_dns" {
value = "http://${azurerm_public_ip.demo_public_ip.fqdn}"
}
d. Ansible playbook run – Ansible is a tool that greatly simplifies configuration management tasks. Playbook is the desired state configuration expressed in YAML.
In this example the playbook “site.yml” uses role “tomcat” to install required JDK, Tomcat 7.0, configure Tomcat and deploy the SpringMusic application . Here are tasks that undeploy previous version of the application and install the new one:
Ansible, like Terraform, operates to reach desired state on the configuration. Below is an example output of the pipeline when Tomcat was already installed on the provisioned servers and only the application was changed:
The result is following resources created, up and running.
And you can see the application at http:///spring-music/
Conclusion
In this example we demonstrated a simple flow that provides application deployment and infrastructure automation. While implementing Infrastructure as Code minimizes configuration drift, it is still possible that some server might deviate due to manual changes and patches. Another consideration is that scale-out process configuration should be done on the new machines and it might take time for complex playbooks. This and some other considerations will be addressed in Part 2 of the tutorial, focused on building immutable infrastructure.
Check out Part 2, “Immutable infrastructure for Azure, using VSTS, Terraform, Packer and Ansible,” here.
Note: There is a “Terraform” task available on VSTS marketplace, it has great capabilities of running templates and using storage account as a backend, but it’s currently Windows only, while Ansible task is Linux based task – for that reason this example was written to use shell scripts.
Elena Neroslavskaya
Elena Neroslavskaya is Cloud Solution Architect at Microsoft. She is interested in all things open source and cloud, and is always looking for new ways to help and learn from OSS community and do great things on Azure or Cloud Foundry. When she’s not working, she’s usually learning, watching stars or hanging out with her family.
Open source is the foundation for AI and, as AI workloads scale, developers need that foundation to be more secure, more predictable, and easier to build apps and agents.
The Cloud Native Computing Foundation’s (CNCF) Hyperlight project delivers faster, more secure, and smaller workload execution to the cloud-native ecosystem.