{"id":5771,"date":"2020-02-22T00:59:24","date_gmt":"2020-02-22T05:59:24","guid":{"rendered":"\/db-blog\/?p=5771"},"modified":"2020-03-11T19:18:13","modified_gmt":"2020-03-11T23:18:13","slug":"azure-terraform-provision-a-virtual-machine-instance-using-infrastructure-as-code","status":"publish","type":"post","link":"https:\/\/droidbasement.com\/db-blog\/azure-terraform-provision-a-virtual-machine-instance-using-infrastructure-as-code\/","title":{"rendered":"Azure\/Terraform \u2013 Provision a Virtual Machine instance using Infrastructure as Code"},"content":{"rendered":"\n<p>In this article we will use <a rel=\"noreferrer noopener\" aria-label=\"Terraform (opens in a new tab)\" href=\"https:\/\/www.terraform.io\/\" target=\"_blank\">Terraform<\/a> (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.<\/p>\n\n\n\n<p>We will use &#8216;myweb&#8217; as an example in this article, using the same base path of &#8216;dev&#8217; that was <a rel=\"noreferrer noopener\" aria-label=\"previously created (opens in a new tab)\" href=\"https:\/\/droidbasement.com\/db-blog\/?p=5346\" target=\"_blank\">previously created<\/a> and the <a rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\" href=\"https:\/\/droidbasement.com\/db-blog\/azure-configure-the-azure-cli-in-the-development-environment-for-containerization\/\" target=\"_blank\">container-admin<\/a> Service Principal.<\/p>\n\n\n\n<p>Please use <a href=\"https:\/\/azure.microsoft.com\/en-us\/free\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"'Create your Azure free account today' (opens in a new tab)\">&#8216;Create your Azure free account today&#8217;<\/a> prior to commencing with this article.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>&#8211;&gt;<br>Go in to the dev directory\/link located within your home directory:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cd ~\/dev<\/pre>\n\n\n\n<p>Upgrade the Azure CLI on your host:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ sudo apt update &amp;&amp; sudo apt -y upgrade azure-cli<\/pre>\n\n\n\n<p>Grab Terraform:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ wget https:\/\/releases.hashicorp.com\/terraform\/0.12.20\/terraform_0.12.20_linux_amd64.zip<\/pre>\n\n\n\n<p>Install Unzip if you do not have it installed:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ sudo apt -y install unzip<\/pre>\n\n\n\n<p>Unzip it to ~\/.local\/bin and set permissions accordingly on it:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ unzip terraform_0.12.20_linux_amd64.zip -d ~\/.local\/bin &amp;&amp; chmod 754 ~\/.local\/bin\/terraform<\/pre>\n\n\n\n<p>Add this function in to your user&#8217;s startup to parse the previously created credentials file and pass pertinent login information as a servicePrincipal to  Terraform, in a sub-shell:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt;&gt; ~\/.bashrc\n&gt;\n&gt; function terraform-az-sp() {\n&gt;         (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&amp;\\E\/'<em> <\/em>| xargs) &amp;&amp; terraform $*)\n&gt; }\n&gt; EOF<\/pre>\n\n\n\n<p>Remove the subscription_id in our previously created az-login-sp function (user\u2019s startup):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ sed -i \"s:\\$HOME\/.azure\/credentials | xargs):\\$HOME\/.azure\/credentials | sed '\/subscription_id\/d' | xargs):\" ~\/.bashrc<\/pre>\n\n\n\n<p>Source it in:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ . ~\/.bashrc<\/pre>\n\n\n\n<p>Add the &lt;SUBSCRIPTION ID&gt; (UI Console -&gt; Azure Active Directory -&gt;  Search for (top left): Subscriptions -&gt; Click your subscription -&gt; Overview) in to the Azure credentials file  (replace &lt;SUBSCRIPTION ID&gt;):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ echo \"subscription_id=&lt;SUBSCRIPTION ID&gt;\" &gt;&gt; ~\/.azure\/credentials <\/pre>\n\n\n\n<p>In the Subscription, add roles to container-admin:<br>Access control (IAM) -&gt; Role assignments -&gt; <\/p>\n\n\n\n<p>Add (Add role assignment) -&gt; Role: DNS Zone Contributor -&gt; Assign access to: Azure AD user, group, or service principal -&gt; Select: container-admin -&gt; Save<\/p>\n\n\n\n<p>Add (Add role assignment) -&gt; Role: Network Contributor -&gt; Assign access to: Azure AD user, group, or service principal -&gt; Select: container-admin -&gt; Save<\/p>\n\n\n\n<p>Add (Add role assignment) -&gt; Role: Virtual Machine Contributor -&gt; Assign access to: Azure AD user, group, or service principal -&gt; Select: container-admin -&gt; Save<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Create a work folder and change in to it:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ mkdir -p terraform\/azure\/myweb\/scripts terraform\/azure\/myweb\/rbac &amp;&amp; cd terraform\/azure\/myweb<\/pre>\n\n\n\n<p>Create a custom Role Based Access for Resource Groups so container-admin can Read, Write and Delete Resource Groups in the subscription (replace &lt;SUBSCRIPTION ID&gt;):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; rbac\/rg-custom.jsn\n&gt; {\n&gt;    \"Name\": \"Resource Group Allowance\",\n&gt;    \"IsCustom\": true,\n&gt;    \"Description\": \"Can read, write and delete Resource Groups.\",\n&gt;    \"Actions\": [\n&gt;       \"Microsoft.Resources\/subscriptions\/resourceGroups\/read\",\n&gt;       \"Microsoft.Resources\/subscriptions\/resourceGroups\/write\",\n&gt;       \"Microsoft.Resources\/subscriptions\/resourceGroups\/delete\"\n&gt;    ],\n&gt;    \"NotActions\": [],\n&gt;    \"AssignableScopes\": [\n&gt;       \"\/subscriptions\/&lt;SUBSCRIPTION ID&gt;\"\n&gt;    ]\n&gt; }\n&gt; EOF<\/pre>\n\n\n\n<p>Authenticate to Azure using the CLI with the same Administrative credentials you use in the UI (a browser window will popup requesting credentials):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ az login<\/pre>\n\n\n\n<p>Create the Role Definition:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ az role definition create --role-definition rbac\/rg-custom.jsn<\/pre>\n\n\n\n<p>Add the role to container-admin:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ 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=)<\/pre>\n\n\n\n<p>Generate an SSH Key Pair (no password) and restrict permissions on it:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ ssh-keygen -q -t rsa -b 2048 -N '' -f ~\/.ssh\/myweb &amp;&amp; chmod 400 ~\/.ssh\/myweb<\/pre>\n\n\n\n<p>Ensure the terraform version is greater then or equal to 0.12:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; versions.tf\n&gt; terraform {\n&gt;   required_version = \"&gt;= 0.12\"\n&gt; }\n&gt; EOF<\/pre>\n\n\n\n<p>Set the version for the AzureRM Provider to greater then or equal to 1.44:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; provider.tf\n&gt; provider \"azurerm\" {\n&gt;   version = \"&gt;= 1.44\"\n&gt; }\n&gt; EOF<\/pre>\n\n\n\n<p>Set the default region and prefix variable:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; vars.tf\n&gt; variable \"region\" {\n&gt;   default = \"EastUS\"\n&gt; }\n&gt;\n&gt; variable \"prefix\" {\n&gt;   default = \"myweb\"\n&gt; }\n&gt; EOF<\/pre>\n\n\n\n<p>The following is performed with this script\/code:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>create a resource group where all of our resources will be put in (within East US)<\/li><li>create an Azure DNS Zone of myweb.com (no A records will be created)<\/li><li>create a virtual network of 10.0.0.0\/16<\/li><li>add a subnet of 10.0.1.0\/24 within the VNET<\/li><li>allocate a static Public IP<\/li><li>create a Network Security Group and add a Security rule for allowing SSH (port 22) Inbound<\/li><li>create a Network Interface with a Dynamic Private IP<\/li><li>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)<\/li><li>tag all resources<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; vm.tf\n&gt; # Create a Resource Group\n&gt; resource \"azurerm_resource_group\" \"myweb\" {\n&gt;   name     = \"${var.prefix}-rg\"\n&gt;   location = var.region\n&gt;\n&gt;   tags = {\n&gt;         Site = \"${var.prefix}.com\"\n&gt;     }\n&gt; }\n&gt;\n&gt; # Create a DNS Zone\n&gt; resource \"azurerm_dns_zone\" \"myweb\" {\n&gt;   name                = \"${var.prefix}.com\"\n&gt;   resource_group_name = azurerm_resource_group.myweb.name\n&gt;\n&gt;   tags = {\n&gt;         Site = \"${var.prefix}.com\"\n&gt;     }\n&gt; }\n&gt;\n&gt; # Create a Virtual Network\n&gt; resource \"azurerm_virtual_network\" \"myweb\" {\n&gt;   name                = \"${var.prefix}-net\"\n&gt;   address_space       = [\"10.0.0.0\/16\"]\n&gt;   location            = azurerm_resource_group.myweb.location\n&gt;   resource_group_name = azurerm_resource_group.myweb.name\n&gt;\n&gt;   tags = {\n&gt;         Site = \"${var.prefix}.com\"\n&gt;     }\n&gt; }\n&gt;\n&gt; # Add a Subnet\n&gt; resource \"azurerm_subnet\" \"internal\" {\n&gt;   name                 = \"internal\"\n&gt;   resource_group_name  = azurerm_resource_group.myweb.name\n&gt;   virtual_network_name = azurerm_virtual_network.myweb.name\n&gt;   address_prefix       = \"10.0.1.0\/24\"\n&gt; }\n&gt;\n&gt; # Allocate a Static Public IP\n&gt; resource \"azurerm_public_ip\" \"external\" {\n&gt;   name                = \"external\"\n&gt;   location            = var.region\n&gt;   resource_group_name = azurerm_resource_group.myweb.name\n&gt;   allocation_method   = \"Static\"\n&gt;\n&gt;   tags = {\n&gt;         Site = \"${var.prefix}.com\"\n&gt;     }\n&gt; }\n&gt;\n&gt; # Create a Network Security Group and allow inbound port(s)\n&gt; resource \"azurerm_network_security_group\" \"myweb\" {\n&gt;   name                = \"${var.prefix}-nsg\"\n&gt;   location            = var.region\n&gt;   resource_group_name = azurerm_resource_group.myweb.name\n&gt;\n&gt;   security_rule {\n&gt;         name                       = \"SSH\"\n&gt;         priority                   = 1001\n&gt;         direction                  = \"Inbound\"\n&gt;         access                     = \"Allow\"\n&gt;         protocol                   = \"Tcp\"\n&gt;         source_port_range          = \"<em>*\"<\/em>\n&gt;         destination_port_range     = \"22\"\n&gt;         source_address_prefix      = \"*\"\n&gt;         destination_address_prefix = \"*\"\n&gt;     }\n&gt;\n&gt;   tags = {\n&gt;         Site = \"${var.prefix}.com\"\n&gt;     }\n&gt; }\n&gt;\n&gt; # Create a Network Interface with a Dynamic Private IP\n&gt; resource \"azurerm_network_interface\" \"myweb\" {\n&gt;   name                      = \"${var.prefix}-nic\"\n&gt;   location                  = azurerm_resource_group.myweb.location\n&gt;   resource_group_name       = azurerm_resource_group.myweb.name\n&gt;   network_security_group_id = azurerm_network_security_group.myweb.id\n&gt;\n&gt;   ip_configuration {\n&gt;        name                          = \"${var.prefix}-nic_conf\"\n&gt;        subnet_id                     = azurerm_subnet.internal.id\n&gt;        private_ip_address_allocation = \"Dynamic\"\n&gt;        public_ip_address_id          = azurerm_public_ip.external.id\n&gt;     }\n&gt;\n&gt;   tags = {\n&gt;         Site = \"${var.prefix}.com\"\n&gt;     }\n&gt; }\n&gt;\n&gt; # Create an Ubuntu Virtual Machine with key based access and run a script on boot; use a Standard SSD\n&gt; resource \"azurerm_virtual_machine\" \"myweb\" {\n&gt;   name                  = \"${var.prefix}-vm\"\n&gt;   location              = azurerm_resource_group.myweb.location\n&gt;   resource_group_name   = azurerm_resource_group.myweb.name\n&gt;   network_interface_ids = [azurerm_network_interface.myweb.id]\n&gt;   vm_size               = \"Basic_A1\"\n&gt;\n&gt;   storage_image_reference {\n&gt;       publisher = \"Canonical\"\n&gt;       offer     = \"UbuntuServer\"\n&gt;       sku       = \"18.04-LTS\"\n&gt;       version   = \"latest\"\n&gt;     }\n&gt;\n&gt;   storage_os_disk {\n&gt;       name              = \"${var.prefix}-disk\"\n&gt;       caching           = \"ReadWrite\"\n&gt;       create_option     = \"FromImage\"\n&gt;       managed_disk_type = \"StandardSSD_LRS\"\n&gt;     }\n&gt;\n&gt;   os_profile {\n&gt;       computer_name  = var.prefix\n&gt;       admin_username = \"ubuntu\"\n&gt;       custom_data    = data.template_file.init_script.rendered\n&gt;     }\n&gt;\n&gt;   os_profile_linux_config {\n&gt;         disable_password_authentication = true\n&gt;         ssh_keys {\n&gt;             path     = \"\/home\/ubuntu\/.ssh\/authorized_keys\"\n&gt;             key_data = file(\"~\/.ssh\/${var.prefix}.pub\")\n&gt;           }\n&gt;     }\n&gt;\n&gt;   tags = {\n&gt;         Site = \"${var.prefix}.com\"\n&gt;     }\n&gt; }\n&gt; EOF<\/pre>\n\n\n\n<p>Output our allocated static Public IP after creation:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; output.tf\n&gt; output \"static_public_ip\" {\n&gt;   value = azurerm_public_ip.external.ip_address\n&gt; }\n&gt; EOF<\/pre>\n\n\n\n<p>Create a template file to reference a boot initialization script:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; install.tf\n&gt; data \"template_file\" \"init_script\" {\n&gt;   template = \"${file(\"scripts\/install.sh\")}\"\n&gt; }\n&gt; EOF<\/pre>\n\n\n\n<p>Create the shell script for custom_data:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; scripts\/install.sh\n&gt; #!\/bin\/bash\n&gt;\n&gt; MY_HOME=\"\/home\/ubuntu\"\n&gt; export DEBIAN_FRONTEND=noninteractive\n&gt;\n&gt; # Install prereqs\n&gt; apt update\n&gt; apt install -y python3-pip apt-transport-https ca-certificates curl software-properties-common\n&gt; # Install docker\n&gt; curl -fsSL https:\/\/download.docker.com\/linux\/ubuntu\/gpg | apt-key add -\n&gt; add-apt-repository \"deb [arch=amd64] https:\/\/download.docker.com\/linux\/ubuntu $(lsb_release -cs) stable\"\n&gt; apt update\n&gt; apt install -y docker-ce\n&gt; # Install docker-compose\n&gt; su ubuntu -c \"mkdir -p $MY_HOME\/.local\/bin\" \n&gt; su ubuntu -c \"pip3 install docker-compose --upgrade --user &amp;&amp; chmod 754 $MY_HOME\/.local\/bin\/docker-compose\"\n&gt; usermod -aG docker ubuntu\n&gt; # Add PATH\n&gt; printf \"\\nexport PATH=\\$PATH:$MY_HOME\/.local\/bin\\n\" &gt;&gt; $MY_HOME\/.bashrc\n&gt;\n&gt; exit 0\n&gt; EOF<\/pre>\n\n\n\n<p>Initialize the directory:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ terraform init<\/pre>\n\n\n\n<p>Run a dry-run to see what will occur:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ terraform-az-sp plan<\/pre>\n\n\n\n<p>Provision:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ terraform-az-sp apply -auto-approve<\/pre>\n\n\n\n<p>Log on to the instance after a short while:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ ssh -i ~\/.ssh\/myweb ubuntu@&lt;The value of static_public_ip that was reported.  One can also use 'terraform-az-sp output static_public_ip' to print it again.&gt;<\/pre>\n\n\n\n<p>Type yes and hit enter to accept.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>On the host (a short while is needed for the boot initialization script to complete):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ docker --version\n$ docker-compose --version\n$ logout<\/pre>\n\n\n\n<p>Tear down what was created by first performing a dry-run to see what will occur:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ terraform-az-sp plan -destroy <\/pre>\n\n\n\n<p>Tear down the instance:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ terraform-az-sp destroy -auto-approve<\/pre>\n\n\n\n<p>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:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ az group delete -n NetworkWatcherRG --yes<\/pre>\n\n\n\n<p>Logout of the Azure CLI session:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ az logout<\/pre>\n\n\n\n<p>&lt;&#8211;<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>References:<\/p>\n\n\n\n<p>Source: <br><a href=\"https:\/\/github.com\/pershoot\/terraform_azure_myweb\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"terraform_azure_myweb (opens in a new tab)\">terraform_azure_myweb<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 &#8216;myweb&#8217; as an example in this article, using the same base [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-5771","post","type-post","status-publish","format-standard","hentry","category-devops"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/posts\/5771","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/comments?post=5771"}],"version-history":[{"count":60,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/posts\/5771\/revisions"}],"predecessor-version":[{"id":6211,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/posts\/5771\/revisions\/6211"}],"wp:attachment":[{"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/media?parent=5771"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/categories?post=5771"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/tags?post=5771"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}