- Introduction
- Getting Started
- Install our dependencies
- Deploy a policy
- Create and configure our environment variables
- Deploy the Packer template
- Deploy the infrastructure as code with Terraform
- References
For this project, you will write a Packer template and a Terraform template to deploy a customizable, scalable web server in Azure.
Infrastructure as Code (IaC) is the management of infrastructure (networks, virtual machines, load balancers, and connection topology) in a descriptive model, using the same versioning as DevOps team uses for source code. Like the principle that the same source code generates the same binary, an IaC model generates the same environment every time it is applied. IaC is a key DevOps practice and is used in conjunction with continuous delivery.
For this project we will use Azure as our cloud provider, in conjunction with Terraform for our IaC needs, and Packer, which will help us with the creation of virtual machine images.
We will use a Packer template (in JSON format), with a Terraform template to deploy a customizable, scalable web server in Azure.
In this project we will follow the following steps:
- Install our dependencies
- Deploy a policy
- Create and configure our environment variables
- Deploy the Packer template
- Deploy the infrastructure as code with Terraform
- Create an Azure Account
- Install the Azure command line interface
- Install Packer
- Install Terraform
We will deploy a security policy that enforces that all the resources that we deploy have a tag, this is to have a better understanding of what each resource does. The rules of the policy are defined in the enforceTag.json
file. To deploy the policy we write in our command line:
az policy definition create --name tagging-policy --mode indexed --rules enforceTag.json --description "Policy to enforce all indexed resources are tagged"
When we have done this, we should wait a few minutes and then enter the following command:
az policy assignment list
If everything went correctly, we should be able to see a json definition of our new policy:
We are ready to continue to the next step.
We will need to configure environment variables in our local computer to use the server.json
Packer template. We will need to create an Azure resource group and then get 4 variables that we can obtain from the resource group.
Ensure that you are logged in to your Azure Subscription
az login
During the build process, Packer creates temporary Azure resources as it builds the source VM. To capture that source VM for use as an image, we must define a resource group. The output from the Packer build process is stored in this resource group.
az group create -n udacity-rg -l southcentralus
Packer authenticates with Azure using a service principal. An Azure service principal is a security identity that you can use with apps, services, and automation tools like Packer. We control and define the permissions as to what operations the service principal can perform in Azure.
az ad sp create-for-rbac --query "{ client_id: appId, client_secret: password, tenant_id: tenant }"
We will also need to obtain the Azure Subscription ID with the following command:
az account show --query "{ subscription_id: id }"
With this 4 variables identified, we can now go to the terminal and export the environment variables with the following commands:
export ARM_CLIENT_ID=your_client_id
export ARM_CLIENT_SECRET=your_client_secret
export ARM_SUBSCRIPTION_ID=your_suscription_id
export ARM_TENANT_ID=your_tenant_id
Once you have exported this environment variables, use the printenv
command to check that they are properly configured:
printenv
We can now proceed with the exercise
Now we can deploy our Packer template with the following command:
packer build server.json
If everything went correctly, we should be able to see an output similar to the following:
azure-arm: output will be in this color.
==> azure-arm: Running builder ...
==> azure-arm: Getting tokens using client secret
==> azure-arm: Getting tokens using client secret
azure-arm: Creating Azure Resource Manager (ARM) client ...
==> azure-arm: WARNING: Zone resiliency may not be supported in South Central US, checkout the docs at https://docs.microsoft.com/en-us/azure/availability-zones/
==> azure-arm: Creating resource group ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-szhuz5shze'
==> azure-arm: -> Location : 'South Central US'
==> azure-arm: -> Tags :
==> azure-arm: ->> tag : udacity
==> azure-arm: Validating deployment template ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-szhuz5shze'
==> azure-arm: -> DeploymentName : 'pkrdpszhuz5shze'
==> azure-arm: Deploying deployment template ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-szhuz5shze'
==> azure-arm: -> DeploymentName : 'pkrdpszhuz5shze'
==> azure-arm:
==> azure-arm: Getting the VM's IP address ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-szhuz5shze'
==> azure-arm: -> PublicIPAddressName : 'pkripszhuz5shze'
==> azure-arm: -> NicName : 'pkrniszhuz5shze'
==> azure-arm: -> Network Connection : 'PublicEndpoint'
==> azure-arm: -> IP Address : '104.214.92.154'
==> azure-arm: Waiting for SSH to become available...
==> azure-arm: Connected to SSH!
==> azure-arm: Provisioning with shell script: C:\Users\jorge\AppData\Local\Temp\packer-shell006649467
==> azure-arm: + echo Hello, World!
==> azure-arm: + nohup busybox httpd -f -p 80
==> azure-arm: Querying the machine's properties ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-szhuz5shze'
==> azure-arm: -> ComputeName : 'pkrvmszhuz5shze'
==> azure-arm: -> Managed OS Disk : '/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/pkr-Resource-Group-szhuz5shze/providers/Microsoft.Compute/disks/pkrosszhuz5shze'
==> azure-arm: Querying the machine's additional disks properties ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-szhuz5shze'
==> azure-arm: -> ComputeName : 'pkrvmszhuz5shze'
==> azure-arm: Powering off machine ...
==> azure-arm: -> ResourceGroupName : 'pkr-Resource-Group-szhuz5shze'
==> azure-arm: -> ComputeName : 'pkrvmszhuz5shze'
==> azure-arm: Capturing image ...
==> azure-arm: -> Compute ResourceGroupName : 'pkr-Resource-Group-szhuz5shze'
==> azure-arm: -> Compute Name : 'pkrvmszhuz5shze'
==> azure-arm: -> Compute Location : 'South Central US'
==> azure-arm: -> Image ResourceGroupName : 'udacity-rg'
==> azure-arm: -> Image Name : 'PackerImage'
==> azure-arm: -> Image Location : 'South Central US'
==> azure-arm:
==> azure-arm: Deleting individual resources ...
==> azure-arm: Adding to deletion queue -> Microsoft.Compute/virtualMachines : 'pkrvmszhuz5shze'
==> azure-arm: Adding to deletion queue -> Microsoft.Network/networkInterfaces : 'pkrniszhuz5shze'
==> azure-arm: Adding to deletion queue -> Microsoft.Network/virtualNetworks : 'pkrvnszhuz5shze'
==> azure-arm: Adding to deletion queue -> Microsoft.Network/publicIPAddresses : 'pkripszhuz5shze'
==> azure-arm: Attempting deletion -> Microsoft.Network/virtualNetworks : 'pkrvnszhuz5shze'
==> azure-arm: Attempting deletion -> Microsoft.Network/publicIPAddresses : 'pkripszhuz5shze'
==> azure-arm: Waiting for deletion of all resources...
==> azure-arm: Attempting deletion -> Microsoft.Network/networkInterfaces : 'pkrniszhuz5shze'
==> azure-arm: Attempting deletion -> Microsoft.Compute/virtualMachines : 'pkrvmszhuz5shze'
==> azure-arm: Error deleting resource. Will retry.
==> azure-arm: Name: pkripszhuz5shze
==> azure-arm: Error: network.PublicIPAddressesClient#Delete: Failure sending request: StatusCode=400 -- Original Error: Code="PublicIPAddressCannotBeDeleted" Message="Public IP address /subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/pkr-Resource-Group-szhuz5shze/providers/Microsoft.Network/publicIPAddresses/pkripszhuz5shze can not be deleted since it is still allocated to resource /subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/pkr-Resource-Group-szhuz5shze/providers/Microsoft.Network/networkInterfaces/pkrniszhuz5shze/ipConfigurations/ipconfig. In order to delete the public IP, disassociate/detach the Public IP address from the resource. To learn how to do this, see aka.ms/deletepublicip." Details=[]
==> azure-arm:
==> azure-arm: Error deleting resource. Will retry.
==> azure-arm: Name: pkrvnszhuz5shze
==> azure-arm: Error: network.VirtualNetworksClient#Delete: Failure sending request: StatusCode=400 -- Original Error: Code="InUseSubnetCannotBeDeleted" Message="Subnet pkrsnszhuz5shze is in use by /subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/pkr-Resource-Group-szhuz5shze/providers/Microsoft.Network/networkInterfaces/pkrniszhuz5shze/ipConfigurations/ipconfig and cannot be deleted. In order to delete the subnet, delete all the resources within the subnet. See aka.ms/deletesubnet." Details=[]
==> azure-arm:
==> azure-arm: Attempting deletion -> Microsoft.Network/publicIPAddresses : 'pkripszhuz5shze'
==> azure-arm: Attempting deletion -> Microsoft.Network/virtualNetworks : 'pkrvnszhuz5shze'
==> azure-arm: Deleting -> Microsoft.Compute/disks : '/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/pkr-Resource-Group-szhuz5shze/providers/Microsoft.Compute/disks/pkrosszhuz5shze'
==> azure-arm: Removing the created Deployment object: 'pkrdpszhuz5shze'
==> azure-arm:
==> azure-arm: Cleanup requested, deleting resource group ...
==> azure-arm: Resource group has been deleted.
Build 'azure-arm' finished after 5 minutes 19 seconds.
==> Wait completed after 5 minutes 19 seconds
==> Builds finished. The artifacts of successful builds are:
--> azure-arm: Azure.ResourceManagement.VMImage:
OSType: Linux
ManagedImageResourceGroupName: udacity-rg
ManagedImageName: PackerImage
ManagedImageId: /subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Compute/images/Packages/PackerImage
ManagedImageLocation: South Central US
And in the Azure portal we should be able to see the image "PackerImage" in our resource group.
The first step is to run the following Terraform command to download all necessary plugins:
terraform init
Should you wish to change the number of virtual machines that are deployed, or the resource group prefix, or anything else, feel free to change it in the vars.tf
file. Just change the default value, or remove it and set it when you will deploy it.
Before we can plan our solution, we have to take into account that we have already created the resource group for our PackerImage, and Terraform does not allow to deploy resources into existing resource groups.
To fix this we need to import the existing resource group to Terraform so that it knows to deploy our resources there. To do that we have to run the following command:
terraform import azurerm_resource_group.main /subscriptions/{subsriptionId}/resourceGroups/{resourceGroupName}
In my specific case it is:
terraform import azurerm_resource_group.main /subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg
Once that is done, we can run the following command to plan our solution:
terraform plan -out solution.plan
To create our infrastructure in Azure we have to run the following command:
terraform apply
While the infrastructure is deploying, we should get an output similar to this:
azurerm_virtual_network.main: Creating...
azurerm_availability_set.main: Creating...
azurerm_public_ip.main: Creating...
azurerm_managed_disk.main[1]: Creating...
azurerm_managed_disk.main[0]: Creating...
azurerm_network_security_group.main: Creating...
azurerm_availability_set.main: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Compute/availabilitySets/udacity-aset]
azurerm_managed_disk.main[0]: Creation complete after 3s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Compute/disks/udacity-md-0]
azurerm_managed_disk.main[1]: Creation complete after 3s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Compute/disks/udacity-md-1]
azurerm_virtual_network.main: Creation complete after 4s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/virtualNetworks/udacity-network]
azurerm_subnet.main: Creating...
azurerm_public_ip.main: Creation complete after 4s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/publicIPAddresses/udacity-ip]
azurerm_lb.main: Creating...
azurerm_network_security_group.main: Creation complete after 4s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkSecurityGroups/udacity-nsg]
azurerm_lb.main: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/loadBalancers/udacity-lb]
azurerm_lb_backend_address_pool.main: Creating...
azurerm_lb_backend_address_pool.main: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/loadBalancers/udacity-lb/backendAddressPools/udacity-bap]
azurerm_subnet.main: Creation complete after 4s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/virtualNetworks/udacity-network/subnets/udacity-subnet]
azurerm_network_interface.main[0]: Creating...
azurerm_network_interface.main[1]: Creating...
azurerm_network_interface.main[0]: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkInterfaces/udacity-nic-0]
azurerm_network_interface.main[1]: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkInterfaces/udacity-nic-1]
azurerm_network_interface_backend_address_pool_association.main[0]: Creating...
azurerm_network_interface_security_group_association.main[1]: Creating...
azurerm_network_interface_backend_address_pool_association.main[1]: Creating...
azurerm_network_interface_security_group_association.main[0]: Creating...
azurerm_linux_virtual_machine.main[0]: Creating...
azurerm_linux_virtual_machine.main[1]: Creating...
azurerm_network_interface_security_group_association.main[1]: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkInterfaces/udacity-nic-1|/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkSecurityGroups/udacity-nsg]
azurerm_network_interface_backend_address_pool_association.main[0]: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkInterfaces/udacity-nic-0/ipConfigurations/udacity-ipconfig|/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/loadBalancers/udacity-lb/backendAddressPools/udacity-bap]
azurerm_network_interface_backend_address_pool_association.main[1]: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkInterfaces/udacity-nic-1/ipConfigurations/udacity-ipconfig|/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/loadBalancers/udacity-lb/backendAddressPools/udacity-bap]
azurerm_network_interface_security_group_association.main[0]: Creation complete after 1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkInterfaces/udacity-nic-0|/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Network/networkSecurityGroups/udacity-nsg]
azurerm_linux_virtual_machine.main[1]: Still creating... [10s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [10s elapsed]
azurerm_linux_virtual_machine.main[1]: Still creating... [20s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [20s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [30s elapsed]
azurerm_linux_virtual_machine.main[1]: Still creating... [30s elapsed]
azurerm_linux_virtual_machine.main[1]: Still creating... [40s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [40s elapsed]
azurerm_linux_virtual_machine.main[1]: Creation complete after 47s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Compute/virtualMachines/udacity-vm-1]
azurerm_linux_virtual_machine.main[0]: Still creating... [50s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [1m0s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [1m10s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [1m20s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [1m30s elapsed]
azurerm_linux_virtual_machine.main[0]: Still creating... [1m40s elapsed]
azurerm_linux_virtual_machine.main[0]: Creation complete after 1m47s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Compute/virtualMachines/udacity-vm-0]
azurerm_virtual_machine_data_disk_attachment.main[1]: Creating...
azurerm_virtual_machine_data_disk_attachment.main[0]: Creating...
azurerm_virtual_machine_data_disk_attachment.main[1]: Still creating... [10s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[0]: Still creating... [10s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[1]: Still creating... [20s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[0]: Still creating... [20s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[0]: Still creating... [30s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[1]: Still creating... [30s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[1]: Still creating... [40s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[0]: Still creating... [40s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[0]: Still creating... [50s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[1]: Still creating... [50s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[1]: Still creating... [1m0s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[0]: Still creating... [1m0s elapsed]
azurerm_virtual_machine_data_disk_attachment.main[0]: Creation complete after 1m1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Compute/virtualMachines/udacity-vm-0/dataDisks/udacity-md-0]
azurerm_virtual_machine_data_disk_attachment.main[1]: Creation complete after 1m1s [id=/subscriptions/76ecc65a-b866-4328-925f-0cafa9642559/resourceGroups/udacity-rg/providers/Microsoft.Compute/virtualMachines/udacity-vm-1/dataDisks/udacity-md-1]
After we have deployed our infrastructure, we should get a confirmation message from Terraform
We can also check if the resources are deployed in the Azure Portal, the result will look something like the following:
We can also check all the resources that we just deployed in Terraform with the following command:
terraform show
Finally, remember to destroy the resources:
terraform destroy