Azure Load Balancer -- 如何实现应用平滑变更
继上一篇介绍了通过 Azure Application Gateway 的 Connection Drain 功能实现应该用平滑变更后,这一篇来介绍一下 Azure Load Balancer 如果希望实现同样的业务目标该如何来做。与 AGW 不同 ALB 作为 L4 负载均衡器,并没有原生给出 Connection Drain 的功能,但是我们可以基于 ALB 运行状态探测检查失败后对于在执行的请求的处理机制来实现类似 Connection Drain 的功能。 ALB 通过定义探针规则(Probe)来实现后端池内服务器的健康检查,当后端服务器探针监测超时后将被视为不健康节点,新建连接或请求将不在向该节点进行分发,对于在执行的请求或连接 ALB 继续保持连接,如果请求或连接未完成可继续访问到该节点。利用该功能,我们同样可以实现应用的平滑变更,当我们在 ALB 后端池中有多个节点时,软件新版本的 Roll Out 可以逐个节点依次完成,并且在线变更可以做到对 on the fly 业务无损,在变更过程中我们可以通过在变更节点上的 NIC 上增加 NSG 规则来拒绝来自 ALB 的探针(ALB 的探针源地址永远是 168.63.129.16/32),此时该节点将拒绝响应 ALB 的探针请求,所以新建请求和连接不再向该节点发送,该节点上现有请求和连接不收到影响继续维持转发,在新版本的 Standard ALB 上对于运行状态探测检查失败后 ALB 的处理行为还做了增强,当 ALB 后端池内所有节点都处于不健康状态时,当前后端池内所有在执行的请求或连接可以继续保持,这个增强也使用户在做应用升级变更时也可以选择批量将所有节点进行升级,升级后统一重新上线。
下面我们来做一个简单的 Demo 来验证一下上述行为,在一个 Standard ALB 后端有两台 VM,上面运行着仿真的 Server 端代码,代码可参考(https://github.com/nonokangwei/agwconnectiondrain/blob/master/httpserver.py),由客户端代码仿真一个 WebSocket 连接,用来模拟一个持续运行的请求。
import socket import time import datetime target_host = "X.X.X.X" # your ALB frontend Public IP target_port = 80 # create a socket object client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) def decode_frame(frame): opcode_and_fin = frame[0] payload_len = frame[1] print(payload_len) encrypted_payload = frame [2 : payload_len] payload = bytearray([ encrypted_payload[i] for i in range(payload_len - 2)]) return payload # connect the client client.connect((target_host,target_port)) # send some data request = "GET /chat HTTP/1.1\r\nHost:%s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\nSec-WebSocket-Protocol: chat, superchat\r\nSec-WebSocket-Version: 13\r\n\r\n" % target_host close = "GET /index.html HTTP/1.1\r\nHost:%s\r\n\r\n" % target_host data = "PUT /chat HTTP/1.1\r\nHost:\r\n\r\n" #% target_host frame = [0] frame += [len(data)] frame_to_send = bytearray(frame) + bytearray(data, 'utf-8') mark = 0 while True: val = input("run again?") if val == "no": client.send(close.encode()) response = client.recv(4096) http_response = repr(response) print(http_response) client.close() break else: if mark == 1: print(datetime.datetime.now()) while True: try: client.send(frame_to_send) response = client.recv(4096) http_response = response.decode('utf-8') time.sleep(2) except: print(datetime.datetime.now()) raise else: client.send(request.encode()) # receive some data response = client.recv(4096) http_response = response.decode('utf-8') print('response', http_response) mark = 1
有客户端代码发起向 ALB 前端 IP 的仿真 HTTP 连接,并将该连接升级为 WebSocket 连接, 执行后从 Server 端日志中判断出当前该 WebSocket 连接所在处理节点,然后在对应节点的网卡 NSG 规则上增加明细拒绝探针规则。
检查已拒绝 ALB 探针节点上的打印日志,会发现 WebSocket 仍然处于保持状态,但 ALB 的探针已经不再接收,此时 ALB 后端池中该节点已被视为不健康状态。使用其它客户端尝试发起新建连接,所有新建请求都会送往剩余的健康节点,在处理 WebSocket 的节点不再处理新建请求。与 AGW 不同的是,ALB 上没有 Connection Drain Timeout 的概念,即不会在指定时间内主动关闭在执行请求,如想实现类似效果,可以通过脚本或代码在服务器内实现。之后再次尝试将所有节点的 NSG 上增加拒绝探针规则,可以看到前面在运行的 WebSocket 会话仍然可以转发。
小功能打不通,希望通过上面的介绍对大家有所帮助,可以将 Azure 的功能发挥到极致。