RDPファームのLoadBalancerとしてのHAProxy。 0ドルの信頼できるソリューション

かなり偶然、時代遅れの2X-LoadBalancerとMSの重くてあいまいなRemote Connection Brokerに代わる受動的な検索で、HAProxyとそのRDPトラフィックをプロキシする機能に出会いました。 検索エンジンの結果では、haproxyは実際にはRDPのプロキシとして発行されません 。 今、彼は突然バンドルを配り始めました。 ただし、上記の機能など、同じ機能を備えた商用製品にはかなりの費用がかかります。



一般的に、これは誰かに興味があるように思えました。 したがって、この決定を強調することにしました。 さらに、最後に、有名な競合他社にはないHAProxyを使用する柔軟性を示します。



簡単に機能する仕組み



HAProxyはRDPを識別し、プロキシ化し、rdp_cookieを解析して必要な情報をフェッチし、ルーティングメカニズムで使用できます。



クライアントはプロキシに接続し、rdp_cookieからログインを引き出し、そのサーバーを選択し、「login-server」値をスティックテーブルに書き込み、ユーザーをサーバーに接続します。

したがって、次回同じクライアントが(このログインで)接続されると、プロキシはテーブル内のエントリを見つけ、ユーザーが開いているセッションを持っているサーバーに接続します。 華麗でシンプル!



スティックテーブルは、プロセスメモリに保存されているテーブルで、各レコードについて寿命を判断できます。 8時間に設定すると、クライアントは1日中同じサーバーにアクセスできます。



以下は標準設定です:



#/usr/local/etc/haproxy.conf global daemon stats socket /var/run/haproxy.sock mode 600 level admin stats timeout 2m defaults log global mode tcp option tcplog option dontlognull frontend fr_rdp mode tcp bind *:3389 name rdp log global option tcplog tcp-request inspect-delay 2s tcp-request content accept if RDP_COOKIE default_backend BK_RDP backend BK_RDP mode tcp balance leastconn timeout server 5s timeout connect 4s log global option tcplog stick-table type string len 32 size 10k expire 8h stick on rdp_cookie(mstshash),bytes(0,6) stick on rdp_cookie(mstshash) option tcp-check tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS01 172.16.50.11:3389 weight 10 check server TS02 172.16.50.12:3389 weight 20 check server TS03 172.16.50.13:3389 weight 10 check server TS04 172.16.50.14:3389 weight 20 check server TS05 172.16.50.15:3389 weight 10 check server TS06 172.16.50.16:3389 weight 10 check server TS07 172.16.50.17:3389 weight 20 check server TS08 172.16.50.18:3389 weight 20 check listen stats bind *:9000 mode http stats enable #stats hide-version stats show-node stats realm Haproxy\ Statistics stats uri /
      
      





難しさ



スティックテーブルはメモリ内にあるため、プロセスを再起動すると、クライアントとサーバーのペアに関するすべての情報が失われます。これは、この場合の重要な情報です。 この状況から抜け出すために、プロセスを再起動するために使用するスクリプトを書きました。 プロセスを停止する前のスクリプトは、スティックテーブルをファイルにスローし、プロセスの開始後にデータを書き戻します(現在のセッションは中断しません)。



 #!/usr/bin/env python import sys import socket import re import subprocess haproxyConf = '/usr/local/etc/haproxy.conf' def restart(): backends = [] with open(haproxyConf) as f: for line in f: lines = line.split(' ') if lines[0] == 'backend': backends.append(lines[1].strip('\n')) for backend in backends: getDataTables(backend) rebootHa() for backend in backends: insertDataTables(backend) # Writes data from stik-tables to external files def getDataTables(table): print table tmp_f = open('/tmp/tmp.' + table,'w') tableVal = {} c = socket.socket( socket.AF_UNIX ) c.connect("/var/run/haproxy.sock") c.send("prompt\r\n") c.send("show table " + table + "\r\n") d = c.recv(10240) for line in d.split('\n'): if re.search('^[a-zA-Z_0-9]',line): line = line.split(' ') del line[0] for item in line: key = item.split('=')[0] val = item.split('=')[1] tableVal[key] = val print tableVal['key'] print tableVal['server_id'] tmp_f.write(tableVal['key'] + ',' + tableVal['server_id'] + '\n') tmp_f.close() def rebootHa(): subprocess.call("/usr/local/etc/rc.d/haproxy reload", shell=True) # Writes data from files to stik-tables def insertDataTables(table): tmp_f = open('/tmp/tmp.' + table,'r') c = socket.socket( socket.AF_UNIX ) c.connect("/var/run/haproxy.sock") c.send("prompt\r\n") for line in tmp_f: line = line.split(',') print "set table " + table + " key " + line[0] + " data.server_id " + line[1] c.send("set table " + table + " key " + line[0] + " data.server_id " + line[1] +"\r\n") c.recv(10240) c.close() restart()
      
      





他に何?



特定のクライアントをプロキシするサーバーを柔軟に制御することもできます。 これは、ログイン、IPアドレス、ネットワーク、時刻などに基づいて実行できます。

ADのグループに基づいて、ファームサーバーを部門に分割する方法の例を示します。例:



アカウンティング用の2つのサーバー

マーケティング用の2つのサーバー

セールスマン用の2つのサーバー

みんなのために2つ。



サーバーの各グループには、インストール済みのソフトウェアと特定の設定など、異なる容量が存在する可能性があることは明らかなので、それらを分離します。



特定のポリシーに基づいて、HAProxyを使用すると、ユーザーを接続するサーバーを柔軟に決定でき、すべてのRDP接続に1つのエントリポイントを使用できます(間違いなく便利です)。



これを行うには、HAProxy構成と再起動スクリプトをわずかに変更する必要があります。



 #/usr/local/etc/haproxy.conf global daemon stats socket /var/run/haproxy.sock mode 600 level admin stats timeout 2m defaults log global mode tcp option tcplog option dontlognull frontend fr_rdp mode tcp bind *:3389 name rdp #timeout client 1h log global option tcplog tcp-request inspect-delay 2s tcp-request content accept if RDP_COOKIE acl Accounting_ACL rdp_cookie(mstshash),bytes(0,6) -m str -i -f /usr/local/etc/haproxy/Accounting acl Marketing_ACL rdp_cookie(mstshash),bytes(0,6) -m str -i -f /usr/local/etc/haproxy/Marketing acl Sales_ACL rdp_cookie(mstshash),bytes(0,6) -m str -i -f /usr/local/etc/haproxy/Sales use_backend Accounting_BK if Accounting_ACL use_backend Marketing_BK if Marketing_ACL use_backend Sales_BK if Sales_ACL default_backend DF_RDP backend DF_RDP mode tcp balance leastconn log global option tcplog stick-table type string len 32 size 10k expire 8h stick on rdp_cookie(mstshash),bytes(0,6) option tcp-check tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS01 172.16.50.11:3389 weight 10 check server TS02 172.16.50.12:3389 weight 10 check backend Accounting_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on rdp_cookie(mstshash),bytes(0,6) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS03 172.16.50.13:3389 weight 10 check server TS04 172.16.50.14:3389 weight 10 check backend Marketing_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on rdp_cookie(mstshash),bytes(0,6) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS05 172.16.50.15:3389 weight 10 check server TS06 172.16.50.16:3389 weight 10 check backend Sales_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on rdp_cookie(mstshash),bytes(0,6) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS07 172.16.50.17:3389 weight 10 check server TS08 172.16.50.18:3389 weight 10 check listen stats bind *:9000 mode http stats enable #stats hide-version stats show-node stats realm Haproxy\ Statistics stats uri /
      
      





変更された再起動スクリプト:



 #!/usr/bin/env python import sys import ldap import socket import re import subprocess ldapDomain = '' ldapUser = '' ldapPass = '' ldapDN = '' # OU=GROUPS,DC=domain,DC=tld' haproxyConf = '/usr/local/etc/haproxy.conf' action = sys.argv[1] # Get users from Active Directory Groups and store it to files def getADGroups(): l = ldap.open(ldapDomain) l.simple_bind_s(ldapUser,ldapPass) f = open('/usr/local/etc/haproxy/' + groupName,'w') results = l.search_s("cn=%s, %s" % (groupName, ldapDN), ldap.SCOPE_BASE) for result in results: result_dn = result[0] result_attrs = result[1] if "member" in result_attrs: for member in result_attrs["member"]: f.write(member.split(',')[0].split('=')[1] + '\n') f.close() restart() # Searching stik-tables to save it and to restore after reload def restart(): backends = [] with open(haproxyConf) as f: for line in f: lines = line.split(' ') if lines[0] == 'backend': backends.append(lines[1].strip('\n')) for backend in backends: getDataTables(backend) rebootHa() for backend in backends: insertDataTables(backend) # Writes data from stik-tables to external files def getDataTables(table): print table tmp_f = open('/tmp/tmp.' + table,'w') tableVal = {} c = socket.socket( socket.AF_UNIX ) c.connect("/var/run/haproxy.sock") c.send("prompt\r\n") c.send("show table " + table + "\r\n") d = c.recv(10240) for line in d.split('\n'): if re.search('^[a-zA-Z_0-9]',line): line = line.split(' ') del line[0] for item in line: key = item.split('=')[0] val = item.split('=')[1] tableVal[key] = val print tableVal['key'] print tableVal['server_id'] tmp_f.write(tableVal['key'] + ',' + tableVal['server_id'] + '\n') tmp_f.close() def rebootHa(): #pass subprocess.call("/usr/local/etc/rc.d/haproxy reload", shell=True) # Writes data from files to stik-tables def insertDataTables(table): #pass tmp_f = open('/tmp/tmp.' + table,'r') #tableVal = {} c = socket.socket( socket.AF_UNIX ) c.connect("/var/run/haproxy.sock") c.send("prompt\r\n") for line in tmp_f: line = line.split(',') print "set table " + table + " key " + line[0] + " data.server_id " + line[1] c.send("set table " + table + " key " + line[0] + " data.server_id " + line[1] +"\r\n") c.recv(10240) c.close() if action == 'restart': restart() if action == 'group': groupName = sys.argv[2] getADGroups()
      
      





仕組み:

グループはADで作成されます(おそらく既にそのようなグループがあります)。アカウント、マーケティング、販売、従業員はこれらのグループに配置されます。 スクリプトはADに接続し、選択したグループの従業員のリストを受け取ります。 従業員のリストは、グループの名前とともにファイルに保存されます。



HAProxy構成では、ACLはこれらのグループファイルのソースで構成されます。 新しい従業員がグループに追加された場合、スクリプトを実行してグループファイルを更新する必要があります。



プロキシは、指定されたファイルにログインがあるかどうかを確認します。 存在する場合、このグループに固有のバックエンドに送信します。 すべてが非常に簡単です!



スクリプト起動オプション:

haproxy.py group group_name-グループの再起動、現在のセッションは中断しません。

haproxy.py restart-プロセスを再起動(構成を再読み込み)しますが、現在のセッションは中断しません。



耐障害性



彼女はそこにいません



この例では、ソリューションに耐障害性はありません。



まず、haproxyは予約されていません。



第二に、クライアントサーバーの値をスティックテーブルに書き込むソリューションでは、haproxyがユーザーをレコードが既にテーブルにあるライブサーバーに接続することを許可せず、接続されたサーバーは現在利用できません。 彼はオンラインではないにもかかわらず、テーブルからサーバーにそれらを送信しようとします。



まず、haproxyバックアップはさまざまな方法で実行できます。



それらの1つは、変更された再起動スクリプトです。 クラウンでこのスクリプトを定期的に起動することで、保存されたテーブルを別のhaproxyのテーブルにコピーしてロードすることができます。

おかげでvasilevkirill 、彼はコメントで共有した組み込みソリューションがあります

habrahabr.ru/post/335872/#comment_10369854



二番目はもっと難しいです。 サーバーで何が起こっているかを正確に判断するメカニズムが必要です。 サーバーは、何らかの正当な理由であまり正当な理由ではないが、たとえば1分間など、ネットワーク上で利用できない場合があります。 ただし、同時にすべてのRDPセッションを開いてください。 サーバーが使用できなくなったと判断し、すべてのユーザーを他のサーバーに切り替える必要がある場合、未保存のデータを取得したり、クライアントが大量の作業を失ったりする可能性があります。



技術的には、スティックテーブルクリーンアップの実装は簡単です。 サーバーの状態を監視するには、さまざまな監視システムを使用できます。 同じZabbixでは、イベントでローカルスクリプトを呼び出すことができますが、この場合、スティックテーブルクリーニングスクリプトを呼び出すことができます。



結論として、上で示した欠点を考慮に入れて、HAProxyは非常に安定して確実に動作します。



All Articles