Healthchecks Should Not Be Pings

on Saturday, December 17, 2016

I had a long held belief that health checks should just be pings. “Is the websites up?” And for years, that was right. Not anymore.

Recently, a developer asked me if he should use health checks to ensure that the Entity Framework Cache stays in memory? It took me a while disassociate health checks from pings, but he was right. YES, you should use health checks to ensure the health of your site.

You should use health checks to do this:

  • Ensure your site is up and running (ping)
  • Ensure all cached values are available and, if possible, at the latest value.
  • Ensure Entity Framework’s cache is hit before your first user
    • EF is a total hog of resources and complete slowdown on first hit
  • Same thing for WCF
  • Cache any application specific values needed before first hit

Health checks should not be pings. They should check the entire health of the site and its responsiveness. It should check the cache, it’s database connectivity, and everything that makes a website work. It’s a “health check” not a ping.

Tyk in Docker on Windows 10

on Sunday, October 16, 2016

I’m very new to all this technology so, please take this with a grain of salt. The reason I’m writing it is because I couldn’t find another guide that had end-to-end setup on Tyk in Docker on Windows 10.

Tyk is an API Gateway product that can be used to help manage a centralized location of many services/micro services. It is a product which is built on top of the nginx web server. And, nginx is really only supported as a “server” product on *nix based systems. Their Windows build is considered a beta.

So, there are already some good guides for each of the next steps, I’m just gonna pull them all together, and add one extra piece at the end.

Install Docker

There are a couple ways to get around the limitation of nginx only being “production ready on *nix”, but I choose to try out Tyk on Docker. Docker is the multiplatform container host that has created a lot of buzz within the cloud space. But, it also seems pretty awesome at setting up small containers on your local machine too.

Note: At this time, 2016-10-16, if you download a Docker for Windows installer, use the Beta Channel. The stable channel has a bug when trying to mount volumes into containers.

The docker installation wizard is pretty straight forward, so no worries there. Once, installed right-click on the Docker systray icon and select Open Kitematic …

image

A pop-up window should come up containing instructions on how to download and install Kitematic. It was amazingly simple and gave a nice GUI interface over the command line.

Follow Tyk’s Installer Instructions

Tyk provides instructions to setup the API gateway & dashboard with Docker on their website. I would suggest getting an account at Docker Hub. I don’t remember when in the process I created one, but I needed it to access … something.

In Step 2. Get the quick start compose files you’ll need to git clone the files to an folder under you C:\Users\XXXX folder. For me, Docker had a permissions restriction that only allowed containers to mount volumes from folders under my user folder. (So, that could be interesting if you run a container on a server under a service account.)

The silver lining about this set of containers is that they only need to use config files from your local drive. So, it’s not like your C:\Users folder is going to store a database.

In Step 4. Bootstrap your dashboard and portal, if you have bash available to you I would suggest trying it when you run ./setup.sh. I haven’t installed Win10 Anniversary Update, Git Bash, or Cygwin so I didn’t have bash available to run setup.sh.

However, I do feel somewhat comfortable in powershell, and the setup.sh script didn’t look too long. Below is the powershell conversion, which you should be saved in the same directory as setup.sh, and you should run .\setup.ps1 from the PowerShell ISE with the arguments that you want.

After that, I had a running Tyk API Gateway.

image

Other Thoughts

Since this was all new technology I ran into a lot of errors and read through a lot of issue/forum posts. Which makes me think this might not be the best idea for a production setup. If you’re able to make linux servers within your production environment, I would strongly suggest that.

Because I made so many mistakes I got used to these three commands which really helped recreate the environment whenever I messed things up. I hope this helps.

Get-FullDomainAccount

on Friday, April 1, 2016

In a previous post I forgot to include the PowerShell code for Get-FullDomainAccount. Sorry about that.

Here it is:

$env:USERDOMAIN = "<your domain>"
<#
.SYNOPSIS
	Ensures that the given domain account also has the domain prefix. For example,
	if the -DomainAccount is "IUSR_AbcXyz" the "<your domain>\IUSR_AbcXyz" would most likely
	be returned. The domain is pulled from the current users domain, $env:USERDOMAIN.

	If -Environment is provided, this will also run the -DomainAccount through
	Get-EnvironmentDomainAccount to replace any environment specific information.

.LINK
	Get-EnvironmentDomainAccount
		Used to apply environment specific value to the domain account

.EXAMPLE
    $result = Get-FullDomainAccount -DomainAccount "IUSR_AbcXyz"
    $result -eq "<your domain>\IUSR_AbcXyz"
#>
Function Get-FullDomainAccount {
[CmdletBinding()]
Param (
	[Parameter(Mandatory=$true)]
	[string] $DomainAccount,
	[string ]$Environment = ""
)
	$accountName = $DomainAccount;

	if($Environment -ne "") {
        $accountName = Get-EnvironmentDomainAccount -Environment $Environment -DomainAccount $DomainAccount;
	}

    if($accountName -match "ApplicationPoolIdentity") {
        $accountName = "IIS AppPool\$accountName"
    }

    if($accountName -match "LocalSystem") {
        $accountName = "$($env:COMPUTERNAME)\$accountName"
    }

	if($accountName -notmatch "\\") {
		$accountName = $env:USERDOMAIN + "\" + $accountName;
	}
	return $accountName;
}

WebAdministration Not Loaded Correctly on Remote

on Friday, November 14, 2014

When making remote calls that use the WebAdministration module you can sometimes get this error, inconsistently:

ERROR: Get-WebSite : Could not load file or assembly ‘Microsoft.IIS.PowerShell.Framework' or one of its dependencies. The system cannot find the file specified.

It’s a really tricky error because its inconsistent. But, there is a workaround that will prevent the error from giving you too much trouble. From the community that has done troubleshooting on this, the problem seems to occur on the first call that uses the WebAdministration module. If you can wrap that call in a try/catch, then subsequent calls will work correctly.

$scriptBlock = {
    Import-Module WebAdministration

    try {
        $sites = Get-WebSite
    } catch {
        # http://help.octopusdeploy.com/discussions/problems/5172-error-using-get-website-in-predeploy-because-of-filenotfoundexception
        $sites = Get-WebSite
    }
}

Invoke-Command -ScriptBlock $scriptBlock -ComputerName Remote01

PowerShell AppPool Assignment Problems

on Friday, November 7, 2014

The WebAdministration module has a Function called IIS:. It essentially acts like a drive letter or an uri protocol. Its really convenient and makes accessing appPool, site information, and ssl bindings easy.

I recently noticed two problems with assigning values through the IIS: protocol or the objects which is works with:

StartMode Can’t Be Set Directly

For some reason, using Set-ItemProperty to set the startMode value directly throws an error. But, if you retrieve the appPool into a variable and set the value using an = operator, everything works fine.

# https://connect.microsoft.com/PowerShell/feedbackdetail/view/1023778/webadministration-apppool-startmode-cant-be-set-directly
ipmo webadministration

New-WebAppPool "delete.me"

Set-ItemProperty IIS:\AppPools\delete.me startMode "AlwaysRunning" # throws an error

$a = Get-Item IIS:\AppPools\delete.me
$a.startMode = "AlwaysRunning"
Set-Item IIS:\AppPools\delete.me $a # works

Here is the error that gets thrown:

Set-ItemProperty : AlwaysRunning is not a valid value for Int32.
At C:\Issue-PowershellThrowsErrorOnAppPoolStartMode.ps1:6 char:1
+ Set-ItemProperty IIS:\AppPools\delete.me startMode "AlwaysRunning" # throws an e ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Set-ItemProperty], Exception
    + FullyQualifiedErrorId : System.Exception,Microsoft.PowerShell.Commands.SetItemPropertyCommand

 

CPU’s resetLimit Can’t Directly Use New-TimeSpan’s Result

I think the example can show the problem better than I can describe it:

# https://connect.microsoft.com/PowerShell/feedbackdetail/view/1023785/webadministration-apppools-cpu-limit-interval-resetlimit-cant-be-set-directly
ipmo webadministration

New-WebAppPool "delete.me"

$a = Get-ItemProperty IIS:\AppPools\delete.me cpu
$a.resetInterval = New-TimeSpan -Minutes 4 # this will throw an error
Set-ItemProperty IIS:\AppPools\delete.me cpu $a

$a = Get-ItemProperty IIS:\AppPools\delete.me cpu
$k = New-TimeSpan -Minutes 4 # this will work
$a.resetInterval = $k
Set-ItemProperty IIS:\AppPools\delete.me cpu $a

Here is the error that gets thrown:

Set-ItemProperty : Specified cast is not valid.
At C:\Issue-PowershellThrowsErrorOnCpuLimitReset.ps1:8 char:1
+ Set-ItemProperty IIS:\AppPools\delete.me cpu $a
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Set-ItemProperty], InvalidCastException
    + FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.SetItemPropertyCommand

The links on each section correspond with bug reports for the issues, so hopefully they will get looked into.

PowerShell Wrapper for Http Namespaces

on Friday, October 31, 2014

When hosting HTTP WCF services as a self-hosted Windows Services the server needs to have the HTTP Namespace reserved. The reservation allows for the domain account which runs the service to setup a listener on a particular port, for a particular address.

There are some tools already available which can help in this process:

  • HTTP Namespace Manager – A nice GUI interface, which is easy to understand and setup. It also works on Server Core Servers.
  • httpcfg – Windows Server 2003
  • netsh – Windows Server 2008+

But, there are no PowerShell wrappers for these commands. So, here’s a wrapper that provides:

  • Add-HttpNamespace
  • Get-HttpNamespace
  • Get-HttpNamespaces
  • Test-HttpNamespaceExists

There’s no remove because I haven’t needed it yet. A namespace is usually associated with a particular port, and I haven’t been involved in a situation where a port needed to be reused.

<#
.SYNOPSIS
    Parses the output from netsh to turn them in PSObjects.
#>
Function Get-HttpNamespaces {
[CmdletBinding()]
[OutputType([PSObject[]])]
Param()

    # the $propsReady variable causes alot of errors to occur, but the results are accurate.
    # so this helps hide the errors
    $originalErrorAction = $ErrorActionPreference
    $ErrorActionPreference = 'SilentlyContinue'

    try {

        # pull the data from netsh
        $urlaclOutput = . netsh http show urlacl

        # parse the data into PSObjects
        $httpNamespaces = New-Object System.Collections.Generic.List[PSObject]
        $props = @{}
        $userProps = @{}
        $userRdy = $false
        for($i = 0; $i -lt $urlaclOutput.Count; $i++) {
            $line = $urlaclOutput[$i].Trim()

            $split = $line.Split(":", [StringSplitOptions]::RemoveEmptyEntries)

            $first = ""
            if($split.Count -gt 0) { $first = $split[0] }
        
            # line parsing
            switch($first.Trim()) {
                "Reserved URL" {
                    $props.ReservedUrl = $line.Substring(25).Trim()
                    $users = New-Object System.Collections.Generic.List[PSObject]
                }
                "User" {
                    if($userRdy) {
                        $user = New-Object PSObject -Property $userProps
                        $users.Add($user)

                        $userProps = @{}
                        $userRdy = $false
                    }

                    $userProps.User = $split[1].Trim()
                }
                "Listen" { $userProps.Listen = $split[1].Trim() }
                "Delegate" {
                    $userProps.Delegate = $split[1].Trim()
                    $userRdy = $true
                }
                "SDDL" {
                    $userProps.SDDL = $line.Substring(5).Trim()
                    $userRdy = $true
                }
                "" {
                    if($userRdy) {
                        # user
                        $user = New-Object PSObject -Property $userProps
                        $users.Add($user)

                        $userProps = @{}

                        # url
                        $props.Users = $users.ToArray()

                        $cnObj = New-Object PSObject -Property $props
                        $httpNamespaces.Add($cnObj)

                        $props = @{}

                        # reset flag
                        $userRdy = $false
                    }
                }
            }
        }
    } finally {
        $ErrorActionPreference = $originalErrorAction # revert the error action
    }

    return $httpNamespaces.ToArray()
}


<#
.SYNOPSIS
    Retrieves the namespace information for a given namespace. It will also search for namespaces
    which match but the host names have been replaced with + or * symbols.
#>
Function Get-HttpNamespace {
[CmdletBinding()]
[OutputType([PSObject])]
Param (
    [Parameter(Mandatory = $true)]
    [string] $HttpNamespace
)

    $httpNamespaces = Get-HttpNamespaces

    # get * and + versions of the url ready
    $starNamespace = $HttpNamespace
    $plusNamespace = $HttpNamespace
    $namespaceRegex = [regex] "http.*://(.*):.*/.*"
    if($HttpNamespace -match $namespaceRegex) {
        $hostname = $Matches[1]
        $starNamespace = $HttpNamespace.Replace($hostname, "*")
        $plusNamespace = $HttpNamespace.Replace($hostname, "+")
    }

    # sometimes the http namespaces get /'s added to the end
    $namespace = $httpNamespaces |? {
                            $_.ReservedUrl -eq $HttpNamespace `
                    -or     $_.ReservedUrl -eq ($HttpNamespace + '/') `
                    -or     $_.ReservedUrl -eq $starNamespace `
                    -or     $_.ReservedUrl -eq ($starNamespace + '/') `
                    -or     $_.ReservedUrl -eq $plusNamespace `
                    -or     $_.ReservedUrl -eq ($plusNamespace + '/')
                }

    return $namespace
}



<#
.SYNOPSIS
    Checks if a namespace already exists. It will also search if the namespace has had its host name
    replaced with + or * symbols.
#>
Function Test-HttpNamespaceExists {
[CmdletBinding()]
[OutputType([bool])]
Param (
    [Parameter(Mandatory = $true)]
    [string] $HttpNamespace
)

    $namespace = Get-HttpNamespace $HttpNamespace

    return $namespace -ne $null
}



<#
.SYNOPSIS
    Adds a new Http Namespace. This will automatically swap out the host name for a + symbol. The
    + symbol allows the Http Namespace to bind on all NIC addresses.
#>
Function Add-HttpNamespace {
[CmdletBinding()]
[OutputType([PSObject])]
Param (
    [Parameter(Mandatory = $true)]
    [string] $HttpNamespace,
    [Parameter(Mandatory = $true)]
    [string] $DomainAccount
)

    $create = $true
    if(Test-HttpNamespaceExists $HttpNamespace) {
        # it already exists, so maybe not create it
        $create = $false

        $namespace = Get-HttpNamespace $HttpNamespace
        # but, if the given DomainAccount doesn't exist then create it
        $user = $namespace.users |? { $_.user -eq $DomainAccount }
        if($user) {
            Write-Warning "NET $env:COMPUTERNAME - Http Namespace '$HttpNamespace' already contains a rule for '$DomainAccount'. Skipping creation."
            return
        } else {
            $create = $true
        }
    }

    if($create) {
        # the standard pattern to use is http://+:port/servicename.
        #   eg. http://contoso01:15110/EmployeeService would become http://+:15110/EmployeeService
        $plusNamespace = $HttpNamespace
        $namespaceRegex = [regex] "http.*://(.*):.*/.*"
        if($HttpNamespace -match $namespaceRegex) {
            $hostname = $Matches[1]
            $plusNamespace = $HttpNamespace.Replace($hostname, "+")
        } else {
            throw "NET $env:COMPUTERNAME - Http Namespace '$HttpNamespace' could not be parsed into plus format before being added. Plus format " + `
                "looks like http://+:port/servicename. For example, http://contoso01:15110/EmployeeService would be formatted into " + `
                "http://+:15110/EmployeeService."
        }

        # ensure the full domain account name is used
        $fullDomainAccount = Get-FullDomainAccount $DomainAccount

        # create the permission
        Write-Warning "NET $env:COMPUTERNAME - Adding Http Namespace '$Httpnamespace' for account '$fullDomainAccount'"
        $results = . netsh http add urlacl url=$plusNamespace user=$fullDomainAccount listen=yes delegate=yes
        Write-Host "NET $env:COMPUTERNAME - Added Http Namespace '$Httpnamespace' for account '$fullDomainAccount'"
    }

    $namespace = Get-HttpNamespace $HttpNamespace
    return $namespace
}

Quick Redis with PowerShell

on Friday, October 24, 2014

ASP.NET has some great documentation on How To Setup a SignalR Backplane using Redis, but it uses a linux based server as the host. The open source port of Redis maintained by MSOpenStack creates an incredibly easy to install Redis server for a Windows Server environment (using Chocolatey ... KickStarter). This is a quick PowerShell script to install redis as a Windows Service, add the firewall rule, and start the service.

# PreRequisites
#    The assumption is that the server can use chocolatey to install

# use chocolatey to install precompiled redis application
#    this will install under the binaries under $env:ChocolateyInstall\lib\redis-X.X.X\
cinst redis-64

# install redis as a service
#    redis will make the service run under Network Service credentials and setup all appropriate permissions on disk
redis-server --service-install

# open firewall ports
. netsh advfirewall firewall add rule name=SignalR-Redis dir=in protocol=tcp action=allow localport=6379 profile=DOMAIN

# start the service
redis-server --service-start

Note: When installing the service there is an error message "# SetNamedSecurityInfo Error 5". But, it doesn't seem to affect anything; everything seems to run without a problem.


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.