3

vRealize SaltStack Config App Deployment - VMware Cloud Management

 3 years ago
source link: https://blogs.vmware.com/management/2021/04/deploying-apps-with-vrealize-automation-and-vrealize-saltstack-config.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
vRealize SaltStack Config App Deployment

In this blog post I’m going to show how you can use vRealize Automation and vRealize SaltStack Config together to deliver automated application deployment. Both of these products are individually powerful but when you combine them together it provides you with some fantastic functionality.

There are several approaches that you can follow to achieve automated application deployment (including orchestration and custom events). For this article I’m going to show you how to do it by leveraging Salt top files combined with Salt grains.

Our Example Distributed Application

Before I dive into Salt top files lets take a look at an example application that I will use to illustrate this method.

This is a 2 tier application that was originally used in the VMware Hands-on-Labs a few years back. I’ve given it a re-work for this article.

It is made up of database and application components which simulates a corporate phone directory. The database content is read via a Python script hosted using Apache on the database server. The application server provides a user web interface that talks to the database server. This is also using a Python script. The application server talks to the database via IP address (more on this later).

It’s a fairly simple distributed application but it allows me to showcase functionality perfectly.

Top Files

A Salt top file sits in the root of a Salt environment and must be named ‘top.sls’. You can have multiple environments, each with their own top file but only one top file per environment.

The top file is not created by default. You won’t find it in your environment unless you have created it before. It maps groups of machines to specific configurations that should be applied to them. The method you use to group machines together can be based on things like minion ID (glob matching), grain values, lists, subnet membership etc.

Here’s my top file sitting at the root of my ‘Example’ environment.

The top file is evaluated by a Salt Minion every time a ‘highstate’ is executed against it. When a Minion picks up a ‘highstate’ event on the event bus or it executes a ‘highstate’ itself, it downloads the top file. The Minion then searches the top file to find formulas that match itself. When it finds a match it applies the Salt states that are listed under the relevant formula.

Top File Configuration

Now you know what a Salt top file is lets talk about how to assign and group machines using it.

I want to mark each component server deployed as having a specific role, in this case an application or database role. This is so I can configure my top file to tell Salt to apply states if a specific role has been identified. I could do this using two different hostname conventions (one per role) but a better way would be to use a Salt grain. I can use a grain to tag each component server deployed with a role value. The top file can then be defined with formulas that use the same grain and values.

Here’s an extract from my top file that relates to the 2-tier application. I have defined 2 configurations within my ‘Example’ Salt environment. Each one matches against the same Salt grain (‘serverRole’) but with different values. The state file to apply for each configuration is also specified which I will cover next.

2-Tier App State Files

I have placed the state files for the 2-tier application in a single directory within my ‘Example’ environment. To keep things simple I have also put any required configuration files in the same place. These files contain scripts that will power the application (e.g. fetching data from the database when requested).

Database Deployment

The ‘database.sls’ state file deploys all the required software packages, script files and service configuration for the database component server. I also use it to seed the database with some data.

# Install packages and configuration for 2tier-app database role

installDbRolePackages: pkg.installed: - pkgs: - python - httpd

startApache: service.running: - name: httpd - enable: True

createDatabaseDirectory: file.directory: - name: /var/www/db - user: apache - group: apache - dir_mode: 755 - file_mode: 755 - recurse: - user - group - mode directory.db: sqlite3.table_present: - db: /var/www/db/directory.db - schema: CREATE TABLE 'directory' ("PhoneNumber" INTEGER, "FirstName" VARCHAR(30), "Surname" VARCHAR(25), "Department" VARCHAR(20)) seedDatabase: module.run: - name: sqlite3.modify - db: /var/www/db/directory.db - sql: "INSERT INTO 'directory' VALUES (441536222333,'John','Adams','Billing'), (441536444654,'Sarah','Williams','Sales')" - require: - sqlite3: directory.db databaseAppFile: file.managed: - name: /var/www/cgi-bin/database.py - source: salt://2tier-app/dbAppScript.py - user: apache - group: apache - mode: 755

stopFirewall: service.dead: - name: firewalld - enable: False

The Python script (‘dbAppScript.py’) is deployed by ‘database.sls’ onto the database component server. It is placed into the Apache ‘cgi-bin’ folder making it accessible remotely by the application component server. This script retrieves data from the database with query options.

#! /usr/bin/python import cgi import sqlite3

conn=sqlite3.connect('/var/www/db/directory.db') curs=conn.cursor() print "Content-type:text/plain\n\n"; form = cgi.FieldStorage() querystring = form.getvalue("querystring") if querystring != None: queryval = "%" + querystring + "%" select = "SELECT * FROM directory WHERE name LIKE '" + queryval + "'" else: select = "SELECT * FROM directory" for row in curs.execute(select): if len(row) == 4: for item in row: print item,'|' print "#" conn.close()

That takes care of the database side of things.

Application Deployment

The application component server is very similar, also deploying a Python script file (‘app.py’). It does have a key difference however. I need to make sure the Python script is configured with the IP address of the database component server. The end goal is for vRealize Automation to be able to deploy multiple instances of this distributed application fully automated. This means the IP address of the database component server could be anything. It cannot be statically written in the script file that is stored in the Salt environment. I’m going to use Salt templating with jinja and Salt grains to achieve this.

Here’s the application component server state file (‘app.sls’). The deployment of the ‘app.py’ Python script uses the ‘template: jinja’ option. This is so I can use dynamic expressions in the script file that represent the database component server IP address.

# Install packages and configuration for 2tier-app app server role

installAppRolePackages: pkg.installed: - pkgs: - python - httpd - epel-release - python-pip - policycoreutils-python

startApache: service.running: - name: httpd - enable: True setSELinuxBoolean: selinux.boolean: - name: httpd_can_network_connect - value: True - persist: True installPipFromCmd: cmd.run: - name: pip install requests installAppScript: file.managed: - name: /var/www/cgi-bin/app.py - source: salt://2tier-app/appScript.py - user: apache - group: apache - mode: 755 - template: jinja stopFirewall: service.dead: - name: firewalld - enable: False

The ‘app.py’ script file contains the html code to display data from the database and the remote call to the database component server to run the data query.

#!/usr/bin/python import os, sys, cgi import requests

print "Content-type:text/html\n\n"; print "<head><title>Company Phone Directory</title></head>\n" print "<body>\n" print "<h1>Directory Lookup</h1>\n" remote = os.getenv("REMOTE_ADDR") form = cgi.FieldStorage() querystring = form.getvalue("querystring") print "Accessed via:",remote,"\n<p>" if querystring != None: url = 'http://{{ grains['databaseServer'] }}/cgi-bin/database.py?querystring=' + querystring else: url = 'http://{{ grains['databaseServer'] }}/cgi-bin/database.py' querystring = "" r = requests.get(url) print '<form action="/cgi-bin/app.py">' print ' Name Filter (blank for all records):' print ' <input type="text" name="querystring" value="'+querystring+'">' print ' <input type="submit" value="Apply">' print '</form>' print "\n<table border=1 bordercolor=black cellpadding=5 cellspacing=0>" print "\n<th>Phone Number</th><th>First Name</th><th>Surname</th><th>Department</th>"

#deal with the data coming across the wire a = r.text.split("|\n#") for row in a: if len(row) != 1: print "<tr>" splitrow = row.split("|") for item in splitrow: if item != None: print "<td>",item,"</td>" print "</tr>\n" print "</body></html>\n"

The two lines in the if/else section have jinja expressions rather than static values. The expressions mean that Salt will replace each one with the value of the ‘databaseServer’ grain as the file is processed.

if querystring != None: url = 'http://{{ grains['databaseServer'] }}/cgi-bin/database.py?querystring=' + querystring else: url = 'http://{{ grains['databaseServer'] }}/cgi-bin/database.py'

The application component server needs to have this grain set with the value of the database IP address for this to work.

vRealize Automation Cloud Template

Up to this point I have just been talking vRealize SaltStack Config and Salt. Now it’s time we spend some time looking at things from a different perspective. I have a top file, state files and Python scripts but I need to create something that deploys the component servers and instructs Salt to process the top file. This is where I can leverage vRealize Automation.

My basic cloud template in vRealize Automation uses two components, one for each role. For simplicity the load balancer is not included. Cloud-init downloads and deploys the Salt Minion from saltstack.com for each component server. A Salt reactor on the Salt Master automatically accepts new Salt Minion keys.

There are two additional steps also performed by cloud-init. The first is to create a Salt grain on the Minion called ‘serverRole’ and set its value. This will enable the top file formula to be matched. The second is to tell Salt to execute a ‘highstate’ triggering the processing of the top file.

In this excerpt from the application component I am also adding the database component IP address as another Salt grain. This allows the jinja template expression in ‘app.py’ to be replaced with the database IP address.

cloudConfig: | #cloud-config preserve-hostname: false hostname: ${self.resourceName}.corp.local runcmd: - curl -L https://bootstrap.saltstack.com -o install_salt.sh - sudo sh install_salt.sh -A ${propgroup.SaltStackConfiguration.masterAddress} - sudo salt-call grains.set serverRole app - sudo salt-call grains.set databaseServer ${resource.database_Server_1.networks.address[0]} - sudo salt-call state.highstate

Deploying my cloud template produces two component machines. Accessing the application by targeting the ‘app.py’ script displays my rudimentary web interface and initial data set from the database server.

The cloud template could then be expanded from here to include multiple application servers, pooled behind a load balancer and protected with micro-segmentation from NSX-T.

Next Steps

You have seen how vRealize SaltStack Config, Salt top files and vRealize Automation can combine to provide seamless application stack deployments. Now it’s your turn to try it out and see how it can enable your application deployments.
Make sure you visit the website for more product information.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK