Introducing Hyperlight: Virtual machine-based security for functions at scale
The Microsoft Azure Core Upstream team is excited to announce the Hyperlight…
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:
Prerequisites:
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:
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.
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):
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:
storage_account_name = "<unique storage accountname>" container_name = "terraform-state" key = "demo-java.terraform.tfstate"
To initialize Terraform shell script will run init command with provided backend configuration:
#!/bin/bash terraform init -backend-config=backend.tfvars
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.
terraform apply -auto-approve export vmss_ip=$(terraform output vm_ip) echo "host1 ansible_ssh_port=50000 ansible_ssh_host=$vmss_ip" > inventory echo "host2 ansible_port=50001 ansible_ssh_host=$vmss_ip" >> inventory
The full Terraform template can be found on GitHub here.
It provisions resource group, virtual network, subnet, public IP, load balancer and NAT rules and VM availability set.
For example, here is the VM resource template:
resource "azurerm_virtual_machine" "vm" { name = "vm${count.index}" location = "${azurerm_resource_group.rg.location}" resource_group_name = "${azurerm_resource_group.rg.name}" availability_set_id = "${azurerm_availability_set.avset.id}" network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"] count = 2 vm_size = "Standard_D1" storage_os_disk { name = "osdisk${count.index}" create_option = "FromImage" } storage_image_reference { publisher = "RedHat" offer = "RHEL" sku = "7.3" version = "latest" } os_profile { computer_name = "myvm" admin_username = "azureuser" admin_password = "xxxx" } os_profile_linux_config { disable_password_authentication = true ssh_keys { path = "/home/azureuser/.ssh/authorized_keys" key_data = "xxxx" } } tags { environment = "Terraform Demo" } }
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:
- name: unDeploy sample app file: path=/usr/share/tomcat/webapps/spring-music.war owner=tomcat group=tomcat state=absent - name: wait for tomcat to undeploy the app wait_for: path=/usr/share/tomcat/webapps/spring-music/ state=absent - name: Deploy sample app copy: src=../../build/libs/spring-music.war dest=/usr/share/tomcat/webapps/spring-music.war owner=tomcat group=tomcat notify: restart tomcat
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/
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.