SoftLayerではCitrix NetScalerという高機能な負荷分散装置が利用できます。
ただし現時点では、このNetScalerは、SoftLayerのAuto Scaleと連携した負荷分散には対応しておらず、Auto Scaleで追加された仮想サーバーを、自動的にNetScalerの負荷分散対象にすることができません。
そこで、以下のような機能を実現するPythonスクリプトを作成してみました。このスクリプトを使うと、SoftLayerのAuto ScaleとNetScalerを連携させることができます。
- SoftLayerのAuto Scaleで追加されたサーバーを、自動的にNetScalerの負荷分散クラスタに追加する
- SoftLayerのAuto Scaleで削除されたサーバーを、自動的にNetScalerの負荷分散クラスタから削除する
このスクリプトでは、SoftLayerの特定のAuto Scale Group(nsAutoScaleGroup)のサーバーが、SoftLayer上の仮想サーバーとして存在しているかどうかで、負荷分散対象とするかどうかを判断しています。実サーバーがHTTP等のサービスを提供しているかどうかはチェックしていませんが、負荷分散対象サーバーとして追加されても、NetScalerからのヘルスチェックで確認されない限りパケット転送対象とはならないので、HTTP等のサービスが起動していない状態であっても、ユーザーが利用するサービス自体には問題はありません。
あと一応動きますがかなりやっつけなので、もっと良い方法があるはず。エラー処理などもほとんどしてません。そのあたりは気が向いたらそのうち対応するかも。。
変更
- 2015/02/11
- 存在しないAuto Scale Groupを指定した場合のエラーハンドリングを追加
- nitroの例外処理を追加
前提
前提ソフトウェア
- Python 2.7
- SoftLayer API Python Client 3.3.0
- nsnitro 1.0.31
Python 2.7
Python 3.3だとnsnitroがエラーになる模様
$ python3.3 ns_test01.py
Traceback (most recent call last):
File "ns_test01.py", line 12, in <module>
from nsnitro import *
File "/Library/Python/2.7/site-packages/nsnitro/nsnitro.py", line 67
except urllib2.URLError, ue:
SoftLayer API
GitHub版を使用
$ sudo pip install git+git://github.com/softlayer/softlayer-python.git
NITRO API
このスクリプトはNetScaler NITRO APIというAPIを使用して、NetScalerを操作しています。
NITROは、RESTベースのインターフェースを提供しているAPIで、リモートからNetScalerを様々なプログラム言語で操作したり監視したりすることができます。
今回はPythonでスクリプトを作成するので、nsnitroというPythonのパッケージをインストールしておきます。
$ sudo pip install nsnitro
- NITRO API - Citrix eDocs
- nsnitro 1.0.31
- GitHub: Citrix Netscaler 9.2+ Python Library (NITRO API)
- API
スクリプト
変数の設定
変数 | 説明 |
---|---|
nsAutoScaleGroup | NetScalerの負荷分散対象とするSoftLayerのAuto Scale Group名 |
nsHost | NetScaler's IP address |
nsApiUser | NetScaler login user |
nsApiPass | NetScaler login password |
nsLBVServer | NetScaler Load Balancing Virtual Server |
nsIpType | SoftLayerの仮想サーバーのPublicとPrivateのどちらのアドレスを負荷分散対象とするかを指定。"public"の場合はPublic Primary IPが、それ以外の場合はPrivate Primary IPが使われる。 |
スクリプト
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import traceback
from prettytable import PrettyTable
import SoftLayer
import sluser
# import the necessary libraries for netscaler nitro
import json
import urllib
import httplib2
import time
from nsnitro import *
SL_USERNAME = sluser.SL_USERNAME
SL_API_KEY = sluser.SL_API_KEY
# NetScaler constant
nsAutoScaleGroup = "TK-as-group" # SoftLayer auto scale group name
nsHost = "192.168.1.141" # NetScaler's IP address
nsApiUser = "nsroot" # NetScaler login user
nsApiPass = "nsroot" # NetScaler login password
nsLBVServer = "vserver01_http" # NetScaler Load Balancing Virtual Server
nsIpType = "public" # IP type used for NetScaler server ip. "public" or "private"
def getASMembers(autoScaleGroups, nsAutoScaleGroup):
"""
Get an array of dictionaries of all members of nsAutoScaleGroup from SoftLayer API
:param autoScaleGroups: SoftLayer auto scale groups objects retrieved by getScaleGroups()
:param nsAutoScaleGroup: SoftLayer auto scale group name which the script looks at to add/delete servers
:return: Array of dictionaries of all auto scale members
"""
for asg in autoScaleGroups:
asMembers = []
if asg['name'] == nsAutoScaleGroup:
for a in asg['virtualGuestMembers'][0:]:
memberId = a['virtualGuest']['id']
asMember = {
"id": a['virtualGuest']['id'],
"name": a['virtualGuest']['hostname'],
"publicPrimaryIP" : client['Virtual_Guest'].getPrimaryIpAddress(id=memberId),
"privatePrimaryIP" : client['Virtual_Guest'].getPrimaryBackendIpAddress(id=memberId),
}
asMembers.append(asMember)
break
if len(asMembers) == 0:
sys.exit("No such auto scale group exists in SoftLayer auto scale configurations.")
return(asMembers)
def addNSServer(nsServerName, nsServerIp, nsServiceName, nsLBVServer):
"""
Add SoftLayer VSIs to NetScaler servers,
create new services and bind the services to LB virtual server.
Must be logged in to NetScaler with API beforehand.
:param nsServerName: The name of NetScaler server to be created.
:param nsServerIp: The IP address of NetScaler server to be created.
:param nsServiceName: The name of NetScaler service to be created.
:param nsLBVServer: The name of NetScaler LB virtual server to be binded with the service.
:return:
"""
# add server test
addserver = NSServer()
addserver.set_name(nsServerName)
addserver.set_ipaddress(nsServerIp)
NSServer.add(nitro, addserver)
# get state
server = NSServer()
server.set_name(nsServerName)
server = server.get(nitro, server)
# print(server.get_name() + ": " + server.get_state())
# add service
addservice = NSService()
addservice.set_name(nsServiceName)
addservice.set_servername(nsServerName)
addservice.set_servicetype("HTTP")
addservice.set_port(80)
NSService.add(nitro, addservice)
# bind service to lbvserver
lbbinding = NSLBVServerServiceBinding()
lbbinding.set_name(nsLBVServer)
lbbinding.set_servicename(nsServiceName)
lbbinding.set_weight(40)
NSLBVServerServiceBinding.add(nitro, lbbinding)
# get binding info
lbbinding = NSLBVServerServiceBinding()
lbbinding.set_name(nsLBVServer)
lbbindings = NSLBVServerServiceBinding.get(nitro, lbbinding)
# for lbb in lbbindings:
# print("sgn: " + lbb.get_servicegroupname())
return
def delNSServer(nsServerName):
"""
Delete NetScaler server.
Must be logged in to NetScaler with API beforehand.
:param nsServerName: The name of NetScaler server to be deleted.
:return:
"""
# delete server
delserver = NSServer()
delserver.set_name(nsServerName)
NSServer.delete(nitro, delserver)
# print("Server deleted.")
return
def getNsServersList(nitro):
"""
Get the list of NetScaler servers
:param nitro: nitro connection
:return: Array of NetScaler servers
"""
nsServers = []
server = NSServer()
for n in NSServer.get_all(nitro):
nsServers.append(n.get_name())
return nsServers
def getAutoScaleHostnamePrefix(autoScaleGroups, nsAutoScaleGroup):
"""
Get auto scale hostname prefix
:param autoScaleGroups:
:param nsAutoScaleGroup:
:return:
"""
for asg in autoScaleGroups:
if asg['name'] == nsAutoScaleGroup:
nsAutoScalePrefix = asg['virtualGuestMemberTemplate']['hostname']
return nsAutoScalePrefix
try:
# Connect to SoftLayer API
client = SoftLayer.Client(username=SL_USERNAME, api_key=SL_API_KEY)
autoScaleGroups = client['Account'].getScaleGroups()
asMembers = getASMembers(autoScaleGroups, nsAutoScaleGroup)
# get auto scale hostname prefix
nsAutoScalePrefix = getAutoScaleHostnamePrefix(autoScaleGroups, nsAutoScaleGroup)
# Login to NetScaler with API
nitro = NSNitro(nsHost, nsApiUser, nsApiPass)
nitro.login()
# Get the list of NetScaler servers
nsServers = getNsServersList(nitro)
# If an auto scale member is not in NetScaler severs,
# add the member to NetScaler server
asMemberNames = []
addedServers = []
for asm in asMembers:
# NetScaler parameters
nsServerName = asm['name']
nsPublicPrimaryIp = asm['publicPrimaryIP']
nsPrivatePrimaryIp = asm['privatePrimaryIP']
if nsIpType == "public":
nsServerIp = nsPublicPrimaryIp
else:
nsServerIp = nsPrivatePrimaryIp
nsServiceName = "service_http_" + nsServerIp.split(".")[-1] # NetScaler Service Name for the server. "service_http_<IP's 4th octet>"
if not asm['name'] in nsServers:
addNSServer(nsServerName, nsServerIp, nsServiceName, nsLBVServer)
addedServers.append(asm['name'])
# print("Server \"%s\" created." % asm['name'])
# make array of asm['name']
asMemberNames.append(asm['name'])
# If a NetScaler server with nsAutoPrefix prefix exists while not in auto scale member,
# delete the server from NetScaler servers
deletedServers = []
for nss in nsServers:
if nss[:-5] == nsAutoScalePrefix:
if not nss in asMemberNames:
delNSServer(nss)
deletedServers.append(nss)
# print("Server \"%s\" deleted." % nss)
# Logout from NetScaler
nitro.logout()
# Print results
print("No servers created.") if len(addedServers) == 0 else "Created: %s" % addedServers
print("No servers deleted.") if len(deletedServers) == 0 else "Deleted: %s" % deletedServers
except NSNitroError, e:
print e.message
except SystemExit as e:
sys.exit(e)
テスト
負荷分散対象サーバーとして登録されいない & 存在しないサーバーが負荷分散対象になっている場合
実行前
SoftLayer上のサーバー
- Auto Scale Group "TK-as-group"の仮想サーバーとして、"TK-as-member-e727"のみ存在
$ python sl_getAutoScaleGroup.py
+-------+------------------+-------+-----------+-----+-----+-----+---------------+---------+
| id | asg name | dc | status | min | max | now | member prefix | members |
+-------+------------------+-------+-----------+-----+-----+-----+---------------+---------+
| 54654 | autoscale_grp01 | hkg02 | SUSPENDED | 1 | 5 | 0 | asserver | |
| 34158 | TK-as-group | sng01 | ACTIVE | 1 | 5 | 1 | TK-as-member | e727 |
| 34758 | autosacle_yasuda | sng01 | SUSPENDED | 1 | 1 | 0 | sh1nobu | |
| 8254 | dai39-group | sng01 | ACTIVE | 1 | 1 | 1 | dai39-scale | cca8 |
+-------+------------------+-------+-----------+-----+-----+-----+---------------+---------+
NetScaler上の負荷分散設定
- "TK-as-member-e727"は負荷分散対象になっていない (serverに存在しない)
- "TK-as-member-1111", "TK-as-member-2222"が負荷分散対象になっている (serverに存在し、serviceが作成され、lb vserverにbindされている)
> show server -summary
-------------------------------------------
Name State
-------------------------------------------
1 192.168.1.254 ENABLED
2 192.168.1.253 ENABLED
3 192.168.1.134 ENABLED
4 192.168.1.136 ENABLED
5 192.168.1.144 ENABLED
6 192.168.1.18 ENABLED
7 192.168.1.143 ENABLED
8 192.168.1.133 ENABLED
9 TK-as-member-1111 ENABLED
10 TK-as-member-2222 ENABLED
Done
>
> show service -summary
---------------------------------------------------------------------------------------------
Name State IP Addr Port Protocol MaxClients MaxReqs
---------------------------------------------------------------------------------------------
1 serv...adns UP 192.168.1.144 53 ADNS 0 0
2 serv..._254 DOWN 192.168.1.254 80 HTTP 0 0
3 serv...p_18 DOWN 192.168.1.134 80 HTTP 0 0
4 serv...tp_1 DOWN TK-as-member-1111 80 HTTP 0 0
5 serv...tp_2 DOWN TK-as-member-2222 80 HTTP 0 0
Done
>
> show lb vserver vserver01_http
vserver01_http (192.168.1.143:80) - HTTP Type: ADDRESS
State: DOWN
Last state change was at Fri Jan 30 15:35:40 2015
Time since last state change: 1 days, 18:18:09.320
Effective State: DOWN
Client Idle Timeout: 180 sec
Down state flush: ENABLED
Disable Primary Vserver On Down : DISABLED
Appflow logging: ENABLED
Port Rewrite : DISABLED
No. of Bound Services : 3 (Total) 0 (Active)
Configured Method: LEASTCONNECTION
Mode: IP
Persistence: NONE
Vserver IP and Port insertion: OFF
Push: DISABLED Push VServer:
Push Multi Clients: NO
Push Label Rule: none
L2Conn: OFF
Skip Persistency: None
IcmpResponse: PASSIVE
RHIstate: PASSIVE
New Service Startup Request Rate: 0 PER_SECOND, Increment Interval: 0
Mac mode Retain Vlan: DISABLED
DBS_LB: DISABLED
Process Local: DISABLED
Traffic Domain: 0
1) service_http_18 (192.168.1.134: 80) - HTTP State: DOWN Weight: 1
2) service_http_1 (1.1.1.1: 80) - HTTP State: DOWN Weight: 1
3) service_http_2 (2.2.2.2: 80) - HTTP State: DOWN Weight: 1
Done
実行
$ python sl_lbNetScaler.py
Created: ['TK-as-member-e727']
Deleted: [u'TK-as-member-1111', u'TK-as-member-2222']
実行結果
- Auto Scale Groupに存在しない"TK-as-member-1111", "TK-as-member-2222"は、NetScalerの負荷分散対象から削除 (serverに存在しない)
- Auto Scale Groupに存在する"TK-as-member-e727"が、負荷分散対象に追加 (serverに追加され、serviceが作成され、lb vserverにbindされている)
> show server -summary
-------------------------------------------
Name State
-------------------------------------------
1 192.168.1.254 ENABLED
2 192.168.1.253 ENABLED
3 192.168.1.134 ENABLED
4 192.168.1.136 ENABLED
5 192.168.1.144 ENABLED
6 192.168.1.18 ENABLED
7 192.168.1.143 ENABLED
8 192.168.1.133 ENABLED
9 TK-as-member-e727 ENABLED
Done
>
> show service -summary
---------------------------------------------------------------------------------------------
Name State IP Addr Port Protocol MaxClients MaxReqs
---------------------------------------------------------------------------------------------
1 serv...adns UP 192.168.1.144 53 ADNS 0 0
2 serv..._254 DOWN 192.168.1.254 80 HTTP 0 0
3 serv...p_18 DOWN 192.168.1.134 80 HTTP 0 0
4 serv..._198 DOWN TK-as-member-e727 80 HTTP 0 0
Done
>
> show lb vserver vserver01_http
vserver01_http (192.168.1.143:80) - HTTP Type: ADDRESS
State: DOWN
Last state change was at Fri Jan 30 15:35:42 2015
Time since last state change: 1 days, 18:23:39.540
Effective State: DOWN
Client Idle Timeout: 180 sec
Down state flush: ENABLED
Disable Primary Vserver On Down : DISABLED
Appflow logging: ENABLED
Port Rewrite : DISABLED
No. of Bound Services : 2 (Total) 0 (Active)
Configured Method: LEASTCONNECTION
Mode: IP
Persistence: NONE
Vserver IP and Port insertion: OFF
Push: DISABLED Push VServer:
Push Multi Clients: NO
Push Label Rule: none
L2Conn: OFF
Skip Persistency: None
IcmpResponse: PASSIVE
RHIstate: PASSIVE
New Service Startup Request Rate: 0 PER_SECOND, Increment Interval: 0
Mac mode Retain Vlan: DISABLED
DBS_LB: DISABLED
Process Local: DISABLED
Traffic Domain: 0
1) service_http_18 (192.168.1.134: 80) - HTTP State: DOWN Weight: 1
2) service_http_198 (119.81.110.198: 80) - HTTP State: DOWN Weight: 40
Done
既に負荷分散対象サーバーとして登録されている場合
実行前
NetScaler上の負荷分散設定
- Auto Scale Groupに存在する"TK-as-member-e727"のみが負荷分散対象となっている
> show server -summary
-------------------------------------------
Name State
-------------------------------------------
1 192.168.1.254 ENABLED
2 192.168.1.253 ENABLED
3 192.168.1.134 ENABLED
4 192.168.1.136 ENABLED
5 192.168.1.144 ENABLED
6 192.168.1.18 ENABLED
7 192.168.1.143 ENABLED
8 192.168.1.133 ENABLED
9 TK-as-member-e727 ENABLED
Done
実行
- 実行しても負荷分散対象サーバーの追加・削除はされない
$ python sl_lbNetScaler.py
No servers created.
No servers deleted.