{"id":2134,"date":"2016-09-13T19:46:29","date_gmt":"2016-09-13T19:46:29","guid":{"rendered":"https:\/\/www.microsoft.com\/reallifecode\/index.php\/2016\/09\/13\/deploying-a-database-as-a-service-with-graph-story-ansible\/"},"modified":"2020-03-15T07:01:08","modified_gmt":"2020-03-15T14:01:08","slug":"deploying-a-database-as-a-service-with-graph-story-ansible","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/deploying-a-database-as-a-service-with-graph-story-ansible\/","title":{"rendered":"Deploying a DBaaS to Azure with Graph Story and Ansible"},"content":{"rendered":"<h1 id=\"who-is-graph-story\">Who is Graph Story?<\/h2>\n<p>Graph Story is a startup based out of Memphis, Tennessee that offers fully-managed <a href=\"http:\/\/neo4j.com\/\">Neo4j<\/a> databases as a service. Neo4j is one of the most popular graph database implementations, providing rich features to support machine learning and intelligence through relationships. Rather than users hosting and maintaining a Neo4j database themselves, they can leverage the Graph Story service and avoid the hassle of database management.<\/p>\n<h1 id=\"enabling-azure-support-for-graph-story\">Enabling Azure Support for Graph Story<\/h2>\n<p><a href=\"http:\/\/knomos.ca\">Knomos<\/a> is a startup that is part of the <a href=\"https:\/\/www.microsoftventures.com\">Microsoft Accelerator<\/a> winter 2016 class. They wanted to use Graph Story instead of hosting their own Neo4j server. However, they wanted their Graph Story managed database to run within Azure.<\/p>\n<p>When we first worked with Graph Story, their database service had a variety of cloud vendors, but their Azure support was lacking. Graph Story, Knomos and Microsoft got together for one week in Seattle, Washington, to enable Graph Story\u2019s database-as-a-service (DBaaS) on Azure.<\/p>\n<p>Our goal was to make Graph Story\u2019s automated database deployments to Azure consistent with the way they deploy to Digital Ocean, AWS and Google Compute Engine. Graph Story leverages <a href=\"https:\/\/www.ansible.com\/\">Ansible<\/a>, an application provisioning and deployment platform. With Ansible, DevOps engineers can provision and deploy apps similarly across different cloud platforms. In addition to working with Graph Story and Knomos, we also contributed to the Ansible project to enable the scenarios required by Graph Story.<\/p>\n<h1 id=\"graph-story-deployment-strategy\">Graph Story Deployment Strategy<\/h2>\n<h2 id=\"custom-image-creation--deployment\">Custom Image Creation &amp; Deployment<\/h2>\n<p>Graph Story has a custom virtual machine image they use for their database nodes. In order for the image to work globally, they need to deploy each image to every Azure region they intend on supporting. The graphic below depicts how this works:<\/p>\n<p> <img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch.png\" alt=\"Image graphstoryarch\" width=\"1675\" height=\"649\" class=\"aligncenter size-full wp-image-11101\" srcset=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch.png 1675w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch-300x116.png 300w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch-1024x397.png 1024w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch-768x298.png 768w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch-1536x595.png 1536w\" sizes=\"(max-width: 1675px) 100vw, 1675px\" \/><\/p>\n<p>First a <a href=\"http:\/\/php.net\/manual\/en\/book.gearman.php\">Gearman<\/a> worker picks up the request for a new image to be created. The Ansible server is triggered via SSH to execute an <a href=\"http:\/\/docs.ansible.com\/ansible\/playbooks.html\">Ansible playbook<\/a> which uses 3 modules:<\/p>\n<ul>\n<li>Azure-Deploy: Deploys Azure templates defined by YAML within the playbook. In our case, it\u2019s to provision the Target VM.<\/li>\n<li>Azure-Image-Capture: A module which captures an Azure VM image and stores the image as a blob.<\/li>\n<li>Azure-Blob-Copy: Copies a blob from one storage account to another. For Graph Story\u2019s scenario, it\u2019s called iteratively to copy the captured image to a list of storage accounts in different Azure regions.<\/li>\n<\/ul>\n<h2 id=\"automated-graph-story-database-instances\">Automated Graph Story Database Instances<\/h2>\n<p>When a customer purchases a database on GraphStory.com, the Graph Story web server sends a message to a Gearman worker. This triggers another Ansible playbook to provision a Graph Story instance onto Azure:<\/p>\n<p> <img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch2.png\" alt=\"Image graphstoryarch2\" width=\"1280\" height=\"638\" class=\"aligncenter size-full wp-image-11102\" srcset=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch2.png 1280w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch2-300x150.png 300w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch2-1024x510.png 1024w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2016\/09\/graphstoryarch2-768x383.png 768w\" sizes=\"(max-width: 1280px) 100vw, 1280px\" \/><\/p>\n<p>This is a similar architecture to the image deployment process; however, since the image already exists in the region targeted by the user, the deployment is much faster, providing a better customer experience.<\/p>\n<h1 id=\"ansible-modules\">Ansible Modules<\/h2>\n<p>We also collaborated with Ansible maintainers to publish the modules used by Graph Story for any Ansible user to capture and deploy Azure virtual machines using playbooks.<\/p>\n<h2 id=\"how-to-deploy-an-azure-virtual-machine-using-ansible\">How to Deploy an Azure Virtual Machine using Ansible<\/h2>\n<p>Using the <a href=\"https:\/\/github.com\/ansible\/ansible-modules-extras\/pull\/1839\">azure-deploy<\/a> Ansible module, you can launch any sort of <a href=\"https:\/\/azure.microsoft.com\/en-us\/documentation\/articles\/resource-group-overview\/\">Azure Resource Template<\/a> directly from your playbook YAML. Resource templates allow you to define a desired-state of your cloud infrastructure with a single deployment command. Typically, these templates are in JSON but with this module, they can be written in YAML. Here\u2019s an example of deploying a virtual machine with the necessary storage and virtual network creation:<\/p>\n<div class=\"language-yaml highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"pi\">-<\/span> <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">Capture a VM image and copy it to other storage accounts<\/span>\r\n  <span class=\"s\">hosts<\/span><span class=\"pi\">:<\/span> <span class=\"s\">127.0.0.1<\/span>\r\n  <span class=\"s\">connection<\/span><span class=\"pi\">:<\/span> <span class=\"s\">local<\/span>\r\n  <span class=\"s\">remote_user<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n  <span class=\"s\">tasks<\/span><span class=\"pi\">:<\/span>\r\n    <span class=\"pi\">-<\/span> <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">Create Azure VM<\/span>\r\n      <span class=\"s\">local_action<\/span><span class=\"pi\">:<\/span>\r\n        <span class=\"s\">module<\/span><span class=\"pi\">:<\/span> <span class=\"s\">azure_deploy<\/span>\r\n        <span class=\"s\">state<\/span><span class=\"pi\">:<\/span> <span class=\"s\">present<\/span>\r\n        <span class=\"s\">subscription_id<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n        <span class=\"s\">client_id<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n        <span class=\"s\">tenant_or_domain<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n        <span class=\"s\">client_secret<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span> <span class=\"c1\"># don't check in!<\/span>\r\n        <span class=\"s\">resource_group_name<\/span><span class=\"pi\">:<\/span> <span class=\"s1\">'<\/span><span class=\"s\">'<\/span>\r\n        <span class=\"s\">template<\/span><span class=\"pi\">:<\/span>\r\n          <span class=\"s\">$schema<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">https:\/\/schema.management.azure.com\/schemas\/2015-01-01\/deploymentTemplate.json#\"<\/span>\r\n          <span class=\"s\">contentVersion<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">1.0.0.0\"<\/span>\r\n          <span class=\"s\">parameters<\/span><span class=\"pi\">:<\/span>\r\n            <span class=\"s\">adminUsername<\/span><span class=\"pi\">:<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">string\"<\/span>\r\n              <span class=\"s\">metadata<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"s\">description<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">User<\/span> <span class=\"s\">name<\/span> <span class=\"s\">for<\/span> <span class=\"s\">the<\/span> <span class=\"s\">Virtual<\/span> <span class=\"s\">Machine.\"<\/span>\r\n            <span class=\"s\">dnsNameForPublicIP<\/span><span class=\"pi\">:<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">string\"<\/span>\r\n              <span class=\"s\">metadata<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"s\">description<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Unique<\/span> <span class=\"s\">DNS<\/span> <span class=\"s\">Name<\/span> <span class=\"s\">for<\/span> <span class=\"s\">the<\/span> <span class=\"s\">Public<\/span> <span class=\"s\">IP<\/span> <span class=\"s\">used<\/span> <span class=\"s\">to<\/span> <span class=\"s\">access<\/span> <span class=\"s\">the<\/span> <span class=\"s\">Virtual<\/span> <span class=\"s\">Machine.\"<\/span>\r\n            <span class=\"s\">ubuntuOSVersion<\/span><span class=\"pi\">:<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">string\"<\/span>\r\n              <span class=\"s\">defaultValue<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">14.04.2-LTS\"<\/span>\r\n              <span class=\"s\">allowedValues<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"pi\">-<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">12.04.5-LTS\"<\/span>\r\n                <span class=\"pi\">-<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">14.04.2-LTS\"<\/span>\r\n                <span class=\"pi\">-<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">15.04\"<\/span>\r\n              <span class=\"s\">metadata<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"s\">description<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">The<\/span> <span class=\"s\">Ubuntu<\/span> <span class=\"s\">version<\/span> <span class=\"s\">for<\/span> <span class=\"s\">the<\/span> <span class=\"s\">VM.<\/span> <span class=\"s\">This<\/span> <span class=\"s\">will<\/span> <span class=\"s\">pick<\/span> <span class=\"s\">a<\/span> <span class=\"s\">fully<\/span> <span class=\"s\">patched<\/span> <span class=\"s\">image<\/span> <span class=\"s\">of<\/span> <span class=\"s\">this<\/span> <span class=\"s\">given<\/span> <span class=\"s\">Ubuntu<\/span> <span class=\"s\">version.<\/span> <span class=\"s\">Allowed<\/span> <span class=\"s\">values:<\/span> <span class=\"s\">12.04.5-LTS,<\/span> <span class=\"s\">14.04.2-LTS,<\/span> <span class=\"s\">15.04.\"<\/span>\r\n          <span class=\"s\">variables<\/span><span class=\"pi\">:<\/span>\r\n            <span class=\"s\">newStorageAccountName<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[uniqueString(resourceGroup().id)]\"<\/span>\r\n            <span class=\"s\">location<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">West<\/span> <span class=\"s\">US\"<\/span>\r\n            <span class=\"s\">imagePublisher<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Canonical\"<\/span>\r\n            <span class=\"s\">imageOffer<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">UbuntuServer\"<\/span>\r\n            <span class=\"s\">OSDiskName<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">osdiskforlinuxsimple\"<\/span>\r\n            <span class=\"s\">nicName<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">myVMNic\"<\/span>\r\n            <span class=\"s\">addressPrefix<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">10.0.0.0\/16\"<\/span>\r\n            <span class=\"s\">subnetName<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Subnet\"<\/span>\r\n            <span class=\"s\">subnetPrefix<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">10.0.0.0\/24\"<\/span>\r\n            <span class=\"s\">storageAccountType<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Standard_LRS\"<\/span>\r\n            <span class=\"s\">publicIPAddressName<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">myPublicIP\"<\/span>\r\n            <span class=\"s\">publicIPAddressType<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Dynamic\"<\/span>\r\n            <span class=\"s\">vmStorageAccountContainerName<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">vhds\"<\/span>\r\n            <span class=\"s\">vmName<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n            <span class=\"s\">vmSize<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Standard_D1\"<\/span>\r\n            <span class=\"s\">virtualNetworkName<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">MyVNET\"<\/span>\r\n            <span class=\"s\">vnetID<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[resourceId('Microsoft.Network\/virtualNetworks',variables('virtualNetworkName'))]\"<\/span>\r\n            <span class=\"s\">subnetRef<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[concat(variables('vnetID'),'\/subnets\/',variables('subnetName'))]\"<\/span>\r\n            <span class=\"s\">sshKeyPath<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[concat('\/home\/',parameters('adminUsername'),'\/.ssh\/authorized_keys')]\"<\/span>\r\n          <span class=\"s\">resources<\/span><span class=\"pi\">:<\/span>\r\n            <span class=\"pi\">-<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Microsoft.Storage\/storageAccounts\"<\/span>\r\n              <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[uniqueString(resourceGroup().id)]\"<\/span>\r\n              <span class=\"s\">apiVersion<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">2015-05-01-preview\"<\/span>\r\n              <span class=\"s\">location<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('location')]\"<\/span>\r\n              <span class=\"s\">properties<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"s\">accountType<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('storageAccountType')]\"<\/span>\r\n            <span class=\"pi\">-<\/span>\r\n              <span class=\"s\">apiVersion<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">2015-05-01-preview\"<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Microsoft.Network\/publicIPAddresses\"<\/span>\r\n              <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('publicIPAddressName')]\"<\/span>\r\n              <span class=\"s\">location<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('location')]\"<\/span>\r\n              <span class=\"s\">properties<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"s\">publicIPAllocationMethod<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('publicIPAddressType')]\"<\/span>\r\n                <span class=\"s\">dnsSettings<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"s\">domainNameLabel<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[parameters('dnsNameForPublicIP')]\"<\/span>\r\n            <span class=\"pi\">-<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Microsoft.Network\/virtualNetworks\"<\/span>\r\n              <span class=\"s\">apiVersion<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">2015-05-01-preview\"<\/span>\r\n              <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('virtualNetworkName')]\"<\/span>\r\n              <span class=\"s\">location<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('location')]\"<\/span>\r\n              <span class=\"s\">properties<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"s\">addressSpace<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"s\">addressPrefixes<\/span><span class=\"pi\">:<\/span>\r\n                    <span class=\"pi\">-<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('addressPrefix')]\"<\/span>\r\n                <span class=\"s\">subnets<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"pi\">-<\/span>\r\n                    <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('subnetName')]\"<\/span>\r\n                    <span class=\"s\">properties<\/span><span class=\"pi\">:<\/span>\r\n                      <span class=\"s\">addressPrefix<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('subnetPrefix')]\"<\/span>\r\n            <span class=\"pi\">-<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Microsoft.Network\/networkInterfaces\"<\/span>\r\n              <span class=\"s\">apiVersion<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">2015-05-01-preview\"<\/span>\r\n              <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('nicName')]\"<\/span>\r\n              <span class=\"s\">location<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('location')]\"<\/span>\r\n              <span class=\"s\">dependsOn<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"pi\">-<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[concat('Microsoft.Network\/publicIPAddresses\/',<\/span> <span class=\"s\">variables('publicIPAddressName'))]\"<\/span>\r\n                <span class=\"pi\">-<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[concat('Microsoft.Network\/virtualNetworks\/',<\/span> <span class=\"s\">variables('virtualNetworkName'))]\"<\/span>\r\n              <span class=\"s\">properties<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"s\">ipConfigurations<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"pi\">-<\/span>\r\n                    <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">ipconfig1\"<\/span>\r\n                    <span class=\"s\">properties<\/span><span class=\"pi\">:<\/span>\r\n                      <span class=\"s\">privateIPAllocationMethod<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Dynamic\"<\/span>\r\n                      <span class=\"s\">publicIPAddress<\/span><span class=\"pi\">:<\/span>\r\n                        <span class=\"s\">id<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[resourceId('Microsoft.Network\/publicIPAddresses',variables('publicIPAddressName'))]\"<\/span>\r\n                      <span class=\"s\">subnet<\/span><span class=\"pi\">:<\/span>\r\n                        <span class=\"s\">id<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('subnetRef')]\"<\/span>\r\n            <span class=\"pi\">-<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">Microsoft.Compute\/virtualMachines\"<\/span>\r\n              <span class=\"s\">apiVersion<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">2015-06-15\"<\/span>\r\n              <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('vmName')]\"<\/span>\r\n              <span class=\"s\">location<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('location')]\"<\/span>\r\n              <span class=\"s\">dependsOn<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"pi\">-<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[concat('Microsoft.Storage\/storageAccounts\/',<\/span> <span class=\"s\">variables('newStorageAccountName'))]\"<\/span>\r\n                <span class=\"pi\">-<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[concat('Microsoft.Network\/networkInterfaces\/',<\/span> <span class=\"s\">variables('nicName'))]\"<\/span>\r\n              <span class=\"s\">properties<\/span><span class=\"pi\">:<\/span>\r\n                <span class=\"s\">hardwareProfile<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"s\">vmSize<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('vmSize')]\"<\/span>\r\n                <span class=\"s\">osProfile<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"s\">computername<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('vmName')]\"<\/span>\r\n                  <span class=\"s\">adminUsername<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[parameters('adminUsername')]\"<\/span>\r\n                  <span class=\"s\">adminPassword<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">notused\"<\/span>\r\n                  <span class=\"s\">linuxConfiguration<\/span><span class=\"pi\">:<\/span>\r\n                    <span class=\"s\">disablePasswordAuthentication<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">true\"<\/span>\r\n                    <span class=\"s\">ssh<\/span><span class=\"pi\">:<\/span> \r\n                        <span class=\"s\">publicKeys<\/span><span class=\"pi\">:<\/span> \r\n                          <span class=\"pi\">-<\/span> \r\n                            <span class=\"s\">path<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('sshKeyPath')]\"<\/span>\r\n                            <span class=\"s\">keyData<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n                <span class=\"s\">storageProfile<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"s\">imageReference<\/span><span class=\"pi\">:<\/span>\r\n                    <span class=\"s\">publisher<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('imagePublisher')]\"<\/span>\r\n                    <span class=\"s\">offer<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[variables('imageOffer')]\"<\/span>\r\n                    <span class=\"s\">sku<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[parameters('ubuntuOSVersion')]\"<\/span>\r\n                    <span class=\"s\">version<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">latest\"<\/span>\r\n                  <span class=\"s\">osDisk<\/span><span class=\"pi\">:<\/span>\r\n                    <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">osdisk\"<\/span>\r\n                    <span class=\"s\">vhd<\/span><span class=\"pi\">:<\/span>\r\n                      <span class=\"s\">uri<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[concat('http:\/\/',variables('newStorageAccountName'),'.blob.core.windows.net\/',variables('vmStorageAccountContainerName'),'\/',variables('OSDiskName'),'.vhd')]\"<\/span>\r\n                    <span class=\"s\">caching<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">ReadWrite\"<\/span>\r\n                    <span class=\"s\">createOption<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">FromImage\"<\/span>\r\n                <span class=\"s\">networkProfile<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"s\">networkInterfaces<\/span><span class=\"pi\">:<\/span>\r\n                    <span class=\"pi\">-<\/span>\r\n                      <span class=\"s\">id<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[resourceId('Microsoft.Network\/networkInterfaces',variables('nicName'))]\"<\/span>\r\n                <span class=\"s\">diagnosticsProfile<\/span><span class=\"pi\">:<\/span>\r\n                  <span class=\"s\">bootDiagnostics<\/span><span class=\"pi\">:<\/span>\r\n                    <span class=\"s\">enabled<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">true\"<\/span>\r\n                    <span class=\"s\">storageUri<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[concat('http:\/\/',variables('newStorageAccountName'),'.blob.core.windows.net')]\"<\/span>\r\n          <span class=\"s\">outputs<\/span><span class=\"pi\">:<\/span>\r\n            <span class=\"s\">storage_key<\/span><span class=\"pi\">:<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">object\"<\/span>\r\n              <span class=\"s\">value<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[listKeys(resourceId('Microsoft.Storage\/storageAccounts',<\/span> <span class=\"s\">variables('newStorageAccountName')),<\/span> <span class=\"s\">'2015-05-01-preview')]\"<\/span>\r\n            <span class=\"s\">storage_name<\/span><span class=\"pi\">:<\/span>\r\n              <span class=\"s\">type<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">string\"<\/span>\r\n              <span class=\"s\">value<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">[uniqueString(resourceGroup().id)]\"<\/span>\r\n        <span class=\"s\">parameters<\/span><span class=\"pi\">:<\/span>\r\n          <span class=\"s\">adminUsername<\/span><span class=\"pi\">:<\/span>\r\n            <span class=\"s\">value<\/span><span class=\"pi\">:<\/span> <span class=\"s1\">'<\/span><span class=\"s\">'<\/span>\r\n          <span class=\"s\">dnsNameForPublicIP<\/span><span class=\"pi\">:<\/span>\r\n            <span class=\"s\">value<\/span><span class=\"pi\">:<\/span> <span class=\"s1\">'<\/span><span class=\"s\">'<\/span>\r\n      <span class=\"s\">register<\/span><span class=\"pi\">:<\/span> <span class=\"s\">azure<\/span>\r\n<\/code><\/pre>\n<\/div>\n<p>At the end of this module <code class=\"highlighter-rouge\">register azure<\/code> will register the output of the deployment as a variable for use later in the playbook. In the example above, the <code class=\"highlighter-rouge\">outputs<\/code> section of the template is returned, allowing us to extract useful information such as the storage account name and key.<\/p>\n<p>Next, we\u2019ll use the information from the Azure deployment module to add the newly created Azure virtual machine to our Ansible <a href=\"http:\/\/docs.ansible.com\/ansible\/intro_dynamic_inventory.html\">dynamic inventory<\/a>.<\/p>\n<div class=\"language-yaml highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"pi\">-<\/span> <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">Add new instance to host group<\/span>\r\n      <span class=\"s\">add_host<\/span><span class=\"pi\">:<\/span> <span class=\"s\">hostname= groupname=launched ansible_user= vm_name=<\/span>\r\n      <span class=\"s\">with_items<\/span><span class=\"pi\">:<\/span> <span class=\"s\">azure.instances<\/span>\r\n<\/code><\/pre>\n<\/div>\n<p><code class=\"highlighter-rouge\">azure.instances<\/code> is a collection of Virtual Machine instances and this Ansible play will be called for each instance (using the <code class=\"highlighter-rouge\">with-items<\/code> property), adding a new host for each iteration. Now you can provision or do anything else to this host as you normally would in Ansible, assuming that your SSH private key for the machine is accessible. We\u2019ll add the new virtual machine to the <code class=\"highlighter-rouge\">launched<\/code> inventory group for later use.<\/p>\n<p>Graph Story runs their existing playbook steps used on all cloud providers after this step.<\/p>\n<h2 id=\"how-to-capture-an-azure-virtual-machine-image\">How to Capture an Azure Virtual Machine Image<\/h2>\n<p>Using the <a href=\"https:\/\/github.com\/ansible\/ansible-modules-extras\/pull\/1845\">azure-image-capture<\/a> module, capturing a virtual machine image becomes simple. First, you need to be sure to de-provision the user information generated by Azure within the target VM in order for it to be ready to become a \u2018generic\u2019 virtual machine. This ensures the image can be reused for creating any VM regardless of the local user settings. You can do this by running <code class=\"highlighter-rouge\">sudo waagent -deprovision+user<\/code> which is a command to the Azure Linux agent:<\/p>\n<div class=\"language-yaml highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"pi\">-<\/span> <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">Run Deprovision user Command<\/span>\r\n  <span class=\"s\">hosts<\/span><span class=\"pi\">:<\/span> <span class=\"s\">launched<\/span>\r\n  <span class=\"s\">remote_user<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n  <span class=\"s\">tasks<\/span><span class=\"pi\">:<\/span>\r\n    <span class=\"pi\">-<\/span> <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">Deprovision User<\/span>\r\n      <span class=\"s\">command<\/span><span class=\"pi\">:<\/span> <span class=\"s\">sudo waagent -deprovision+user -force<\/span>\r\n<\/code><\/pre>\n<\/div>\n<p>Now the target virtual machine is ready to be captured:<\/p>\n<div class=\"highlighter-rouge\">\n<pre class=\"highlight\"><code>- name: Capture Image\r\n  hosts: 127.0.0.1\r\n  connection: local\r\n  tasks:\r\n    - name: Capture\r\n      local_action:\r\n        module: azure_image_capture \r\n        subscription_id: \"\"\r\n        resource_group_name: ''\r\n        destination_container: copiedvhds\r\n        client_id: \"\"\r\n        tenant_id: \"\"\r\n        client_secret: \"\"\r\n        vm_name: \"\"\r\n        wait: \"\"\r\n      register: capture_info\r\n<\/code><\/pre>\n<\/div>\n<p>After this step, the VM image has been captured, and a link to the image defined by <code class=\"highlighter-rouge\">capture_info.vhd_uri<\/code> is returned. You can use this image directly to create a new VM, if it resides in the data center region you require. If you\u2019re like Graph Story, you\u2019ll need to copy the image to various region in order to use the image within regions other than where the generic VM was created.<\/p>\n<p>We wrote the <a href=\"https:\/\/github.com\/ansible\/ansible-modules-extras\/pull\/1844\">azure-copy-blob<\/a> module specifically for this purpose:<\/p>\n<div class=\"language-yaml highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"pi\">-<\/span> <span class=\"s\">name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">Copy Blob<\/span>\r\n    <span class=\"s\">local_action<\/span><span class=\"pi\">:<\/span>\r\n      <span class=\"s\">module<\/span><span class=\"pi\">:<\/span> <span class=\"s\">azure_copy_blob<\/span>\r\n      <span class=\"s\">source_uri<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n      <span class=\"s\">source_key<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n      <span class=\"s\">destination_account<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n      <span class=\"s\">destination_container<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n      <span class=\"s\">destination_key<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n      <span class=\"s\">destination_blob<\/span><span class=\"pi\">:<\/span> <span class=\"s2\">\"<\/span><span class=\"s\">\"<\/span>\r\n    <span class=\"s\">with_items<\/span><span class=\"pi\">:<\/span> \r\n<\/code><\/pre>\n<\/div>\n<p>In the snippet above, we can use the output of the azure-image-capture module to copy the image from the source storage account to the destination. We can iterate using the <code class=\"highlighter-rouge\">with_items<\/code> property by providing a collection of <code class=\"highlighter-rouge\">storage_accounts<\/code> like this:<\/p>\n<div class=\"language-yaml highlighter-rouge\">\n<pre class=\"highlight\"><code><span class=\"pi\">-<\/span> <span class=\"s\">vars<\/span><span class=\"pi\">:<\/span>\r\n    <span class=\"s\">storage_accounts<\/span><span class=\"pi\">:<\/span>\r\n      <span class=\"pi\">-<\/span>\r\n        <span class=\"s\">account_name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">graphstory4<\/span>\r\n        <span class=\"s\">key<\/span><span class=\"pi\">:<\/span> <span class=\"s\">STORAGE_ACCOUNT_KEY<\/span>\r\n        <span class=\"s\">container<\/span><span class=\"pi\">:<\/span> <span class=\"s\">storedimages<\/span>\r\n        <span class=\"s\">blob<\/span><span class=\"pi\">:<\/span> <span class=\"s\">graphstoryneo4j2.vhd<\/span>\r\n      <span class=\"pi\">-<\/span>\r\n        <span class=\"s\">account_name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">graphstory5<\/span>\r\n        <span class=\"s\">key<\/span><span class=\"pi\">:<\/span> <span class=\"s\">STORAGE_ACCOUNT_KEY<\/span>\r\n        <span class=\"s\">container<\/span><span class=\"pi\">:<\/span> <span class=\"s\">storedimages<\/span>\r\n        <span class=\"s\">blob<\/span><span class=\"pi\">:<\/span> <span class=\"s\">graphstoryneo4j2.vhd<\/span>\r\n      <span class=\"pi\">-<\/span>\r\n        <span class=\"s\">account_name<\/span><span class=\"pi\">:<\/span> <span class=\"s\">graphstory6<\/span>\r\n        <span class=\"s\">key<\/span><span class=\"pi\">:<\/span> <span class=\"s\">STORAGE_ACCOUNT_KEY<\/span>\r\n        <span class=\"s\">container<\/span><span class=\"pi\">:<\/span> <span class=\"s\">storedimages<\/span>\r\n        <span class=\"s\">blob<\/span><span class=\"pi\">:<\/span> <span class=\"s\">graphstoryneo4j2.vhd<\/span>\r\n<\/code><\/pre>\n<\/div>\n<p>Graph Story uses this approach to deploy and update their Graph Story images across Azure. Checkout the <a href=\"https:\/\/github.com\/ansible\/ansible-modules-extras\">Ansible Modules<\/a> repository, where you can find these modules plus a great variety of others to use in your next automation project.<\/p>\n<p>To get started with Graph Story, checkout <a href=\"http:\/\/graphstory.com\/pricing\">their pricing page<\/a> where you can select a Neo4j database which fits your needs.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How we worked with Graph Story to enable their Neo4j Database-as-a-Service (DBaaS) deployments on Microsoft Azure using Ansible.<\/p>\n","protected":false},"author":21366,"featured_media":11100,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[16],"tags":[44,60,189,227,270],"class_list":["post-2134","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-ansible","tag-azure","tag-graph-story","tag-knomos","tag-neo4j"],"acf":[],"blog_post_summary":"<p>How we worked with Graph Story to enable their Neo4j Database-as-a-Service (DBaaS) deployments on Microsoft Azure using Ansible.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/2134","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/21366"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=2134"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/2134\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/11100"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=2134"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=2134"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=2134"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}