☰
F5 REST API自动化实操:Python创建负载均衡四件套
2026/10/15 15:34:57 网站建设 项目流程

上一篇文章里我们把F5的iControl REST接口怎么通、Python环境怎么搭、第一次调通GET /mgmt/tm/ltm/virtual的细节基本讲完了。这篇直接进正题,把负载均衡日常交付里最常用的四件套——monitor、pool、node、vs——一次建完,并且把我在实际环境中踩过的坑一并写出来。

这个系列适合两类人:一类是运维或交付工程师,日常工作里要反复在F5的Web界面里点鼠标创建后端节点、建负载均衡池、绑健康检查、发布虚拟服务器,嫌慢又容易漏;另一类是刚接触“用代码管设备”的网工,想看看Python调F5 API到底能自动化到什么程度。这篇不会停留在“调通接口”的层面,而是直接给你一套可以抄作业的脚本思路,从连接封装、对象创建、依赖顺序到重复执行的幂等处理,全部串起来。

1. 整体思路:直接requests裸调,不迷信F5 SDK

1.1 为什么选requests而不是F5 SDK

F5官方有Python SDK,叫f5-sdk,也提供配置工具CCCL。但我的建议是:如果只是做配置交付而非平台级的持续管理,直接用requests裸调REST API反而更省心。

原因有三个。第一,F5的REST API本身非常稳定,核心路径就是/mgmt/tm/ltm/下面那几个资源,不需要SDK再做一层封装;第二,SDK的版本跟进不一定跟得上设备固件版本,我遇到过SDK某个方法在特定版本上报错,查源码发现是字段映射问题,裸调反而一清二楚;第三,裸调的调试成本低,抓包或者看返回的JSON直接就能定位问题。

当然,SDK也有它的价值,比如自动处理token过期、自带建模。但对于“创建monitor、pool、node、vs”这种短平快的交付任务,requests足够,还能少装一个依赖。

1.2 工程结构怎么摆

先看整体结构,我习惯用一个目录放所有脚本,核心分三个文件:

f5_auto/ ├── requirements.txt ├── f5_client.py # API连接与基础请求封装 └── create_ltm_objects.py # 创建四件套的主脚本

requirements.txt里只有一行:

requests>=2.20.0

依赖越少,后面扔到跳板机或者CI流水线里跑的时候越不容易出幺蛾子。

1.3 前置信息准备清单

动手之前,先把这几个信息列出来,脚本里都要用:

信息项示例说明
F5管理地址lb-mgmt.example.com注意走管理口还是业务口
账号api_deploy建议单独建账号,别用admin
密码环境变量传递不要写死在脚本里
分区Common跨分区引用时要写成/Common/xxx
虚拟服务器IP10.1.1.10对外的VS地址
后端节点10.0.0.10/10.0.0.11真实业务服务器
业务端口80对外和回源都用HTTP 80

账号这件事多说一句。生产环境最好给自动化脚本创建独立账号,权限范围缩小到目标分区。否则某天误操作把/Common里的配置全刷了,光恢复就有得受。

2. 连接与会话封装:把认证和请求统一管起来

2.1 F5认证的两种方式

F5的REST API认证方式主要是两种:HTTP Basic认证和Token认证。

Basic认证最简单,直接把用户名密码放到请求头里,适合脚本和短任务。Token认证是先调/mgmt/shared/authn/login换一个token,后续请求用X-F5-Auth-Token头携带,适合频繁调用的场景,避免每次请求都带明文密码。

下面的客户端类用的是Basic认证,代码最直观:

# f5_client.py import os import requests import urllib3 urllib3.disable_warnings() class F5Client: def __init__(self, host=None, user=None, password=None, timeout=15): self.base = "https://{}/mgmt/tm/ltm".format(host or os.getenv("F5_HOST")) self.timeout = timeout self.session = requests.Session() self.session.auth = ( user or os.getenv("F5_USER"), password or os.getenv("F5_PASSWORD"), ) self.session.verify = False def get(self, path, params=None): url = "{}/{}".format(self.base, path.strip("/")) return self.session.get(url, params=params, timeout=self.timeout) def post(self, path, body, params=None): url = "{}/{}".format(self.base, path.strip("/")) return self.session.post(url, json=body, params=params, timeout=self.timeout) def delete(self, path): url = "{}/{}".format(self.base, path.strip("/")) return self.session.delete(url, timeout=self.timeout)

2.2 为什么关闭SSL校验

F5默认证书是自签的,用verify=False跳过证书校验,顺手用urllib3.disable_warnings()去掉告警。如果你对内网环境的安全要求比较高,也可以提前把F5的CA证书导入到系统信任链,然后把verify改成证书路径,代码层面不用变。

一个容易被忽略的细节是timeout。F5在配置比较大的时候,某些POST操作会超过默认的几秒钟,比如创建VS时触发了profile关联,返回时间可能变长。我在客户端类里设了15秒超时,批量创建大配置时可以再放宽一些。

2.3 关于F5 API的返回状态码

F5的REST API有个容易让人懵的点:POST创建成功后返回的状态码是200,而不是很多REST服务习惯的201 Created。别看到200就以为“没创建成功,只是通了”,只要返回体里有对象的name和selfLink,就是创建成功了。

另外,F5的API对错误状态的表达也不完全统一。比如创建重复对象时,某些版本返回400,某些版本返回409 Conflict。所以写脚本时别只检查状态码,还要把返回体里的message字段打出来看。

3. 核心创建实战:node、monitor、pool、vs逐个过

3.1 先建node:后端节点的基本面

F5里的node,对应的就是一台真实后端服务器的IP地址。它本身不绑定端口,因为一个node可以配合多个服务端口使用,比如10.0.0.10:80和10.0.0.10:443可以同时存在。

创建node的API路径是:

POST /mgmt/tm/ltm/node

请求体最简形式:

body = { "name": "10.0.0.10", "address": "10.0.0.10", "partition": "Common", "description": "backend web server 01" }

这里有个关键点:name和address虽然经常一样(都是IP),但含义不同。name是F5对象名,address是真实IP。如果你先在一个环境用IP命名,到了另一个环境可能想用主机名命名,比如web-01,那address照样填IP就行。

创建node时还可以直接挂“节点级监控”,但我一般不在这里做。原因后面讲pool的时候会说明。

3.2 创建monitor:健康检查怎么构造才不踩坑

monitor是F5里最容易配错的一类对象。它负责定时探测后端节点的健康状态,探测失败就自动把节点摘除。

创建HTTP monitor的API路径是:

POST /mgmt/tm/ltm/monitor/http

请求体示例:

body = { "name": "http_monitor", "defaultsFrom": "/Common/http", "interval": 10, "timeout": 30, "send": "GET /healthz HTTP/1.0\r\nHost: web.example.com\r\n\r\n", "recv": "200 OK", "partition": "Common" }

逐个字段说:

  • defaultsFrom:必填,表示继承哪个内置模板。这里是继承F5自带的/Common/http,然后基于它修改探测参数。
  • interval:探测间隔,单位秒。10秒一次是相对合理的默认值。
  • timeout:超时时间,单位秒。这里设30秒,意味着F5认为节点不可达的门槛是30秒。
  • send:F5发送的探测请求内容。注意HTTP协议里换行必须是\r\n,字符串里写成\r\n。
  • recv:响应里必须包含的字符串。如果响应内容不包含200 OK,F5就判定节点down。

关于interval和timeout的关系,建议保持timeout至少是interval的3倍。比如10秒探一次,30秒超时,这样F5可以在约3个探测周期内判定节点状态,避免一次慢响应就误判。我见过有人把interval设成5秒、timeout也设成5秒,结果后端稍微慢一点,节点健康状态就来回抖动。

另一个坑是send里的Host头。很多Web服务器对HTTP/1.0的请求要求必须带Host,否则直接返回400 Bad Request。如果F5发出去的探测请求不带Host,目标服务器根本理都不会理,monitor永远显示red。所以生产环境里我习惯把Host头写进send里,跟真实业务域名保持一致。

3.3 创建pool:把node和monitor绑到一起

pool是F5 LTM里的核心容器,它把一组后端节点聚合起来,统一绑定一个健康检查,并决定负载均衡算法。

创建pool的API路径是:

POST /mgmt/tm/ltm/pool

请求体示例:

body = { "name": "web_pool", "partition": "Common", "monitor": "/Common/http_monitor", "loadBalancingMode": "round-robin", "members": [ { "name": "10.0.0.10:80", "address": "10.0.0.10", "port": 80 }, { "name": "10.0.0.11:80", "address": "10.0.0.11", "port": 80 } ] }

这里有几个细节:

monitor字段可以直接写/Common/http_monitor。如果这个monitor不存在,F5会直接报错。所以顺序上必须先建monitor再建pool,这个依赖关系在后面编排脚本时要守住。

members字段里的每个元素,name格式是“node名:端口”。这个member概念跟node不同:node只代表IP,member才代表“某个IP的某个端口”。创建pool时直接在members里写address和port,F5会自动寻找或创建对应的node对象。我们前面已经手动建了node,所以这里的member会关联到已有的node上。

loadBalancingMode默认是round-robin。如果后端机器配置差异大,可以改成least-connections-node,F5会按当前连接数调度。生产环境建议按业务类型选,默认round-robin不是万金油。

3.4 创建vs:对外流量的入口

vs(virtual server)是真正对外的流量入口,它把虚拟IP:端口映射到pool上。

创建vs的API路径是:

POST /mgmt/tm/ltm/virtual

请求体示例:

body = { "name": "vs_web", "partition": "Common", "destination": "10.1.1.10%80", "mask": "255.255.255.255", "pool": "/Common/web_pool", "sourceAddressTranslation": { "type": "automap" }, "profiles": [ { "name": "/Common/tcp", "context": "all" }, { "name": "/Common/http", "context": "all" } ] }

里面最有迷惑性的是destination字段。F5用的是%来分隔IP和端口,不是冒号。写成10.1.1.10:80会直接报错,因为F5把这个字符串整体当作地址解析。我第一次写的时候就是这里卡了半小时,报错信息还不直观。

mask字段也值得注意。如果虚拟IP是单个IP,这里必须写255.255.255.255。如果是一个网段,比如10.1.1.0%80,mask就要写成255.255.255.0。同时这个vs会占用整个网段的80端口,使用时要非常小心。

sourceAddressTranslation是源地址转换,automap表示自动映射为F5当前出接口的IP。这里用新式写法,不要用旧字段snat。

profiles这个数组指定VS启用的协议栈。HTTP服务的VS建议显式挂http和tcp两个profile,注意context要写成all。如果你只是做TCP四层转发,不挂http也行,但tcp还是建议显式指定。

3.5 一个完整的创建函数清单

前面这些片段拆开看不直观,把它们放进一个主脚本里更清晰。下面是我常用的写法,每个对象对应一个函数,主流程里依次调用。

# create_ltm_objects.py import sys from f5_client import F5Client def create_node(api, ip, description=""): body = { "name": ip, "address": ip, "partition": "Common", "description": description, } return api.post("node", body) def create_http_monitor(api, name, host, uri="/healthz", interval=10, timeout=30): body = { "name": name, "partition": "Common", "defaultsFrom": "/Common/http", "interval": interval, "timeout": timeout, "send": "GET {} HTTP/1.0\r\nHost: {}\r\n\r\n".format(uri, host), "recv": "200 OK", } return api.post("monitor/http", body) def create_pool(api, name, monitor_name, member_list): members = [ { "name": "{}:{}".format(ip, port), "address": ip, "port": port, } for ip, port in member_list ] body = { "name": name, "partition": "Common", "monitor": "/Common/{}".format(monitor_name), "loadBalancingMode": "round-robin", "members": members, } return api.post("pool", body) def create_vs(api, name, vip, port, pool_name): body = { "name": name, "partition": "Common", "destination": "{}%{}".format(vip, port), "mask": "255.255.255.255", "pool": "/Common/{}".format(pool_name), "sourceAddressTranslation": {"type": "automap"}, "profiles": [ {"name": "/Common/tcp", "context": "all"}, {"name": "/Common/http", "context": "all"}, ], } return api.post("virtual", body) def main(): api = F5Client() BAKCEND_NODES = [ ("10.0.0.10", 80), ("10.0.0.11", 80), ] create_node(api, "10.0.0.10", "web01") create_node(api, "10.0.0.11", "web02") create_http_monitor(api, "http_monitor", "web.example.com") create_pool(api, "web_pool", "http_monitor", BAKCEND_NODES) create_vs(api, "vs_web", "10.1.1.10", 80, "web_pool") if __name__ == "__main__": sys.exit(main())

注意,上面代码里把create_node单独调了两次,是刻意为之。如果后续想在node上追加描述、加节点级监控,node对象必须提前存在。直接在pool里写address让F5自动建node也能用,但node对象是隐式创建的,后续不好单独管理。

4. 全流程编排:执行顺序、幂等与配置保存

4.1 为什么执行顺序必须是node到monitor到pool再到vs

创建顺序不是随便定的,背后是F5的对象依赖关系:

  1. 先建node,因为pool的member要关联node。
  2. 再建monitor,因为pool的monitor字段要求引用一个已存在的monitor,否则POST直接失败。
  3. 然后建pool,因为vs的pool字段要求引用已存在的pool。
  4. 最后建vs,它只依赖pool。

依赖关系简单说就是:vs吃pool,pool吃monitor和node。倒过来建,每一步都会报错。

4.2 重复执行不报错:幂等处理

生产环境里你的脚本不会只跑一次。可能是同事重复执行,可能是CI里多次触发。如果不做幂等处理,第二次跑就会创建重复对象报错。

幂等逻辑很朴素:创建前先GET查一下对象是否存在,存在就跳过,不存在才创建。

def ensure_object(api, collection_path, object_name, body_factory): resp = api.get("{}/{}".format(collection_path, object_name)) if resp.status_code == 200: print("[SKIP] {} already exists: {}".format(collection_path, object_name)) return resp.json() if resp.status_code == 404: resp = api.post(collection_path, body_factory()) print("[CREATED] {}: {}".format(collection_path, object_name)) return resp.json() raise RuntimeError( "unexpected status {}: {}".format(resp.status_code, resp.text) )

调用时这样用:

ensure_object( api, "node", "10.0.0.10", lambda: { "name": "10.0.0.10", "address": "10.0.0.10", "partition": "Common", }, )

这个函数的精髓在于:判断标准是404还是200。F5返回别的状态码时直接抛异常,避免把权限问题、网络问题当成“对象不存在”处理。

4.3 配置保存:不save,重启就丢

这是最容易翻车的一步。通过REST API创建的对象,默认只在内存运行配置里生效,不会自动进入到启动配置。设备重启后,你用脚本建的所有node、pool、vs全部消失。

F5保存配置的接口不是TMSH里的save sys config,而是:

POST /mgmt/tm/sys/config

请求体:

body = { "command": "save" }

我习惯在创建脚本最后执行一次保存。注意一次全量save在配置量大的设备上可能需要几十秒,超时时间要放宽。

另外,F5某些型号在集群同步场景下还需要额外触发config sync,这里不展开,但生产环境一定要确认你的变更是否需要同步到对端设备。

5. 排错实录:遇到最多的问题与排查方法

5.1 401和403:认证与权限问题

401通常是账号密码错误。排掉账号密码因素后,要检查是不是账号被锁定或启用了双因子认证。

403通常是权限不够。F5的RBAC比较细,如果账号只有某个分区的操作权限,访问其他分区资源就会403。报错信息里一般会提示not authorized之类的关键字。解决方法是给账号加对应分区的权限,或者统一把脚本要操作的对象都建在目标分区内。

5.2 400:看似创建失败,其实请求体有坑

F5的400最常见原因有三种:

第一种是monitor创建时缺了defaultsFrom字段。这个字段不填,F5不知道怎么继承参数模板,直接报错。

第二种是pool里monitor字段指向了不存在的monitor。比如monitor建在/Common,pool引用时却写成http_monitor而不是/Common/http_monitor,在不同的分区场景下会解析不到。

第三种是vs的destination格式错误。写成冒号分隔的IP:端口,F5解析不了。牢记%分隔。

5.3 member一直unknown或者red

配置都创建成功了,但pool里的member状态不对,这时先分三个层面排查:

第一层,看node状态。如果node本身就是red,说明F5连基础连通性都没有,检查后端服务器的防火墙、路由和ARP。

第二层,看monitor是否真的在工作。把send里的URI改成后端服务器实际存在的路径,很多场景里后端健康检查路径是/healthz,但应用实际只响应/,返回404或者301,monitor就会判red。

第三层,看recv匹配是否合理。recv字段是“响应里必须包含的字符串”,如果F5的HTTP响应是HTTP/1.1 200 OK,你写recv: "HTTP/1.0 200"就匹配不上。调试时可以先在F5上用TMSH命令modify monitor http_monitor recv ""临时放开匹配,或者干脆把recv改成一个短字符串200试试。

5.4 VS建好了但访问不通

VS建好不代表能通,我列一个排查顺序:

  1. 看VS状态是不是green。如果VS本身是red,说明pool里没有可用member。
  2. 检查VS的VLAN限制。默认VS对所有VLAN生效,但如果你指定了vlans字段,来源VLAN不在列表内就会被丢弃。
  3. 检查目的地址和端口。用F5侧tcpdump抓包看有没有流量到达VS,有流量但没转出去,多半是SNAT或路由问题。
  4. 确认pool的member端口是否跟VS端口匹配。VS的80端口转给pool的member,如果member端口写成了8080,后端没监听8080,当然不通。

5.5 常见问题速查表

现象直接原因处理办法
POST返回401认证失败检查用户名密码、账号是否锁定
POST返回403RBAC权限不足给账号分配目标分区权限
POST返回400,monitor报错缺defaultsFrom补上defaultsFrom: "/Common/http"
POST返回400,pool报错monitor不存在先建monitor,引用写成/Common/xxx
POST返回400,vs报错destination格式错误用%分隔IP和端口,如10.1.1.10%80
member显示red健康检查路径或recv不匹配确认send和recv与真实响应一致
VS是redpool没有可用member查member状态和monitor
重启后配置丢失没有savePOST/mgmt/tm/sys/config保存

6. 扩展建议:从“脚本”到“平台”

四件套创建脚本跑通之后,最自然的下一步是把它做成配置驱动的工具。把每套环境里的业务参数抽出来放到JSON或者YAML文件里,脚本只负责解析和执行,这样你不需要为每个业务都改一遍代码。

比如建一套代理配置,只需写一个这样的输入文件:

project: demo_web vip: 10.1.1.10 vport: 80 pool: web_pool monitor: http_monitor nodes: - ip: 10.0.0.10 port: 80 - ip: 10.0.0.11 port: 80

脚本读取后自动补齐依赖、校验参数、创建对象、最后save。这样再接到几十个业务的交付需求时,你的工作量就从“每个业务写一遍脚本”变成“每个业务写一段YAML”,效率和正确率完全不是一个量级。

我个人在实际操作中还有一个体会:F5的REST API返回的JSON信息量很大,创建完别只打印status_code,把返回对象里的selfLink或generation字段打出来,后面排查配置漂移和做变更记录都很有用。另外,脚本里所有账号密码务必走环境变量或密钥管理服务,不要出现在代码仓库里。这个坑,一旦踩到就是安全事故,不是开玩笑的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询