2 – PaaS, AppServices WebApps and DBs

The second post in a series of 8 from my classroom hands on tech training. It will be about automatically deploying a PaaS WebApp and a database to Azure. It will be done completly with a script and I’ll provide a Powershell version and two versions for CLI – one for Windows and one for Mac. The WebApp is a super-tiny PHP+MySQL app I found on a tutorial site that I’ve slightly modified to avoid having to install the MySQL client on your client to complete the setup. The reason that there are two different versions of the CLI script is simply because I make use of environment variables and bash and the DOS command prompt have different syntax for that.

The Task

The task that the scripts carry out is in short:

  1. Deploy the WebApp
    1. Create an AppService plan in Azure
    2. Create a WebApp in the AppService plan
    3. Deploy an application from Github to the WebApp
  2. Create a MySQL database (PaaS)
  3. Update WebApp config settings with MySQL connection info

I deliberatly choose a PHP/MySQL webapp to highlight the fact that Azure isn’t just about Windows and .Net. I got bitten a bit by this choice since performing step 2 in Powershell was a bit of a mouthful, since the azure Powershell cmdlets at the time of writing was a little bit behind the CLI. Otherwise it’s straight forward.

Step 1: Deploy the WebApp in CLI

In Azure CLI for Mac, the steps to create the WebApp looks like the below

gitrepo=https://github.com/cljung/php-mysql-sample.git

webappname=$USER$RANDOM
rgname="$webappname-rg" 
webappplan="$webappname-plan"

az group create --location westeurope --name "$rgname"
az appservice plan create --name "$webappplan" --resource-group "$rgname" --sku FREE
az webapp create --name "$webappname" --resource-group "$rgname" --plan "$webappplan"
az webapp deployment source config --name "$webappname" --resource-group "$rgname" 
        --repo-url "$gitrepo" --branch master --manual-integration

Every Azure resource exists in a Resource Group, so the first thing we need to create is a resource group. I derive the name of the resource group from the logged in username together with random digits and a “-rg” prefix. This makes it unique, repeatable and easily identifiable.

The SKU of the AppService plan is set to FREE. There are only a limited number of free plans within a subscription, so if you get an error here, change it to S0 which is the cheapest you can get.

The “az webapp deployment” is the magic that tells the Azure WebApp to pull the code for the webapp from a Github repo. Once this command has run, you will have a webapp deployed (but no db). If you checkin a code change to the github repo, Azure WebApp will detect that and automatically pull the latest stuff from the source control. You don’t need to rerun the command.

Step 2: Create the MySQL database in CLI

Azure offers a managed MySQL database solution and to provision it you need to create a server (just a name – not a VM), create the database and set some firewall rules (optional).

The server name needs to be in lower case, so the first part is using the current logged in user and making sure that userid actually is in lower case. I also grab the public ip address I’m currently working from by making a web request to https://ipinfo.io/ip so I can use that value and limit the MySQL firewall to grant me network access but block the rest of the world. The other firewall setting that is made is to let the Azure WebApp access the MySQL database. That is the 0.0.0.0 nonsense.

# transform userid to lowercase since some Azure resource names don't like uppercase
userid=$(echo "$USER" | awk '{print tolower(S0)}')

SERVERNAME="$userid-mysqlsrv01"
DBAUID=dba01
DBAPWD="MySqlDb$RANDOM"
DBNAME=msgdb
_PIP=$(curl ipinfo.io/ip)   

az mysql server create -l westeurope -g "$rgname" -n "$SERVERNAME" 
          -u "$DBAUID" -p "$DBAPWD" --sku-name B_Gen4_2 
          --ssl-enforcement Disabled --storage-size 51200 --version "5.7"
az mysql db create -g "$rgname" -s "$SERVERNAME" -n "$DBNAME"

# allow Azure to access it from the inside
az mysql server firewall-rule create -g "$rgname" -s "$SERVERNAME" 
        -n AllowAllWindowsAzureIps --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0

# allow login from the PIP currently being used
az mysql server firewall-rule create -g "$rgname" -s "$SERVERNAME" 
        -n allowall --start-ip-address $_PIP --end-ip-address $_PIP

DBSRV=$(az mysql server show -g $rgname -n $SERVERNAME --query "fullyQualifiedDomainName" -o tsv)
echo "FQDN of MySQL server $DBSRV"

The last az command is to retrieve the FQDN of the MySQL database which we need in the next step to update the WebApp config

 

Step 3: Update the WebApp config in CLI

Azure AppServices has something called Application Settings, which is a key/value store on the Azure WebApp resource level that it applies in different formats. For a .Net based webapp, it overrides the Web.Config files AppSettings, for instance. It also sets the key/values as environment variables which Java, PHP and node.js based apps can pick up. We are going to set four AppSettings to the Azure WebApp

az webapp config appsettings set -g $rgname -n $webappname --settings "dbHost=$DBSRV"
az webapp config appsettings set -g $rgname -n $webappname --settings "dbName=$DBNAME"
az webapp config appsettings set -g $rgname -n $webappname --settings "dbUser=$DBAUID@$SERVERNAME"
az webapp config appsettings set -g $rgname -n $webappname --settings "dbPwd=$DBAPWD"

The names dbHost, dbName, etc, is something application dependant and has nothing to do with Azure.

Test drive the WebApp

When the script is finished, you can access the website via http://<username-random>.azurewebsites.net/. The MySQL table that the app is using isn’t created, so the first thing you need to do is to go to page http://<username-random>.azurewebsites.net/createdb.php to create it, then you can write messages.

I’ve added the FQDN server name of the MySQL database as a header so you can see that it’s actually picked that up.

Step 1: Deploy the WebApp in Powershell

The Powershell version of deploying the webappisn’t much different to begin with. Of course, Powershell looks a little bit different with it’s Verb-dash-what style of naming convention plus it is longer commands to type. It is pretty close still with four commands to carry out the task.

$gitrepo="https://github.com/cljung/php-mysql-sample.git"
$location="West Europe"
$userid=$env:USERNAME.tolower()
$webappname="$userid$(Get-Random)"
$rgname="$webappname-rg"
$webappplan="$webappname-plan"

New-AzureRmResourceGroup -Name "$rgname" -Location "$location"

New-AzureRmAppServicePlan -Name $webappplan -Location $location 
                         -ResourceGroupName $rgname -Tier Free
New-AzureRmWebApp -Name $webappname -Location $location -AppServicePlan $webappplan 
                         -ResourceGroupName $rgname

$PropertiesObject = @{repoUrl="$gitrepo"; branch="master"; isManualIntegration="true";}
Set-AzureRmResource -ResourceGroupName $rgname -ResourceName "$webappname/web" 
                         -ResourceType "Microsoft.Web/sites/sourcecontrols" 
                         -PropertyObject $PropertiesObject -ApiVersion "2015-08-01" -Force

The Set-AzureRmResource is the generic Swiss Army knife of Azure powershell commands. You basically can manipulate any resource you like by passing the appropriate set of properties to it and specify what ResourceType inside the resource we want to apply it to.

Step 2: Create the MySQL database in Powershell

At the time of writing, the Microsoft documentation for MySQL database says that there is no Powershell support for creating or managing MySQL. That is both true and false. There are no commands called New-AzureRmMySQLServer, etc, but that doesn’t mean that we can provision a MySQL database from powershell. After all, there must be a REST API available, because otherwise the CLI wouldn’t work, right?

The answer in a situation like this is to revert to using the Net-AzureRmResource and do it a bit more low level way. Step 2 in powershell therefor looks like this

$SERVERNAME="$userid-mysqlsrv01"
$DBAUID="dba01"
$DBAPWD="MySqlDb$(Get-Random)"
$DBNAME="msgdb"
# get our current public ip addr so we can set the NSG below to only allow access from that ip addr
$resp = Invoke-RestMethod "http://ipinfo.io"
$_PIP=$resp.ip

# register the ARM provider - one time operation
Register-AzureRmResourceProvider -ProviderNamespace "Microsoft.DBforMySql"
$apiVersion="2017-12-01"
                        
# create server
$json2='{"administratorLogin":"'+$DBAUID+'","administratorLoginPassword":"'+$DBAPWD+'",
          "storageProfile":{"storageMB":51200,"backupRetentionDays":7,
          "geoRedundantBackup":"Disabled"},"version":"5.7","sslEnforcement":"Disabled",
          "replicationRole":"None","primaryServerId":"","replicaCapacity":5}'
$prop2=($json2 | ConvertFrom-json)
$resSrv = New-AzureRmResource -Force -ResourceType "Microsoft.DBforMySQL/servers" 
                       -ResourceGroupName "$rgname" -ApiVersion $apiVersion -Location $location `
    -ResourceName "$SERVERNAME" -SkuObject @{name='B_Gen4_2'} -PropertyObject @prop2

# create db
$propDB = ('{"charset":"latin1","collation":"latin1_swedish_ci"}' | ConvertFrom-json)        
New-AzureRmResource -Force -ResourceType "Microsoft.DBforMySQL/servers/databases" 
    -ResourceGroupName "$rgname" -ApiVersion $apiVersion -Location $location `
    -ResourceName "$SERVERNAME/$DBNAME" -PropertyObject @propDB
        
# set MySQL firweall to allow your current public ip address
$propFW = ('{"startIpAddress":"'+$_PIP+'","endIpAddress":"'+$_PIP+'"}' | ConvertFrom-json)        
New-AzureRmResource -Force -ResourceType "Microsoft.DBforMySQL/servers/firewallRules" 
    -ResourceGroupName "$rgname" -ApiVersion $apiVersion -Location $location `
    -ResourceName "$SERVERNAME/AllowPip" -PropertyObject @propFW

# set MySQL firweall to allow internal Azure traffic
$propFW = ('{"startIpAddress":"0.0.0.0","endIpAddress":"0.0.0.0"}' | ConvertFrom-json)        
New-AzureRmResource -Force -ResourceType "Microsoft.DBforMySQL/servers/firewallRules" 
    -ResourceGroupName "$rgname" -ApiVersion $apiVersion -Location $location `
    -ResourceName "$SERVERNAME/AllowAllWindowsAzureIps" -PropertyObject @propFW

Yikes, you may say, and that isn’t completely wrong. The point here is not showing off but to prove to you that you still can succeed even there is no specific commands because powershell is very powerfull. How the h@ did I get all these JSON structure might be another thing you say. Well, that wasn’t so hard as it looks. I simply created the MySQL database with CLI, used Get-AzureRmResource to query out the resources and used the $resource | ConvertTo-Json  command to see how it looked in JSON.

Step 3: Update the WebApp config in Powershell

The final step is luckily not that complex, since all we need to do is to create a Dictionary object in powershell with the application settings and call the Set-AzureRmWebApp command

$newAppSettings = @{"dbHost"="$($resSrv.Properties.fullyQualifiedDomainName)";
                    "dbName"="$DBNAME";"dbUser"="$DBAUID@$SERVERNAME";"dbPwd"="$DBAPWD"}
Set-AzureRmWebApp -ResourceGroupName "$rgName" -Name "$webappname" -AppSettings $newAppSettings

Summary

This post is all about showing you that it is very easy to provision a classic webapp with it’s complementary database, even if it is a PHP and MySQL type of app. Running the script will have your app ready in minutes. Since its deployment model is bound to Github, that means that what ever changes you check in will be pushed to Azure.

References

You’ll find three scripts in my github repo https://github.com/cljung/aztechdays for this post

  • appservices-mysql-deploy-git.ps1  – the Powershell version
  • appservices-mysql-deploy-git-win.cmd – the CLI version for Windows Command Prompt
  • appservices-mysql-deploy-git-mac.sh – the CLI version for Mac/Linux

Note that the CLI/Windows version is written to be executed in the DOS command prompt. If you rather prefer to run it in the Powershell command prompt, you need to change the use of environment variables, ie from %USERNAME% to $env:USERNAME, etc

Other useful Microsoft documentation

https://docs.microsoft.com/en-us/azure/app-service/scripts/app-service-cli-deploy-github

https://docs.microsoft.com/en-us/azure/mysql/quickstart-create-mysql-server-database-using-azure-cli