In this article we will use Terraform (Infrastructure as Code) to swiftly bring up a Microsoft Azure Virtual Machine instance in East US on a static IP, add a DNS Zone for the site in mention and install docker/docker-compose on it.
We will use ‘myweb’ as an example in this article, using the same base path of ‘dev’ that was previously created and the container-admin Service Principal.
Please use ‘Create your Azure free account today’ prior to commencing with this article.
–>
Go in to the dev directory/link located within your home directory:
$ cd ~/dev
Upgrade the Azure CLI on your host:
$ sudo apt update && sudo apt -y upgrade azure-cli
Grab Terraform:
$ wget https://releases.hashicorp.com/terraform/0.12.20/terraform_0.12.20_linux_amd64.zip
Install Unzip if you do not have it installed:
$ sudo apt -y install unzip
Unzip it to ~/.local/bin and set permissions accordingly on it:
$ unzip terraform_0.12.20_linux_amd64.zip -d ~/.local/bin && chmod 754 ~/.local/bin/terraform
Add this function in to your user’s startup to parse the previously created credentials file and pass pertinent login information as a servicePrincipal to Terraform, in a sub-shell:
$ cat << 'EOF' >> ~/.bashrc > > function terraform-az-sp() { > (export $(grep -v '^\[' $HOME/.azure/credentials | sed 's/application_id/arm_client_id/; s/client_secret/arm_client_secret/; s/directory_id/arm_tenant_id/; s/subscription_id/arm_subscription_id/; s/^[^=]*/\U&\E/' | xargs) && terraform $*) > } > EOF
Remove the subscription_id in our previously created az-login-sp function (user’s startup):
$ sed -i "s:\$HOME/.azure/credentials | xargs):\$HOME/.azure/credentials | sed '/subscription_id/d' | xargs):" ~/.bashrc
Source it in:
$ . ~/.bashrc
Add the <SUBSCRIPTION ID> (UI Console -> Azure Active Directory -> Search for (top left): Subscriptions -> Click your subscription -> Overview) in to the Azure credentials file (replace <SUBSCRIPTION ID>):
$ echo "subscription_id=<SUBSCRIPTION ID>" >> ~/.azure/credentials
In the Subscription, add roles to container-admin:
Access control (IAM) -> Role assignments ->
Add (Add role assignment) -> Role: DNS Zone Contributor -> Assign access to: Azure AD user, group, or service principal -> Select: container-admin -> Save
Add (Add role assignment) -> Role: Network Contributor -> Assign access to: Azure AD user, group, or service principal -> Select: container-admin -> Save
Add (Add role assignment) -> Role: Virtual Machine Contributor -> Assign access to: Azure AD user, group, or service principal -> Select: container-admin -> Save
Create a work folder and change in to it:
$ mkdir -p terraform/azure/myweb/scripts terraform/azure/myweb/rbac && cd terraform/azure/myweb
Create a custom Role Based Access for Resource Groups so container-admin can Read, Write and Delete Resource Groups in the subscription (replace <SUBSCRIPTION ID>):
$ cat << 'EOF' > rbac/rg-custom.jsn > { > "Name": "Resource Group Allowance", > "IsCustom": true, > "Description": "Can read, write and delete Resource Groups.", > "Actions": [ > "Microsoft.Resources/subscriptions/resourceGroups/read", > "Microsoft.Resources/subscriptions/resourceGroups/write", > "Microsoft.Resources/subscriptions/resourceGroups/delete" > ], > "NotActions": [], > "AssignableScopes": [ > "/subscriptions/<SUBSCRIPTION ID>" > ] > } > EOF
Authenticate to Azure using the CLI with the same Administrative credentials you use in the UI (a browser window will popup requesting credentials):
$ az login
Create the Role Definition:
$ az role definition create --role-definition rbac/rg-custom.jsn
Add the role to container-admin:
$ az role assignment create --role "Resource Group Allowance" --assignee $(grep application_id ~/.azure/credentials | cut -f2 -d=) --subscription $(grep subscription_id ~/.azure/credentials | cut -f2 -d=)
Generate an SSH Key Pair (no password) and restrict permissions on it:
$ ssh-keygen -q -t rsa -b 2048 -N '' -f ~/.ssh/myweb && chmod 400 ~/.ssh/myweb
Ensure the terraform version is greater then or equal to 0.12:
$ cat << 'EOF' > versions.tf > terraform { > required_version = ">= 0.12" > } > EOF
Set the version for the AzureRM Provider to greater then or equal to 1.44:
$ cat << 'EOF' > provider.tf > provider "azurerm" { > version = ">= 1.44" > } > EOF
Set the default region and prefix variable:
$ cat << 'EOF' > vars.tf > variable "region" { > default = "EastUS" > } > > variable "prefix" { > default = "myweb" > } > EOF
The following is performed with this script/code:
- create a resource group where all of our resources will be put in (within East US)
- create an Azure DNS Zone of myweb.com (no A records will be created)
- create a virtual network of 10.0.0.0/16
- add a subnet of 10.0.1.0/24 within the VNET
- allocate a static Public IP
- create a Network Security Group and add a Security rule for allowing SSH (port 22) Inbound
- create a Network Interface with a Dynamic Private IP
- create a Basic A1 instance based off of Ubuntu 18_04 with a Standard SSD, password authentication turned off, our public key added as authorized and reference an extraneous file for custom_data (initialization script on Virtual Machine boot)
- tag all resources
$ cat << 'EOF' > vm.tf > # Create a Resource Group > resource "azurerm_resource_group" "myweb" { > name = "${var.prefix}-rg" > location = var.region > > tags = { > Site = "${var.prefix}.com" > } > } > > # Create a DNS Zone > resource "azurerm_dns_zone" "myweb" { > name = "${var.prefix}.com" > resource_group_name = azurerm_resource_group.myweb.name > > tags = { > Site = "${var.prefix}.com" > } > } > > # Create a Virtual Network > resource "azurerm_virtual_network" "myweb" { > name = "${var.prefix}-net" > address_space = ["10.0.0.0/16"] > location = azurerm_resource_group.myweb.location > resource_group_name = azurerm_resource_group.myweb.name > > tags = { > Site = "${var.prefix}.com" > } > } > > # Add a Subnet > resource "azurerm_subnet" "internal" { > name = "internal" > resource_group_name = azurerm_resource_group.myweb.name > virtual_network_name = azurerm_virtual_network.myweb.name > address_prefix = "10.0.1.0/24" > } > > # Allocate a Static Public IP > resource "azurerm_public_ip" "external" { > name = "external" > location = var.region > resource_group_name = azurerm_resource_group.myweb.name > allocation_method = "Static" > > tags = { > Site = "${var.prefix}.com" > } > } > > # Create a Network Security Group and allow inbound port(s) > resource "azurerm_network_security_group" "myweb" { > name = "${var.prefix}-nsg" > location = var.region > resource_group_name = azurerm_resource_group.myweb.name > > security_rule { > name = "SSH" > priority = 1001 > direction = "Inbound" > access = "Allow" > protocol = "Tcp" > source_port_range = "*" > destination_port_range = "22" > source_address_prefix = "*" > destination_address_prefix = "*" > } > > tags = { > Site = "${var.prefix}.com" > } > } > > # Create a Network Interface with a Dynamic Private IP > resource "azurerm_network_interface" "myweb" { > name = "${var.prefix}-nic" > location = azurerm_resource_group.myweb.location > resource_group_name = azurerm_resource_group.myweb.name > network_security_group_id = azurerm_network_security_group.myweb.id > > ip_configuration { > name = "${var.prefix}-nic_conf" > subnet_id = azurerm_subnet.internal.id > private_ip_address_allocation = "Dynamic" > public_ip_address_id = azurerm_public_ip.external.id > } > > tags = { > Site = "${var.prefix}.com" > } > } > > # Create an Ubuntu Virtual Machine with key based access and run a script on boot; use a Standard SSD > resource "azurerm_virtual_machine" "myweb" { > name = "${var.prefix}-vm" > location = azurerm_resource_group.myweb.location > resource_group_name = azurerm_resource_group.myweb.name > network_interface_ids = [azurerm_network_interface.myweb.id] > vm_size = "Basic_A1" > > storage_image_reference { > publisher = "Canonical" > offer = "UbuntuServer" > sku = "18.04-LTS" > version = "latest" > } > > storage_os_disk { > name = "${var.prefix}-disk" > caching = "ReadWrite" > create_option = "FromImage" > managed_disk_type = "StandardSSD_LRS" > } > > os_profile { > computer_name = var.prefix > admin_username = "ubuntu" > custom_data = data.template_file.init_script.rendered > } > > os_profile_linux_config { > disable_password_authentication = true > ssh_keys { > path = "/home/ubuntu/.ssh/authorized_keys" > key_data = file("~/.ssh/${var.prefix}.pub") > } > } > > tags = { > Site = "${var.prefix}.com" > } > } > EOF
Output our allocated static Public IP after creation:
$ cat << 'EOF' > output.tf > output "static_public_ip" { > value = azurerm_public_ip.external.ip_address > } > EOF
Create a template file to reference a boot initialization script:
$ cat << 'EOF' > install.tf > data "template_file" "init_script" { > template = "${file("scripts/install.sh")}" > } > EOF
Create the shell script for custom_data:
$ cat << 'EOF' > scripts/install.sh > #!/bin/bash > > MY_HOME="/home/ubuntu" > export DEBIAN_FRONTEND=noninteractive > > # Install prereqs > apt update > apt install -y python3-pip apt-transport-https ca-certificates curl software-properties-common > # Install docker > curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - > add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > apt update > apt install -y docker-ce > # Install docker-compose > su ubuntu -c "mkdir -p $MY_HOME/.local/bin" > su ubuntu -c "pip3 install docker-compose --upgrade --user && chmod 754 $MY_HOME/.local/bin/docker-compose" > usermod -aG docker ubuntu > # Add PATH > printf "\nexport PATH=\$PATH:$MY_HOME/.local/bin\n" >> $MY_HOME/.bashrc > > exit 0 > EOF
Initialize the directory:
$ terraform init
Run a dry-run to see what will occur:
$ terraform-az-sp plan
Provision:
$ terraform-az-sp apply -auto-approve
Log on to the instance after a short while:
$ ssh -i ~/.ssh/myweb ubuntu@<The value of static_public_ip that was reported. One can also use 'terraform-az-sp output static_public_ip' to print it again.>
Type yes and hit enter to accept.
On the host (a short while is needed for the boot initialization script to complete):
$ docker --version $ docker-compose --version $ logout
Tear down what was created by first performing a dry-run to see what will occur:
$ terraform-az-sp plan -destroy
Tear down the instance:
$ terraform-az-sp destroy -auto-approve
Destroy the Network Watcher Resource Group that was automatically created (if not found prior), if you do not have other virtual networks in the region which are using it:
$ az group delete -n NetworkWatcherRG --yes
Logout of the Azure CLI session:
$ az logout
<–
References:
Source:
terraform_azure_myweb