{"id":3182,"date":"2017-05-09T23:06:02","date_gmt":"2017-05-09T23:06:02","guid":{"rendered":"https:\/\/www.microsoft.com\/reallifecode\/?p=3182"},"modified":"2020-03-15T05:21:16","modified_gmt":"2020-03-15T12:21:16","slug":"provision-configure-infrastructure-azure-using-saltstack","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/provision-configure-infrastructure-azure-using-saltstack\/","title":{"rendered":"Provision and Configure your Infrastructure on Azure using SaltStack"},"content":{"rendered":"<p>Recently we partnered with <a href=\"https:\/\/logdna.com\/\">LogDNA<\/a> to help migrate their existing infrastructure to Azure using <a href=\"https:\/\/github.com\/saltstack\/salt\">SaltStack<\/a> and the Azure Python SDK. LogDNA is based in the San Francisco area, and their product offerings include log data ingestion and analytics. The company has been leveraging SaltStack to provision and manage their infrastructure across AWS and DigitalOcean. In this post, we will use ElasticSearch as an example to illustrate the end-to-end process of how we provisioned and configured the deployment of an ElasticSearch cluster on Azure using SaltStack.<\/p>\n<p><a href=\"https:\/\/github.com\/saltstack\/salt\">SaltStack<\/a> is an open source project, supported by the company of the same name that delivers an <a href=\"http:\/\/www.saltstack.com\">enterprise version<\/a> of the project. SaltStack delivers infrastructure as code and configuration management with an\u00a0abstraction of the cloud provider selected. At the time of writing this post, support for Azure ARM by SaltStack is still in preview, and the documentation for Azure ARM support is still in development. We hope this post serves as both an example and guide.<\/p>\n<h2><strong>Overview<\/strong><\/h2>\n<p>The model of SaltStack is based on masters and minions, with each minion agent reaching back to the master; this approach makes the solution very scalable. The master holds the configuration of the minions in a set of configuration files. Those files provide an idempotent configuration that will be applied when the minion role is deployed or at any time the configuration is re-applied. In this scenario, we will be using SaltStack to install and configure an ElasticSearch cluster on Azure.<\/p>\n<h3>Installation<\/h3>\n<p>Clone this repo:<\/p>\n<pre class=\"lang:sh decode:true\">$ git clone https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch<\/pre>\n<p>Get Azure CLI 2.0 from <a href=\"https:\/\/docs.microsoft.com\/en-us\/cli\/azure\/install-azure-cli\">here<\/a> if you don&#8217;t already have it. Then log into the CLI:<\/p>\n<pre class=\"lang:sh decode:true\">$ az login\r\n$ az account show<\/pre>\n<p>Create a service principal following <a href=\"https:\/\/docs.microsoft.com\/en-us\/cli\/azure\/create-an-azure-service-principal-azure-cli\">these steps<\/a>.The output should look something like the following:<\/p>\n<pre class=\"lang:sh decode:true\">{\r\n\"appId\": \"59db508a-3429-4094-a828-e8b4680fc790\",\r\n\"displayName\": \"myserviceprincipalapp\",\r\n\"name\": \"https:\/\/myserviceprincipalapp.azurewebsites.net\",\r\n\"password\": {the password you supplied displayed here},\r\n\"tenant\": \"72f988bf-86f1-41af-91ab-2d7cd011db47\"\r\n}\r\n<\/pre>\n<p>Now you are ready to kick off the scripts. Use <em>appId<\/em> from the previous step for <em>servicePrincipalAppId<\/em>, and <em>password<\/em> from the previous step for <em>serviceprincipalsecret.<\/em>\u00a0Sit back and enjoy a cup of coffee. (This might take a while!) Once the script is done, you will have a working ElasticSearch cluster ready\u00a0for use.<\/p>\n<pre class=\"lang:default decode:true\">$ deploy-salt-cluster.sh -o create \r\n-u &lt;adminUsernameForVM&gt; \r\n-n &lt;namespaceForResourceGroup&gt; \r\n-c &lt;servicePrincipalAppId&gt; \r\n-s &lt;serviceprincipalsecret&gt; \r\n-t &lt;tenantid&gt;\r\n<\/pre>\n<p>When prompted, create\u00a0a password for the SaltStack cluster master node admin user. This credential\u00a0will be used by the scripts to complete installation and configuration on the master node. You can also modify this example to use an ssh key.<\/p>\n<p>After the <em>deploy-salt-cluster.sh<\/em> script is done, look for the IP address of the <em>minionesmaster<\/em> node from the <a href=\"https:\/\/portal.azure.com\/\">Azure portal<\/a>. This IP address is the public IP of the ElasticSearch master node. Now ElasticSearch is accessible via <em>http:\/\/{minionesmaster-public-ip}:9200<\/em>.<\/p>\n<p>You can use a tool like the\u00a0<a href=\"https:\/\/chrome.google.com\/webstore\/detail\/sense-beta\/lhjgkmllcaadmopgmanpapmpjgmfcfig\">Sense Chrome extension<\/a> to add new content to your search index and to conduct search queries.<\/p>\n<p><figure id=\"attachment_3186\" aria-labelledby=\"figcaption_attachment_3186\" class=\"wp-caption alignnone\" ><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2017\/05\/demo.gif\" alt=\"Image demo\" width=\"839\" height=\"583\" class=\"aligncenter size-full wp-image-11004\" \/><figcaption id=\"figcaption_attachment_3186\" class=\"wp-caption-text\">ElasticSearch Demo<\/figcaption><\/figure><\/p>\n<h2>What is the script doing?<\/h2>\n<p>Now let&#8217;s take a look under the hood. When we run <a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/deploy-salt-cluster.sh\">deploy-salt-cluster.sh<\/a>, we are actually doing two things:<\/p>\n<ol>\n<li>Deploying resources needed to provision a SaltStack cluster on Azure<\/li>\n<li>Creating and configuring an ElasticSearch cluster using SaltStack<\/li>\n<\/ol>\n<h3>Deploy Resources needed for a SaltStack Cluster<\/h3>\n<p>In <a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/deploy-salt-cluster.sh\">deploy-salt-cluster.sh<\/a>, we are using the Azure Service Principal created in the previous section to create an Azure Resource Group and all the resources needed to provision a SaltStack cluster using an <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/azure-resource-manager\/resource-group-authoring-templates\">Azure ARM template<\/a> and the <a href=\"https:\/\/docs.microsoft.com\/en-us\/cli\/azure\/install-azure-cli\">Azure CLI<\/a>.<\/p>\n<pre class=\"lang:sh decode:true\">$ az group deployment create \r\n-g $resourceGroupName -n $NamePrefix \r\n--template-uri $TEMPLURI --parameters \"$PARAMS\"<\/pre>\n<p>From the Azure ARM template <a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/azuredeploy.json\">azuredeploy.json<\/a>, we&#8217;ll provision the following resources on Azure for the SaltStack cluster:<\/p>\n<ul>\n<li>1 virtual machine for the SaltStack master node<\/li>\n<li>1 storage account to persist the data<\/li>\n<li>1 public IP for the SaltStack master<\/li>\n<li>1 network interface card for the SaltStack master<\/li>\n<li>1 network security group to allow SaltStack and ElasticSearch ports to be accessible<\/li>\n<li>1 virtual network to ensure all nodes within the SaltStack cluster can talk to each other<\/li>\n<li>1 custom script that runs after all resources are provisioned<\/li>\n<\/ul>\n<p><figure id=\"attachment_3191\" aria-labelledby=\"figcaption_attachment_3191\" class=\"wp-caption alignnone\" ><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2017\/05\/Screen-Shot-2017-04-27-at-5.32.16-PM.png\" alt=\"Image Screen Shot 2017 04 27 at 5 32 16 PM\" width=\"638\" height=\"263\" class=\"aligncenter size-full wp-image-11007\" srcset=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2017\/05\/Screen-Shot-2017-04-27-at-5.32.16-PM.png 638w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2017\/05\/Screen-Shot-2017-04-27-at-5.32.16-PM-300x124.png 300w\" sizes=\"(max-width: 638px) 100vw, 638px\" \/><figcaption id=\"figcaption_attachment_3191\" class=\"wp-caption-text\">Resources for SaltStack Cluster on Azure<\/figcaption><\/figure><\/p>\n<h3>Create the ElasticSearch cluster using SaltStack<\/h3>\n<p>Now that we have all the Azure resources created for our SaltStack cluster, the post-install script <a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/saltstackinstall.sh\">saltstackinstall.sh<\/a> at the end of the previous section will kick off the installation of the ElasticSearch cluster using the azurearm driver of salt-cloud.\u00a0<a href=\"https:\/\/docs.saltstack.com\/en\/latest\/ref\/cli\/salt-cloud.html\">Salt Cloud<\/a> is the system used to provision virtual machines on various public clouds via a cleanly controlled profile and mapping system.<\/p>\n<p>From\u00a0<a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/saltstackinstall.sh\">saltstackinstall.sh<\/a>, you will see the following cloud profile definition, which tells salt-cloud what resources to use to create a new virtual machine on Azure.<\/p>\n<pre class=\"lang:yaml decode:true\">azure-vm:\r\n  provider: azure\r\n  image: OpenLogic|CentOS|7.2n|7.2.20160629\r\n  size: Standard_DS2_v2\r\n  location: $location\r\n  ssh_username: $adminUsername\r\n  ssh_password: $adminPassword\r\n  storage_account: $storageName\r\n  resource_group: $resourceGroupname\r\n  security_group: $nsgname\r\n  network_resource_group: $resourceGroupname\r\n  network: $vnetName\r\n  subnet: $subnetName\r\n  public_ip: True\r\n  script: bootstrap-salt.sh\r\n  script_args: -U\r\n  sync_after_install: grains<\/pre>\n<p>The following cloud provider definition is for ElasticSearch minion nodes. It tells salt-cloud what resources to create for each of the ElasticSearch minion nodes. Note that its definition is extended from the <em>azure-wus1<\/em> cloud provider&#8217;s one in the previous section. Specifically, the cloud provider definition below tells salt-cloud to create a new VM of size <em>Standard_DS2_v2<\/em> in region<em> $location<\/em>, attach a new data disk of size <em>50GB<\/em> created in our Azure storage account <em>$storageName<\/em>, and set its role as\u00a0<em>elasticsearch,<\/em> and its cluster as\u00a0<em>es-cluster-local-01<\/em>.<\/p>\n<pre class=\"lang:yaml decode:true\">azure-vm-esnode:\r\n  extends: azure-vm\r\n  size: Standard_DS2_v2\r\n  volumes:\r\n    - {disk_size_gb: 50, name: 'datadisk1' }\r\n  minion:\r\n    grains:\r\n      region: $location\r\n      roles: elasticsearch\r\n      elasticsearch:\r\n        cluster: es-cluster-local-01<\/pre>\n<p>Next, we have the cloud provider definition for the ElasticSearch master node. It has almost the same configurations as the ElasticSearch minion nodes, except for its role, which is\u00a0<em>elasticsearchmaster<\/em>.<\/p>\n<pre class=\"lang:yaml decode:true\">azure-vm-esmaster:\r\n  extends: azure-vm\r\n  size: Standard_DS2_v2\r\n  volumes:\r\n    - {disk_size_gb: 50, name: 'datadisk1' }\r\n  minion:\r\n    grains:\r\n      region: $location\r\n      roles: elasticsearchmaster\r\n      elasticsearchmaster:\r\n        cluster: es-cluster-local-01<\/pre>\n<p>With the following commands in the\u00a0<a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/saltstackinstall.sh\">saltstackinstall.sh<\/a>\u00a0script, salt-cloud creates the ElasticSearch master node and the ElasticSearch minion nodes in Azure.<\/p>\n<pre class=\"lang:sh decode:true\">salt-cloud -p azure-wus1-esmaster \"${resourceGroupname}minionesmaster\"\r\nsalt-cloud -p azure-wus1-esnode \"${resourceGroupname}minionesnode\"<\/pre>\n<p><figure id=\"attachment_3193\" aria-labelledby=\"figcaption_attachment_3193\" class=\"wp-caption alignnone\" ><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/cse\/wp-content\/uploads\/sites\/55\/2017\/05\/Screen-Shot-2017-04-27-at-5.18.44-PM.png\" alt=\"Image Screen Shot 2017 04 27 at 5 18 44 PM\" width=\"643\" height=\"514\" class=\"aligncenter size-full wp-image-11006\" srcset=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2017\/05\/Screen-Shot-2017-04-27-at-5.18.44-PM.png 643w, https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2017\/05\/Screen-Shot-2017-04-27-at-5.18.44-PM-300x240.png 300w\" sizes=\"(max-width: 643px) 100vw, 643px\" \/><figcaption id=\"figcaption_attachment_3193\" class=\"wp-caption-text\">ElasticSearch Cluster on Azure<\/figcaption><\/figure><\/p>\n<p>From the previous image, salt-cloud created the following resources for the ElasticSearch cluster:<\/p>\n<ul>\n<li>1 virtual machine for the ElasticSearch master node<\/li>\n<li>1 public IP for the ElasticSearch master node<\/li>\n<li>1 network interface card for the ElasticSearch master<\/li>\n<li>1 virtual machine for the ElasticSearch minion node<\/li>\n<li>1 public IP for the ElasticSearch minion node<\/li>\n<li>1 network interface card for the ElasticSearch minion<\/li>\n<\/ul>\n<h3>Configure the ElasticSearch cluster using SaltStack<\/h3>\n<p>Now that we have all the Azure resources created for our ElasticSearch cluster, the post install script\u00a0<a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/saltstackinstall.sh\">saltstackinstall.sh<\/a>\u00a0will kick off the configuration of the ElasticSearch cluster. It will use SaltStack Configuration Management to run ElasticSearch on each node in the cluster.<\/p>\n<p>We also need to manage the group of machines in our cluster effectively. One way is to create roles for machines that perform similar tasks. In Salt, the file that contains the mapping between groups of machines on a network and the configuration roles that should be applied to them is called a <em>top file<\/em>.<\/p>\n<p>From\u00a0<a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/saltstackinstall.sh\">saltstackinstall.sh<\/a>, you will see the following <em>top.sls<\/em> top file added to<em> \/srv\/salt<\/em> of the Salt master node. The top file defines the roles of the machines in the cluster, in this case, <em>elasticsearch<\/em> and <em>elasticsearchmaster<\/em>, and it defines a set of state files to apply to the targeted machines based on the roles tagged. Specifically, we are going to apply the <em>&#8220;common_packages&#8221;<\/em> state file to all the machines here.<\/p>\n<pre class=\"lang:yaml decode:true\">base:\r\n  '*':\r\n    - common_packages\r\n  'roles:elasticsearch':\r\n    - match: grain\r\n    - elasticsearch\r\n  'roles:elasticsearchmaster':\r\n    - match: grain\r\n    - elasticsearchmaster<\/pre>\n<p>The core of the Salt State system is the SLS, or <strong>S<\/strong>a<strong>L<\/strong>t <strong>S<\/strong>tate file. The SLS is a representation of the state that a system should be in. From\u00a0<a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/saltstackinstall.sh\">saltstackinstall.sh<\/a>, we are adding the <em>common_packages.sls<\/em> state file to <em>\/srv\/salt<\/em> of the Salt master node. This state file tells SaltStack which packages to install on each target machine.<\/p>\n<pre class=\"lang:yaml decode:true\">common_packages:\r\n    pkg.installed:\r\n        - names:\r\n            - git\r\n            - tmux\r\n            - tree<\/pre>\n<p>The SLS files are laid out in a directory structure on the Salt master. An SLS file describes which files to download to the target machines. From\u00a0<a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\/blob\/master\/saltstackinstall.sh\">saltstackinstall.sh<\/a>, we are creating two directories in <em>\/srv<\/em>, <em>elasticsearch<\/em> and <em>elasticsearchmaster<\/em>, on the Salt master. Each directory contains the following files:<\/p>\n<ul>\n<li>init.sls state file\n<ul>\n<li>describes what files to copy to the target machine<\/li>\n<li>tells what packages to install on the target machine<\/li>\n<\/ul>\n<\/li>\n<li>elasticsearch.yml\u00a0helps ElasticSearch to discover and identify nodes in the ElasticSearch cluster<\/li>\n<\/ul>\n<p>With the following command in the script, Salt configures and applies all the states to the target nodes:<\/p>\n<pre class=\"lang:sh decode:true\">salt '*' state.highstate<\/pre>\n<p>Voil\u00e0! Now we have a working ElasticSearch cluster running on Azure configured by SaltStack.<\/p>\n<h2 id=\"opportunities-for-reuse\">Opportunities for Reuse<\/h2>\n<p>That\u2019s it! As we&#8217;ve just demonstrated, you can use SaltStack to create, configure, and manage infrastructure on Azure. Similarly, you can use SaltStack to create, configure, and manage applications or distributed systems.<\/p>\n<p>Our solution for <a href=\"https:\/\/github.com\/ritazh\/azure-saltstack-elasticsearch\">deploying\u00a0an ElasticSearch cluster on Azure using SaltStack<\/a>\u00a0is on GitHub, which can serve as an example of how to leverage SaltStack to manage infrastructure as code\u00a0for\u00a0your own application.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>SaltStack delivers infrastructure as code and configuration management with an abstraction of the cloud provider selected. We will illustrate the end-to-end process of how we provisioned and configured the deployment of an ElasticSearch cluster on Azure using SaltStack.<\/p>\n","protected":false},"author":21378,"featured_media":11005,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[16],"tags":[60,91,164,318],"class_list":["post-3182","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-azure","tag-azure-resource-manager-arm","tag-elasticsearch","tag-saltstack"],"acf":[],"blog_post_summary":"<p>SaltStack delivers infrastructure as code and configuration management with an abstraction of the cloud provider selected. We will illustrate the end-to-end process of how we provisioned and configured the deployment of an ElasticSearch cluster on Azure using SaltStack.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/3182","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\/21378"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=3182"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/3182\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/11005"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=3182"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=3182"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=3182"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}