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
« Firmware – CyanogenMod 12.1 (Lollipop) – Transformer Pad (TF701T – Macallan) Azure/Ansible – Provision a Virtual Machine instance using Infrastructure as Code »
