{"id":5820,"date":"2020-02-23T06:34:30","date_gmt":"2020-02-23T11:34:30","guid":{"rendered":"\/db-blog\/?p=5820"},"modified":"2020-03-12T01:02:04","modified_gmt":"2020-03-12T05:02:04","slug":"azure-ansible-provision-a-virtual-machine-instance-using-infrastructure-as-code","status":"publish","type":"post","link":"https:\/\/droidbasement.com\/db-blog\/azure-ansible-provision-a-virtual-machine-instance-using-infrastructure-as-code\/","title":{"rendered":"Azure\/Ansible \u2013 Provision a Virtual Machine instance using Infrastructure as Code"},"content":{"rendered":"\n<p><em>Note: This article has been duplicated from the <a href=\"https:\/\/droidbasement.com\/db-blog\/azure-terraform-provision-a-virtual-machine-instance-using-infrastructure-as-code\/\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\">previous article<\/a> which uses Terraform and has been modified for Ansible.<\/em> <\/p>\n\n\n\n<p>In this article we will use <a rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\" href=\"https:\/\/www.ansible.com\/\" target=\"_blank\">Ansible<\/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>Update PIP:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ python3 -m pip install --upgrade --user pip<\/pre>\n\n\n\n<p> If there was an update,  then forget remembered location references in the shell environment:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ hash -r pip <\/pre>\n\n\n\n<p>Install\/Upgrade Ansible:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ pip3 install ansible --upgrade --user &amp;&amp; chmod 754 ~\/.local\/bin\/ansible ~\/.local\/bin\/ansible-playbook<\/pre>\n\n\n\n<p>Install the Ansible Azure modules (this may take a while):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ pip3 install 'ansible[azure]' --upgrade --user<\/pre>\n\n\n\n<p>Modify the profile and the key\/variable strings in the previously created Azure credentials file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ sed -i 's\/\\[container-admin\/\\[default\/; s\/application_id\/client_id\/; s\/client_secret\/secret\/; s\/directory_id\/tenant\/' ~\/.azure\/credentials<\/pre>\n\n\n\n<p><em>Note: The above change will break the previously created terraform-az-sp function.  If you are also using Terraform, then please do this <\/em> (user&#8217;s startup)<em>:<\/em><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ sed -i 's:application_id\/arm_:client_id\/arm_:; s:client_secret\/arm_:secret\/arm_:; s:directory_id\/arm_:tenant\/arm_:' ~\/.bashrc<\/pre>\n\n\n\n<p>Remove the subscription_id and modify the keys\/variables in our previously created az-login-sp function (user&#8217;s startup).<\/p>\n\n\n\n<p>If you have not gone through the Azure\/Terraform article:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ sed -i \"s:\\$HOME\/.azure\/credentials | xargs):\\$HOME\/.azure\/credentials | sed '\/subscription_id\/d; s\/client_id\/application_id\/; s\/secret\/client_secret\/; s\/tenant\/directory_id\/' | xargs):\" ~\/.bashrc<\/pre>\n\n\n\n<p>If you have gone through the Azure\/Terraform article:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ sed -i \"s:\\$HOME\/.azure\/credentials | sed '\/subscription_id\/d':\\$HOME\/.azure\/credentials | sed '\/subscription_id\/d; s\/client_id\/application_id\/; s\/secret\/client_secret\/; s\/tenant\/directory_id\/':\" ~\/.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<p>Note: This can be omitted if you have gone through the Azure\/Terraform article:<\/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 ansible\/azure\/myweb\/scripts ansible\/azure\/myweb\/rbac &amp;&amp; cd ansible\/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 client_id ~\/.azure\/credentials | cut -f2 -d=) --subscription $(grep subscription_id ~\/.azure\/credentials | cut -f2 -d=)<\/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>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>Create a hosts file and specify localhost:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ cat &lt;&lt; 'EOF' &gt; hosts\n&gt; [local]\n&gt; localhost\n&gt; EOF<\/pre>\n\n\n\n<p>The following is performed with this Playbook:<\/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.yml\n&gt; # Create an Azure Virtual Machine instance and add a way to destroy it\n&gt; ---\n&gt; - hosts: local\n&gt;   connection: local\n&gt;\n&gt;   vars:\n&gt;     region: EastUS\n&gt;     prefix: myweb\n&gt;     subnet_name: internal\n&gt;     public_ip_name: external\n&gt;\n&gt;   tasks:\n&gt;   - name: Create a resource group\n&gt;     azure_rm_resourcegroup:\n&gt;       name: \"{{ prefix }}-rg\"\n&gt;       location: \"{{ region }}\"\n&gt;       state: present\n&gt;       tags:\n&gt;           Site: \"{{ prefix }}.com\"\n&gt;\n&gt;   - name: Create a DNS Zone\n&gt;     azure_rm_dnszone:\n&gt;       name: \"{{ prefix }}.com\"\n&gt;       resource_group: \"{{ prefix }}-rg\"\n&gt;       state: present\n&gt;       tags:\n&gt;           Site: \"{{ prefix }}.com\"\n&gt;\n&gt;   - name: Create a Virtual Network\n&gt;     azure_rm_virtualnetwork:\n&gt;       name: \"{{ prefix }}-net\"\n&gt;       address_prefixes: \"10.0.0.0\/16\"\n&gt;       location: \"{{ region }}\"\n&gt;       resource_group: \"{{ prefix }}-rg\"\n&gt;       state: present\n&gt;       tags:\n&gt;           Site: \"{{ prefix }}.com\"\n&gt;\n&gt;   - name: Add a Subnet\n&gt;     azure_rm_subnet:\n&gt;       name: \"{{ subnet_name }}\"\n&gt;       resource_group: \"{{ prefix }}-rg\"\n&gt;       virtual_network: \"{{ prefix }}-net\"\n&gt;       address_prefix: \"10.0.1.0\/24\"\n&gt;       state: present\n&gt;\n&gt;   - name: Allocate a Static Public IP\n&gt;     azure_rm_publicipaddress:\n&gt;       name: \"{{ public_ip_name }}\"\n&gt;       location: \"{{ region }}\"\n&gt;       resource_group: \"{{ prefix }}-rg\"\n&gt;       allocation_method: Static\n&gt;       state: present\n&gt;       tags:\n&gt;           Site: \"{{ prefix }}.com\"\n&gt;     register: static_public_ip\n&gt;\n&gt;   - name: Create a Network Security Group and allow inbound port(s)\n&gt;     azure_rm_securitygroup:\n&gt;       name: \"{{ prefix }}-nsg\"\n&gt;       location: \"{{ region }}\"\n&gt;       resource_group: \"{{ prefix }}-rg\"\n&gt;       rules:\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;       state: present\n&gt;       tags:\n&gt;           Site: \"{{ prefix }}.com\"\n&gt; \n&gt;   - name: Create a Network Interface with a Dynamic Private IP\n&gt;     azure_rm_networkinterface:\n&gt;       name: \"{{ prefix }}-nic\"\n&gt;       location: \"{{ region }}\"\n&gt;       resource_group: \"{{ prefix }}-rg\"\n&gt;       security_group: \"{{ prefix }}-nsg\"\n&gt;       virtual_network: \"{{ prefix }}-net\"\n&gt;       subnet_name: \"{{ subnet_name }}\"\n&gt;       ip_configurations:\n&gt;         - name: \"{{ prefix }}-nic_conf\"\n&gt;           private_ip_allocation_method: Dynamic\n&gt;           public_ip_address_name: \"{{ public_ip_name }}\"\n&gt;           primary: True\n&gt;       state: present\n&gt;       tags:\n&gt;           Site: \"{{ prefix }}.com\"\n&gt;\n&gt;   - name: Create an Ubuntu Virtual Machine with key based access and run a script on boot; use a Standard SSD\n&gt;     azure_rm_virtualmachine:\n&gt;       name: \"{{ prefix }}-vm\"\n&gt;       location: \"{{ region }}\"\n&gt;       resource_group: \"{{ prefix }}-rg\"\n&gt;       network_interfaces: \"{{ prefix }}-nic\"\n&gt;       vm_size: Basic_A1\n?       image:\n&gt;         publisher: Canonical\n&gt;         offer: UbuntuServer\n&gt;         sku: '18.04-LTS'\n&gt;         version: latest\n&gt;       os_type: Linux\n&gt;       os_disk_name: \"{{ prefix }}-disk\"\n&gt;       os_disk_caching: ReadWrite\n&gt;       managed_disk_type: StandardSSD_LRS\n&gt;       short_hostname: \"{{ prefix }}\"\n&gt;       admin_username: ubuntu\n&gt;       custom_data: \"{{ lookup('file', '.\/scripts\/install.sh') }}\"\n&gt;       ssh_password_enabled: false\n&gt;       ssh_public_keys:\n&gt;             - path: \/home\/ubuntu\/.ssh\/authorized_keys\n&gt;               key_data: \"{{ lookup('file', '~\/.ssh\/{{ prefix }}.pub') }}\"\n&gt;       state: present\n&gt;       tags:\n&gt;           'Site': \"{{ prefix }}.com\"\n&gt;\n&gt;   - debug: msg=\"Public (static) IP is {{ static_public_ip.state.ip_address }} for {{ azure_vm.name }}\"\n&gt;     when: static_public_ip.state.ip_address is defined\n&gt;\n&gt;   - debug: msg=\"Run this playbook for {{ azure_vm.name }} shortly to list the Public (static) IP.\"\n&gt;     when: static_public_ip.state.ip_address is not defined\n&gt;\n&gt;   - name: Destroy a Resource Group and all resources that fall under it\n&gt;     azure_rm_resourcegroup:\n&gt;       name: \"{{ prefix }}-rg\"\n&gt;       force_delete_nonempty: yes\n&gt;       state: absent\n&gt;     tags: [ 'never', 'destroy' ]\n&gt;\n&gt;   - name: Destroy the Network Watcher Resource Group and all resources that fall under it\n&gt;     azure_rm_resourcegroup:\n&gt;       name: \"NetworkWatcherRG\"\n&gt;       force_delete_nonempty: yes\n&gt;       state: absent\n&gt;     tags: [ 'never', 'destroy_networkwatcher' ]\n&gt; EOF<\/pre>\n\n\n\n<p><\/p>\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' > scripts\/install.sh\n> #!\/bin\/bash\n>\n> MY_HOME=\"\/home\/ubuntu\"\n> export DEBIAN_FRONTEND=noninteractive\n>\n> # Install prereqs\n> apt update\n> apt install -y python3-pip apt-transport-https ca-certificates curl software-properties-common\n> # Install docker\n> curl -fsSL https:\/\/download.docker.com\/linux\/ubuntu\/gpg | apt-key add -\n> add-apt-repository \"deb [arch=amd64] https:\/\/download.docker.com\/linux\/ubuntu $(lsb_release -cs) stable\"\n> apt update\n> apt install -y docker-ce\n> # Install docker-compose\n> su ubuntu -c \"mkdir -p $MY_HOME\/.local\/bin\"\n> su ubuntu -c \"pip3 install docker-compose --upgrade --user &amp;&amp; chmod 754 $MY_HOME\/.local\/bin\/docker-compose\"\n> usermod -aG docker ubuntu\n> # Add PATH\n> printf \"\\nexport PATH=\\$PATH:$MY_HOME\/.local\/bin\\n\" >> $MY_HOME\/.bashrc\n>\n> exit 0\n> EOF<\/pre>\n\n\n\n<p>Run the playbook:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ ansible-playbook -i hosts vm.yml<\/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 re-run the playbook 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 the instance:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$ ansible-playbook -i hosts vm.yml --tags \"destroy\"<\/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\">$  ansible-playbook -i hosts vm.yml --tags \"destroy_networkwatcher\" <\/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\/ansible_myweb\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"ansible_myweb (opens in a new tab)\">ansible_myweb<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Note: This article has been duplicated from the previous article which uses Terraform and has been modified for Ansible. In this article we will use Ansible (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 [&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-5820","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\/5820","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=5820"}],"version-history":[{"count":63,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/posts\/5820\/revisions"}],"predecessor-version":[{"id":6226,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/posts\/5820\/revisions\/6226"}],"wp:attachment":[{"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/media?parent=5820"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/categories?post=5820"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/droidbasement.com\/db-blog\/wp-json\/wp\/v2\/tags?post=5820"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}