My Profile Photo

Spencer Owen


The art of progress is to preserve order amid change and preserve change amid order - Alfred North Whitehead


Automating F5s with powershell

At my job we are on the ‘infrastructure as code’ bandwagon. The most popular tool for coding your infrastructure is terraform.

Unfortunately we have 3 core pieces of software that don’t yet work with terraform, and we don’t have the time to learn golang to write them.

  • F5 load balancers
  • Nutanix Hypervisors
  • Octopus Deploy

Until terraform gets the support we need, we wrote our own home grown version of terraform in powershell. Now we can define our vms as objects and put them in version control. Hooray!

{
  "foobar": {
    "Size": {
      "CPU": 1,
      "RAM": 2,
      "Cores": 4
    },
    "Volumes": [{
      "Description": "Cdrive",
      "ImageName": "win2012-vagrant",
      "Type": "FromImage"
    }],
    "Provisioners": [{ 
      "Chef": {
        "Environment": "dev",
        "RunList": [
          "role[web]",
          "role[splunk]"
        ],
        "BootstrapVersion": "12.16.42"
      }
    },{
      "F5": {
        "Pools": [ "foo", "bar" ],
        "Port": 80,
        "HealthMonitors": ["icmp"]
      }
    }],
    "Location": {
      "ActiveHypervisor": "nutanix",
      "Nutanix": "nutanix.example.com"
    },
    "Metadata": {
      "Powerform": "true"
    },
    "Network": [{
      "VLANID": "130"
    }],
    "DNS": {
      "Server": "lab-domain01",
      "Zone": "example.com"
    }
  }
}

The hardest part by far was navigating the poorly written F5 powershell cmdlet api documentation. It doesn’t even have examples! I’ve shared the hard work of guessing how the powershell cmdlets are supposed to work here. I’ve navigated the mine field, here is some help.

Add-PSSnapin iControlSnapin;

Api docs https://devcentral.f5.com/wiki/iControl.APIReference.ashx

Authenticate to F5

if ( (Get-PSSnapin -Name iControlSnapin -ErrorAction SilentlyContinue) -eq $null ) {Add-PsSnapin iControlSnapin -ErrorAction Stop}
$F5Connected = ((Get-F5.iControl).initialized -and (Get-F5.iControl).ConnectionInfo.hostname -eq $LoadBalancer)
if ($F5Connected) {
    Write-Verbose "F5 connection already initialized"
}
else {
    $Creds = Get-Credential -Message "$($LoadBalander) Credentials"
    $Connection = Initialize-F5.icontrol -Hostname $LoadBalancer -Credentials $Creds
    }
}

Get pool

(Get-F5.iControl).LocalLBPool.get_member_v2(@('spencer'))

Get commands

(get-f5.icontrol).LocalLbNodeAddressV2 | get-member

Set Description on node

(Get-F5.iControl).LocalLBNodeAddressV2.set_description( (, "herpderp"), (, "my description") )

Set Description on node with time

$now = get-date -f s
(get-f5.icontrol).LocalLbNodeAddressV2.set_description( (,'herpderp'), (, "created with powerform $now") )

Get state

(Get-F5.iControl).LocalLBNodeAddressV2.get_object_status('192.0.2.0')

Add Metadata

(get-f5.icontrol).LocalLbNodeAddressV2.add_metadata('192.0.2.0',('foo'), ('bar'))
(get-f5.icontrol).LocalLbNodeAddressV2.add_metadata('192.0.2.0',('powerform'), ('true'))

Get Metadata

(get-f5.icontrol).LocalLbNodeAddressV2.get_metadata('192.0.2.0')

Create Node

note 3 ways to make nodes, you access node name via 'AddressPort' because F5 is stupid https://devcentral.f5.com/questions/exception-when-using-locallbpooladd_member_v2-primary_error_code-17237812-0x01070734

(get-f5.icontrol).LocalLbNodeAddressV2.create('herpderp','192.0.2.0', 0)

Get Connection Limit

(get-f5.icontrol).LocalLbNodeAddressV2.get_connection_limit('herpderp')

Get All Ip Addresses

$nodeNames   = @((get-f5.icontrol).LocalLBNodeAddressV2.get_list());
$nodeAddress = @((get-f5.icontrol).LocalLBNodeAddressV2.get_address($nodeNames));

Get All Pools

(Get-F5.iControl).LocalLBPool.get_list()

Add member to pool

$Node = New-Object -TypeName iControl.CommonAddressPort;
$Node.address = 'foobar' # Note this is named dumb, address really is node name
$Node.port = 80
(get-f5.icontrol).locallbpool.add_member_v2 ('foobar', $Node )

Get all partitions

(Get-F5.iControl).ManagementPartition.get_partition_list()

Get Active Partition

(Get-F5.iControl).ManagementPartition.get_active_partition()

Set Active Partition

(Get-F5.iControl).ManagementPartition.set_active_partition( (,"Foobar") )

Get all ip addresses of all nodes

 $nodeNames   = (Get-F5.iControl).LocalLBNodeAddressV2.get_list()
 $nodeAddresses = (Get-F5.iControl).LocalLBNodeAddressV2.get_address($nodeNames)

Get Ip Address of F5 Node

(Get-F5.iControl).LocalLBNodeAddressV2.get_address('foobar')

Delete Node Completely

$ip = (get-f5.icontrol).LocalLBNodeAddressV2.get_address($VMName)
(Get-F5.iControl).LocalLBNodeAddress.delete_node_address($ip)

Get member in pool

$members = (Get-F5.iControl).LocalLBPool.get_member_v2((,'spencer'))[0]
ForEach ($m in $members ) {
  $m.address
}

Create pool
Note: you cant create an empty pool, you must assign alteast 1 node

$pool_names = (, $Pool);
$lb_methods = (, "LB_METHOD_ROUND_ROBIN");
$member = New-Object -TypeName iControl.CommonIPPortDefinition;
$ip = F5GetIpFromNode -VMName $VMName -Partition $Partition
$member.address = $ip;
$member.port = $Port;
$memberA = (, $member);
$memberAofA = (, $memberA);
(Get-F5.iControl).LocalLBPool.create( $pool_names, $lb_methods, $memberAofA );

Get member port number

function F5GetMemberPort {
    [CmdletBinding()]
    param(
        [Parameter(mandatory=$true)][String]$VMName,
        [Parameter(mandatory=$false)][String]$Partition = 'Common',
        [Parameter(mandatory=$true)][String]$Pool
    )
    (Get-F5.iControl).ManagementPartition.set_active_partition( (,"$($Partition)"))
    $PoolMembers = (Get-F5.iControl).LocalLBPool.get_member_v2((,$Pool))[0] | Where-Object { $_.address -like "/$($Partition)/$($VMName)" } 
    if ($PoolMembers.length -gt 1) {
        Write-Error "Found more than one member $($VMName) in pool $($Pool)"
        break
    } 
    elseif ($PoolMembers.length -lt 1) {
        Write-Error "Could not find member $($VMName) in pool $($Pool) "
        break
    }
    else {
        return $PoolMembers[0].port
    }
}

Get Pool health monitor template

(Get-F5.icontrol).LocalLBPool.get_monitor_association(@('spencer2')).monitor_rule

Get all health monitor templates

(Get-F5.icontrol).LocalLBMonitor.get_template_list()

Add health monitor to pool (overwriting all existing health monitors on that pool)

$monitor_association = New-Object -TypeName iControl.LocalLBPoolMonitorAssociation;
$monitor_association.pool_name = 'spencer';
$monitor_association.monitor_rule = New-Object -TypeName iControl.LocalLBMonitorRule;
$monitor_association.monitor_rule.type = "MONITOR_RULE_TYPE_SINGLE";
$monitor_association.monitor_rule.quorum = 0;
$monitor_association.monitor_rule.monitor_templates = (, '/Common/Spencer');
(Get-F5.iControl).LocalLBPool.set_monitor_association((,$monitor_association))

Add health monitor to pool (append to existing health monitors)

$monitor_association = New-Object -TypeName iControl.LocalLBPoolMonitorAssociation;
$monitor_association.pool_name = 'foo';
$monitor_association.monitor_rule = New-Object -TypeName iControl.LocalLBMonitorRule;
$monitor_association.monitor_rule.type = "MONITOR_RULE_TYPE_AND_LIST";
$monitor_association.monitor_rule.quorum = 0;
$existing_templates = (Get-F5.icontrol).LocalLBPool.get_monitor_association(@('foo')).monitor_rule
$existing_templates += '/Common/bar'
$monitor_association.monitor_rule.monitor_templates = (,$existing_templates);
(Get-F5.iControl).LocalLBPool.set_monitor_association((,$monitor_association))

Get all f5 partitions

$allPartitions = (Get-F5.iControl).ManagementPartition.get_partition_list()
# Return a string of all partition names available on F5
return $allPartitions.partition_name
}
view raw f5.md hosted with ❤ by GitHub