Showing posts with label load. Show all posts
Showing posts with label load. Show all posts

Thursday, November 19, 2015

Monitoring your CDN's Webservers

(This post considers options for writing a monitor in powershell that can handle round-robin DNS load-distributed web servers, especially those that you do not control)

Premise

After observing some issues with a vendor's CDN having occasional server/service outages, I decided to write some powershell to dig through and give me a quick analysis so I could determine where the problem was and what effect it should have on the users. Some of you may read this and think "If the site is down, it's down, right?"

Well many websites now spread their resources across CDNs (Content Delivery Networks). This means that while the html page may come from one webserver (or even one CDN), the javascript and CSS file may come from another, and the images and videos from yet another. You can use the Developer Tools in your web browser to dig up out the sources for (at least THIS load) of the website.

On top of this, there are a few approaches to load balancing, where the exact same resources are duplicated across many servers, with the intent of spreading traffic around and reducing the chances of downtime. Citrix's Netscaler is a popular (and VERY capable) option, but sometimes you'll see the vendor go down the cheaper and easier path: Round-Robin DNS.

Approach

In Powershell, you can invoke the GetHostAddresses method to see how many IP addresses are linked to a DNS entry.

[System.Net.DNS]::GetHostAddresses("google.com").ipAddressToString

At the time of this writing, that returns 11 IP addresses. If you want to dig into nslookup, you can watch it cycle through the IPs in that list as you repeatedly query for google.com.

So what if your CDN is using Round Robin, and your customers are complaining of an outage, and the site appears up?


function test-CDNPage{
    Param
        ([string]$url) #Properly Formatted with http:// or https://
    try{
        write-host -ForegroundColor Cyan "Trying $url"
        Invoke-WebRequest -Uri $url
    }
    Catch [System.Net.WebException]
    {
    if ($_.exception -match "403")
        {
            # 403 is expected because CDNs typically dont allow IP access
            Write-host "403" -ForegroundColor Green
            $value = $true
        }
        ELSE
        {
            # A non-403 response is treated as 
            write-host "Non-403 Response" -ForegroundColor Red
            $_
            $value = $false
        }   
    }
    #Add Results to Object
    $thisResult = New-Object psobject -property @{
        url        = $url
        Responding = $value
        }
    return $thisResult
}

$data = @()
$site = "google.com"
$ipaddys = [system.net.dns]::GetHostAddresses($site).IPAddressToString
foreach ($ip in $ipaddys){
    $data += testpage "http://$ip"
}


Now this code probably won't return anything because Google isn't a CDN. They see you trying to access the IP directly and re-direct your web browser to https:\\google.com. They likely also have some smarter handling behind their Round-Robin DNS solution, as they are...Google.

But consider a company like CloudFlare. They may be hosting multiple different websites from multiple different companies on their servers.

When you try to access a site like that, you load a page telling you that "direct IP access is not allowed" in your browser, and powershell's Invoke-WebRequest throws a 403 Terminating error. This is where Powershell can do things that some other monitoring tools can not. The powershell snippet above uses a try/catch statement to look at the error. If the error is a 403, it sets the $value to true. If the error is NOT a 403, it sets the $value to false. I've set this up to output an XML file every minute that is consumable by my client's monitoring system, and now they can see when individual pieces of their vendor's web services go down.

Having solid data like this can frequently get you past the first tier support layer, that at many companies is incapable of solving network issues on their own end, and is typically only a fence I must cross to reach an actual solution.

Tuesday, April 8, 2014

get-XALoadDetails

I wanted a tool to be able to pull load information from the different cmdlets available in the XenApp powershell snapin, I also took the opportunity to learn how powershell modules work. Grabbing from get-xaserver, get-xaloadevaluator, and get-xaserver it produces the following output:

LoadEval LoginStatus   Load     Sessions Max ServerName
-------- -----------   ----     -------- --- ----------
Default  AllowLogOns   2100           21 100 XenAppSvr

It accepts pipeline input (from get-xaworkergroupserver for instance) and includes handling for the snap-in to avoid the getting the snap-in scope-locked.



xenModules.psm1
<?ps
#Requires -version 2.0
#Requires –PSSnapin Citrix.XenApp.Commands


function get-XALoadDetails{
    #.Synopsis
        # Gathers XenApp Load Evaluation Data into a single object
    #.Description
        # Gathers XenApp Load Evaluation Data into a single object, useful for determining load balancing behavior. Requires Citrix Xenapp Powershell snapin.
    #.Parameter Server
        # Server to pull load Evaluation data from, or enter * or all, to poll the entire farm.
    #.Parameter HandleSnapin
        # Loads and unloads the Citrix.XenApp.Commands snapin for the script. Default is false.
    #.Example
        # Show Load Balancing Details for server "2K8-Hamster"
        # Get-XALoadDetails -Server 2K8-Hamster
    #.Example
        # Show Load Balancing Details for all XenApp Servers
        # Get-XALoadDetails -Server All
 
  [cmdletbinding()]
    param(
            # Target Server, or * to pull all Xen Servers
            [Parameter(Position=0, Mandatory=$True, ValueFromPipeline=$True)]
            [string[]]$server,
            # Handle loading the snapin? Default is no.
            [Parameter(Position=1, Mandatory=$False)]
            [Alias("LS")]
            [switch]$handleSnapin
       )
    BEGIN{
        #Handling the Citrix Snapin. If the $handleSnapin is called, CHECK to see if the snapin is loaded before continuing
        #If the snapin was already loaded, set $handleSnapin to $false
            if($handleSnapin -eq $true){
                if ((Get-PSSnapin -name Citrix.XenApp.Commands -ErrorAction SilentlyContinue) -eq $null){
                    Add-PSSnapin Citrix.XenApp.Commands
                    }ELSE{
                    $handleSnapin = $false
                    }
            }
 
        #{Add-PSSnapin citrix*}
        $allLoads = Get-XAServerLoad #grab load info ONCE
        
         
         
                                                         
    }PROCESS{
        
        [string[]]$servers = " "
        if ($server -eq "*" -or $_ -eq "all")
            {$servers = Get-XAServer | sort ServerName | select -expand servername}
            ELSE
            {$servers = $server}
                
        
          #if (($server -eq "*") -or ($server -eq "all")){$server = Get-XAServer | sort ServerName | select -expand servername}
          $servers
        foreach ($server in $servers){
        if((Test-Connection -count 1 ($server) -quiet) -eq $true){
            "yup"
            $3in1 = get-xaserver ($server) -full | select ServerName, SessionCount, LogOnMode
            $allEvals = get-xaloadevaluator -server ($3in1.ServerName) | select ServerUserLoad, LoadEvaluatorName
            $obj = New-Object PSObject -Property @{
                MaxSessions = $allEvals.ServerUserLoad
                LoadEvaluator = $allEvals.LoadEvaluatorName
                LoginStatus = $3in1.LogOnMode
                Load = $allLoads | where{($3in1.ServerName) -eq $server.ServerName} | select -expand Load
                Sessions = $3in1.SessionCount
                ServerName = $3in1.Servername
                }
            $obj.pstypenames.Insert(0,'xenModule.LoadDetails')
            $obj
             
            }

        }
 
    }END{
    #Remove PSSnapin if it wasn't loaded before, to avoid scope locking
    if($handleSnapin -eq $true){Remove-PSSnapin Citrix.XenApp.Commands
        }
    }
 
}##End of get-XALoadDetails Function
export-modulemember -function get-XALoadDetails


xenModules.psd1
<?ps
#Requires –PSSnapin Citrix.XenApp.Commands
#
# Module manifest for module 'xenModules'
#
# Generated by: Jared Shippy
#
# Generated on: 04/03/2014
#

@{

# Script module or binary module file associated with this manifest
# RootModule = ''

ModuleToProcess = 'xenModules.psm1'

# Version number of this module.
ModuleVersion = '1.0'

# ID used to uniquely identify this module
#GUID = 'd0a9150d-b6a4-4b17-a325-e3a24fed0aa9'

# Author of this module
Author = 'Jared Shippy'

# Company or vendor of this module
#CompanyName = 'Unknown'

# Copyright statement for this module
#Copyright = '(c) 2012 User01. All rights reserved.'

# Description of the functionality provided by this module
# Description = ''

# Minimum version of the Windows PowerShell engine required by this module
#PowerShellVersion = '2'

# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''

# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''

# Minimum version of the .NET Framework required by this module
# DotNetFrameworkVersion = ''

# Minimum version of the common language runtime (CLR) required by this module
# CLRVersion = ''

# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module
# ScriptsToProcess = @()

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = @('xenModules.Format.ps1xml')

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()

# Functions to export from this module
FunctionsToExport = '*'

# Cmdlets to export from this module
CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module
AliasesToExport = '*'

# List of all modules packaged with this module
# ModuleList = @()

# List of all files packaged with this module
# FileList = @()

# Private data to pass to the module specified in RootModule/ModuleToProcess
# PrivateData = ''

# HelpInfo URI of this module
# HelpInfoURI = ''

# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

}


xenModules.Format.ps1xml
<?xml



  

  HashTable
  
    XenModule.LoadDetails
  
  
    
      
        
        8
      
      
        13
      
      
        8
      
      
        8
        Right
      
      
        
        3
      
      
        10
      
    
    
      
        
           
             LoadEvaluator
           
           
             LoginStatus
           
           
             Load
           
           
             Sessions
           
           
              MaxSessions
           
           
             ServerName