Journey to Azure

Recently I achieved a certification in AWS that I have been working on for months, the AWS Solutions Architect – Professional. Of course there are specialty certifications that focus on specific areas in more details but for right now I feel complete in my AWS certification journey. While AWS is still the biggest public cloud, Microsoft Azure is not far behind. Now I learn best by doing and I also feel that is one of the greatest aspects of the public cloud is that its not too expensive to try things out for yourself in a personal lab. So this website and its technology stack was a primary way for me to learn about things in AWS so I figure to bolster my multi-cloud skills I should migrate over to Azure which I have since completed.

Obviously the migration aspect is a big learning experience, and also a big part of my professional career. Thankfully I didn’t have the red tape of change management or people unhappy with downtime, since hardly anyone even reads this blog. I selected Azure’s App Service running PHP for kind of a PaaS approach. I used git to push my existing WordPress code to Azure DevOps which integrates nicely with other Azure offerings including App Service. Its out of the box pipeline config tests and deploys your code once it gets a new commit.

For the database I did a mysqldump from the RDS instance and imported into Azure Database for MySQL server. The interesting thing is it is public facing but can be restricted.

I also spun up a small Azure Cache for Redis instance, it has 250 MB of RAM which seems like it is plenty big for this website.

I’m a little concerned about the cost of all of this so I set up some various cost alerts just so I can be prepared but ultimately I am undecided in how much I’m willing to spend. I view the expense as an investment in my skills which translates into growth in my professional career. Also the app service instance is larger than the Ec2 instance it was running on but it appears to be faster and does more frequent incremental backups which is a nice plus.

Let me know what you think of the new stack running this site in the comments!

Data recovery in the Cloud

Recently in my job I had to perform some data recovery activities for an Ec2 instance in AWS. The instance had been powered off for more than a year and the key pair for logging in had been lost. The instance’s root was an EBS volume that was not encrypted. We had a couple of options, we could have mounted the volume to another instance to insert a new key so we can log back in. It turned out that the client was just interested in the data. So ultimately it was just a matter of mounting the volume to an instance and pulling out the desired files. If this sounds simple it’s because it is. I think this is why it’s important to consider carefully who has elevated access to your AWS account. Even with encryption you can perform these activities, only if the application inside the instance had encrypted the data would I have gotten stuck.

Improved Website

The web-server configuration has changed yet again. It used to be highly available with scaling policies and everything but it came at a cost. First of all it carried a price tag, I was using AWS EFS to distribute the files to multiple servers. EFS is a great product but for my use it was also slow. The time to first byte was several seconds. I tried to improve this through memcached and NFS caching utilities but the lowest I got it to was 2.5 seconds to start serving the first byte. Now it is down to half a second and feels much snappier. It is on a single server now with backing EBS as the drive. I’m still running memcached on a seperate instance through AWS’s Elasticache offering. I’m really tempted to stand up an instance on Vultr or Digital Ocean to see how the speeds compare. I will be sure to post the results.

Moved to AWS!

This website is now being hosted on a cluster of servers within AWS. I definitely use this website as an excuse to learn more about infrastructure. I’ll post more about it soon, but currently we are fault tolerant. We are using two EC2 instances running apache, EFS holding the files, and an RDS instance running the database. This is behind an Application Load Balancer and the javascript and pictures are being served over CloudFront. It’s a little slow though for me, I’m trying to figure out why. If you aren’t logged in you’ll have a better experience. I’m gathering metrics on load times to help see what the story is.

SCript Race: Azure VS AWS

Let us compare the data I collected from both my scripts I made for AWS and Azure. Each script accomplishes the same things:

  • Deploy two Windows Server VMs from the providers official image repository
  • Deploy one internet facing load balancer with the two servers behind it on port 80
  • Use the providers built in orchestration method to install IIS and place a simple webpage in the root web directory
  • Validate the website is being served over the internet through the Load Balancer

Here is a comparison of the common tasks:

Here is a table of all the data:

TaskSecondsDuration in SecondsDuration in Minutes
Script Start00.00
Create Load Balancer14140.23
Create VM11761622.70
Install IIS On VM181664010.67
Deploy Website on VM1878621.03
Add VM1 to LoadBalancer954761.27
Create VM211151612.68
Install IIS On VM214853706.17
Deploy Website on VM21547621.03
Add VM2 to LoadBalancer1601540.90
Website Available160210.02
Script Complete (Total)1602160226.70
TaskSecondsDuration in SecondsDuration in Minutes
Script Start00.00
Create Load Balancer880.13
Create VM11570.12
Create VM22160.10
Assign SSM IAM Role on VM137160.27
Assign SSM IAM Role on VM24140.07
Deploy System Management Agent on VM11671262.10
Deploy System Management Agent on VM21701292.15
Execute Install IIS & Website On VM117220.03
Execute Install IIS & Website On VM217310.02
Add VM1 to LoadBalancer17520.03
Add VM2 to LoadBalancer17720.03
Website Available260831.38
Script Complete (Total)2602604.30
Certain tasks in AWS do not wait for their execution to complete. Checks were added in the script and the duration column indicates which were essentially run in parallel.

As you can see from the data above, in terms of automation AWS is much faster.


As a follow on to my script that deploys a cluster of two load balanced Windows servers, installs IIS, and deploys a website for Azure, I created a similar script to do so in AWS. A few things of note that I feel makes AWS’s script better.

  • Certain tasks are non blocking and do not wait for the action to complete. I added wait states in the script to make sure time comparisons are true.
  • AWS actions are much faster. On average in my script it takes Azure 65 seconds to add a VM to a load balancer where in AWS its an average of 2 seconds.
  • AWS’s CLI allows for multiple instance IDs to be provided per command to increase efficiency even though my script doesn’t really take advantage of this which provides a more true comparison since I don’t think Azure’s CLI or PowerShell module allows for this.

Here is the script:

#This script creates a number of Windows VMs, installs IIS, a simple webpage, and places them behind a load balancer
#run this if needed
#aws configure
function elapsedTime {
    $CurrentTime = $(get-date)
    $elapsedTime = $CurrentTime - $StartTime
    $elapsedTime = [math]::Round($elapsedTime.TotalSeconds,2)
    Write-Host "Elapsed time in seconds: " $elapsedTime -BackgroundColor Blue
#Captures start time for script elapsed time measurement
$StartTime = $(get-date)

#Sets "Constants" to be used to create VMs
$imageID = "ami-0182e552fba672768" #Amazon's provided windows 2019 datacenter base
$subnet = "subnet-000000000" #my subnet for us-east-2a
$securityGroup = "sg-00000000" #My network security baseline
$instanceType = "t2.medium" #Instance size, 2 vCPUs, 4 GB RAM
$keyPair = "ServerHobbyist" #Keypair to retreive administrator password
$instanceName = "WebWinApp" #sets base name
$class = "disposable" #sets class tag as disposable for easier identification and cleanup

#creates load balancer
$lbName = "WinWebLB1"
Write-Host "Creating Loadbalancer $($LbName)"
aws elb create-load-balancer `
    --load-balancer-name $lbName `
    --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" `
    --subnets $subnet `
    --security-groups $securityGroup
aws elb add-tags --load-balancer-name $lbName --tags Key=Class,Value=$class #tags elb with disposable class
aws elb configure-health-check --load-balancer-name $lbName --health-check Target=HTTP:80/,Interval=5,UnhealthyThreshold=2,HealthyThreshold=2,Timeout=3 #sets a lower threshold for health checks
Write-Host "Load Balancer $($Lbname) created"

$serverCount = 2 #how many VMs to deploy
$instancesDeployed =  New-Object System.Collections.Generic.List[System.Object] #creates array list that will contain instance IDs deployed
for ($i=1; $i -le $serverCount; $i++){
    $instanceNameTag = $instanceName + $i
    Write-Host "Creating VM $($instanceNameTag)"
    $instance = aws ec2 run-instances `
        --image-id $imageID `
        --count 1 `
        --instance-type $instanceType `
        --key-name $keyPair `
        --security-group-ids $securityGroup `
        --subnet-id $subnet | ConvertFrom-Json
    aws ec2 create-tags --resources $instance.instances.InstanceId --tags Key=Name,Value=$instanceNameTag #tags instance with name
    aws ec2 create-tags --resources $instance.instances.InstanceId --tags Key=Class,Value=$class #tags instance with name

    Write-Host "VM $($instanceNameTag) created"
Start-Sleep -Seconds 15

#Checks to make sure each instance deployed from above is in a running state, otherwise it can't recieve the IAM role.
foreach ($instanceDeployed in $instancesDeployed){
    $instance = aws ec2 describe-instances --instance-ids $instanceDeployed | ConvertFrom-Json
    $InstanceTags = $Instance.Reservations.Instances.Tags
    $InstanceName = $InstanceTags | Where-Object {$_.Key -eq "Name"}
    $InstanceName = $InstanceName.Value
    Write-Host "Checking if instance $($InstanceName)  is ready to receive IAM role for SSM"
    while ($instance.Reservations.Instances.State.Name -ne "running") {
        Write-Host "Instance $($InstanceName) not ready. Waiting to check again"
        sleep 5
        Write-Host "Checking if instance $($InstanceName) is ready to receive IAM role for SSM"
        $instance = aws ec2 describe-instances --instance-ids $instanceDeployed | ConvertFrom-Json
    Write-Host "Instance $($InstanceName) is now ready, assigning role"
    aws ec2 associate-iam-instance-profile --instance-id $instanceDeployed --iam-instance-profile Name=AmazonSSMRoleForInstancesQuickSetup
Start-Sleep -Seconds 15
#Checks to make sure each instance deployed from above has the SSM agent. Otherwise commands can't be sent through AWS's orchestration system
foreach ($instanceDeployed in $instancesDeployed){
    $instance = aws ec2 describe-instances --instance-ids $instanceDeployed | ConvertFrom-Json
    $InstanceTags = $Instance.Reservations.Instances.Tags
    $InstanceName = $InstanceTags | Where-Object {$_.Key -eq "Name"}
    $InstanceName = $InstanceName.Value
    Write-Host "Checking if instance $($InstanceName) has receieved the system management agent"
    $ssmTest = aws ssm list-inventory-entries --instance-id $instanceDeployed --type-name "AWS:InstanceInformation" | ConvertFrom-Json
    while ($ssmTest.Entries.AgentType -ne "amazon-ssm-agent"){
        $ssmTest = aws ssm list-inventory-entries --instance-id $instanceDeployed --type-name "AWS:InstanceInformation" | ConvertFrom-Json
        Write-Host "Instance $($InstanceName) has not yet received the SSM agent"
        start-sleep -Seconds 5
    Write-Host "Instance $($InstanceName) has received the SSM agent. Proceeding to next instance or step."

#Installs IIS and deploys website
foreach ($instanceDeployed in $instancesDeployed){
    Write-Host "Sending command to install IIS and deploy website on $($instanceDeployed)"
    aws ssm send-command `
        --document-name "AWS-RunPowerShellScript" `
        --parameters commands=['Add-WindowsFeature Web-Server; Invoke-WebRequest -Uri "" -OutFile "c:\inetpub\wwwroot\index.html"'] `
        --targets "Key=instanceids,Values=$($instanceDeployed)" `
        --comment "Installs IIS"
    Write-Host "Command sent to $($instanceDeployed)"

#adds VMs to load balancer
foreach ($instanceDeployed in $instancesDeployed){
    Write-Host "Registering instance $($instanceDeployed) with LB"
    aws elb register-instances-with-load-balancer --load-balancer-name $lbName --instances $instanceDeployed #registers instance with load balancer
    Write-Host "Registered instance $($instanceDeployed) with LB"

Write-Host "Checking if website is ready to be served from load balancer"
$lbURL = aws elb describe-load-balancers --load-balancer-name $lbName | ConvertFrom-Json
$lbURL = "http://" + $lbUrl.LoadBalancerDescriptions.CanonicalHostedZoneName
$check = $false
while ($check -eq $false){
try {
    $check = $true
    $result = invoke-webrequest -uri $lbURL -UseBasicParsing -TimeoutSec 20
catch {
    $check = $false
    Write-Host "Website failed to load. Trying again"
    Start-Sleep -Seconds 5
Write-Host "Website is now loading at $lbURL"

Write-Host "Script completed" -BackgroundColor Blue

Infrastructure as Code: Azure Edition

Since I’ve been using AWS as a hobbyist for about a decade it is the public cloud I am most comfortable with. Lately to expand my horizons I’ve been learning about Microsoft’s take on it with Azure. I hope I’m not too bias since its easier for me to favor AWS since I’ve been using it for so long however my initial take on Azure is not positive. It is slow. I’m working on a comparison in terms of speed between AWS and Azure with the goal of standing up a 2 node load balanced IIS cluster. While I continue to work on making a good comparison write-up here is the code that deploys out the Azure resources. It is written in PowerShell and includes a function that measures completion time.

Import-Module Az
function elapsedTime {
    $CurrentTime = $(get-date)
    $elapsedTime = $CurrentTime - $StartTime
    $elapsedTime = [math]::Round($elapsedTime.TotalSeconds,2)
    Write-Host "Elapsed time in seconds: " $elapsedTime -BackgroundColor Green
#Run this to connect to Azure account if needed
#Captures start time for script elapsed time measurement
$StartTime = $(get-date)
#Sets "Constants" to be used throughout script
$resourceGroup = "DisposableLab"
$location = Get-AzLocation | Where-Object {$_.DisplayName -like "North Central US"}
$vnet = "vnet1"
$subnet = "default"
$securityGroup = "DisposableLabSecurityGroup"
$secpasswd = ConvertTo-SecureString "password" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ("username", $secpasswd)
$lbname = "WebAppWinLB"
$availSetName = "WinWebappAvailabilitySet"
#Creates Availability Set to allow both servers to be load balanced
New-AzAvailabilitySet `
   -Location $location.Location `
   -Name $availSetName `
   -ResourceGroupName $resourceGroup `
   -Sku aligned `
   -PlatformFaultDomainCount 2 `
   -PlatformUpdateDomainCount 2

$publicIp = New-AzPublicIpAddress -Name 'LB1PublicIP' -ResourceGroupName $resourceGroup -AllocationMethod Static -Location $location.Location
#sets up the inbound IP pool for the load balancer
$feip = New-AzLoadBalancerFrontendIpConfig -Name 'myFrontEndPool' -PublicIpAddress $publicIp
$bepool = New-AzLoadBalancerBackendAddressPoolConfig -Name 'myBackEndPool' 
#creates health check for load balancer
$probe = New-AzLoadBalancerProbeConfig `
 -Name 'myHealthProbe' `
 -Protocol Http -Port 80 `
 -RequestPath / -IntervalInSeconds 360 -ProbeCount 5
#creates load balancing rule
$rule = New-AzLoadBalancerRuleConfig `
  -Name 'webInbound' -Protocol Tcp `
  -Probe $probe -FrontendPort 80 -BackendPort 80 `
  -FrontendIpConfiguration $feip `
  -BackendAddressPool $bepool
#creates new LB from settings gathered so far
 $lb = New-AzLoadBalancer `
  -ResourceGroupName $ResourceGroup `
  -Name $lbname `
  -Location $location.Location `
  -FrontendIpConfiguration $feip `
  -BackendAddressPool $bepool `
  -Probe $probe `
  -LoadBalancingRule $rule 


$serversCount = 2
for ($i=1; $i -le $serversCount; $i++) {
$VMName = "WebappWin" + $i
Write-Host "Creating VM " + $VMName
#Generates new public IP for the new load balancer to be created
$VM = Get-AzVM -Name $VMName
$NIC = Get-AzNetworkInterface -Name $VMName
#creates new VM
New-AzVm `
    -Credential $credential `
    -ResourceGroupName $resourceGroup `
    -Name $VMName `
    -Location $location.Location `
    -VirtualNetworkName $vnet `
    -SubnetName $subnet `
    -SecurityGroupName $securityGroup `
    -PublicIpAddressName "$($VMName)PublicIP" `
    -AvailabilitySetName $availSetName
Write-Host "VM $($VMName) has been created"
Write-Host "Installing IIS for " + $VMName
$PublicSettings = '{"commandToExecute":"powershell Add-WindowsFeature Web-Server"}'
#Waits a few seconds for the VM to become available to recieve 
Start-Sleep -Seconds 5
Set-AzVMExtension -ExtensionName "IIS" -ResourceGroupName $resourceGroup -VMName $vmName `
  -Publisher "Microsoft.Compute" -ExtensionType "CustomScriptExtension" -TypeHandlerVersion 1.4 `
  -SettingString $PublicSettings -Location $location.location
Write-Host "IIS Installed for $($VMName)"
Write-Host "Deploying website for " + $VMName

Invoke-AzVMRunCommand -ResourceGroupName $resourceGroup -VMName $VMName -CommandId "RunPowerShellScript" -ScriptPath "C:\pathhere\WebsiteTest\deployWebsite.ps1"
Write-Host "Website deployed on $($VMname)"
#Gets load balancer object based on name
$lb = Get-AzLoadBalancer -Name $lbname
$backendConfig = Get-AzLoadBalancerBackendAddressPoolConfig -LoadBalancer $lb
#Get's NIC from virtual machine
$NIC = Get-AzNetworkInterface -Name $VMName
#Removes VM from LB
#Adds VM to LB
Write-Host "Adding $($VMName) to Loadbalancer " + $lbname
Set-AzNetworkInterface -NetworkInterface $nic
Write-Host "VM $($VMName) added to the load balancer"

Write-Host "Script completed" -BackgroundColor Blue


I tried moving this website to CentOS 8 but I somehow managed to break YUM/DNF in my initial configuration of the LAMP stack. I don’t want to admit I screwed up so I’ll just say that CentOS 8 is not ready for production quite yet, at least not in my world. As a result of the remediation I have moved this server to Azure. I’m learning more and more about Microsoft’s way of running a cloud provider..