F5 iRules TCL 脚本入门实战

📅 2026/7/7 4:07:40 👁️ 阅读次数 📝 编程学习
F5 iRules TCL 脚本入门实战

学完前面三篇,你已经能完成 VIP 上线、配 Monitor 保障后端可用、配 SSL 卸载处理 HTTPS。

但如果要自定义流量处理逻辑——比如 URL 改写、按路径分发到不同 Pool、IP 白名单——靠标准配置就不够了。

这就是 iRules 的用武之地。

一、iRules 是什么

iRules 是 F5 上的TCL 脚本引擎,在流量经过 VS 时拦截,让你能写代码自定义处理逻辑。

客户端请求 → VS │ ├── Profile 栈(TCP/HTTP/SSL 正常处理) │ └── 🧩 iRules 在 Profile 处理中插入自定义逻辑 ├── 请求到达时 → 改 URL、插头、选 Pool ├── 响应返回时 → 改响应头、隐藏后端信息 └── 连接建立时 → IP 白名单过滤

二、TCL 基础语法(够用就行)

iRules 用 TCL 语言,但你不需要精通,记住 5 个核心语法就能覆盖 70% 场景:

变量

set my_var "hello" # 定义变量 log local0. "值是 $my_var" # 使用变量 set full_name "user_${my_var}" # 变量嵌套用 ${}

条件判断

if { [条件表达式] } { # 条件为真 } elseif { [另一个条件] } { # 另一个条件 } else { # 都不满足 }

字符串操作

if { [string equal $uri "/health"] } { ... } # 精确匹配 if { [string match "/api/*" $uri] } { ... } # 通配符匹配 set ext [string range $uri 0 3] # 取子串 set new_uri [string map {"/old/" "/new/"} $uri] # 替换

列表操作

set allowed_ips [list "10.0.0.1" "10.0.0.2"] foreach ip $allowed_ips { ... } # 遍历 if { [lsearch -exact $list $item] >= 0 } { ... } # 查找

正则

if { [regexp {^/api/v[0-9]+/} $uri] } { ... } # 匹配 regexp {user_id=([0-9]+)} $uri match user_id # 提取分组

三、iRules 事件模型——理解触发时机

iRules 是事件驱动的,在不同的时机挂不同的代码。

核心事件

CLIENT_ACCEPTED ← TCP 连接建立成功(做 IP 过滤) │ HTTP_REQUEST ← 收到完整 HTTP 请求头(90% 用这个) │ HTTP_REQUEST_DATA ← 收到请求体 │ [ 流量发往后端 ] │ HTTP_RESPONSE ← 收到后端返回的响应头(改写响应头) │ HTTP_RESPONSE_DATA ← 收到响应体

怎么选事件

你想什么时候处理流量? │ ├── TCP 连接建立时 → CLIENT_ACCEPTED ├── 请求头到达时 → HTTP_REQUEST(90% 的场景) ├── 响应头返回时 → HTTP_RESPONSE └── 响应体到达时 → HTTP_RESPONSE_DATA

四、9 个实战场景(从易到难)

场景 1:HTTP → HTTPS 跳转

when HTTP_REQUEST { if { [HTTP::header "X-Forwarded-Proto"] ne "https" } { HTTP::redirect "https://[HTTP::host][HTTP::uri]" } }

触发事件:HTTP_REQUEST效果:用户访问http://example.com/page→ 自动 301 到https://example.com/page


场景 2:插入真实客户端 IP

when HTTP_REQUEST { HTTP::header remove "X-Forwarded-For" # 删除伪造头 HTTP::header insert "X-Forwarded-For" [IP::client_addr] HTTP::header insert "X-Real-IP" [IP::client_addr] }

触发事件:HTTP_REQUEST效果:后端收到X-Forwarded-For: 客户端真实 IP,不再是 F5 的 IP


场景 3:URL 路径重写(去掉前缀)

when HTTP_REQUEST { if { [string match "/api/v1/*" [HTTP::uri]] } { # 把 /api/v1/user → /user set new_uri [string range [HTTP::uri] 7 end] HTTP::uri $new_uri } }

触发事件:HTTP_REQUEST效果:客户端请求/api/v1/user→ 后端实际收到/user


场景 4:按 URI 分发到不同 Pool

when HTTP_REQUEST { set uri [HTTP::uri] ​ if { [string match "/api/*" $uri] } { pool api-pool # API → API 池 } elseif { [string match "/static/*" $uri] } { pool static-pool # 静态资源 → 静态池 } else { pool web-pool # 其他 → 默认池 } }

触发事件:HTTP_REQUEST效果:一个 VIP 根据 URL 路径分发到不同后端集群


场景 5:按 User-Agent 分流(移动端 vs PC)

when HTTP_REQUEST { set ua [string tolower [HTTP::header "User-Agent"]] ​ if { [string match "*mobile*" $ua] || [string match "*iphone*" $ua] } { pool mobile-pool } else { pool pc-pool } }

触发事件:HTTP_REQUEST效果:手机用户和 PC 用户走不同的后端集群


场景 6:IP 白名单

when CLIENT_ACCEPTED { set allowed_subnets [list "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16"] set client_ip [IP::client_addr] set allowed false ​ foreach subnet $allowed_subnets { if { [IP::addr $client_ip equals $subnet] } { set allowed true break } } ​ if { !$allowed } { log local0. "拒绝非内网访问: $client_ip" reject } }

触发事件:CLIENT_ACCEPTED效果:非内网 IP 在 TCP 连接阶段直接断开,不给 HTTP 握手机会


场景 7:隐藏后端服务信息

when HTTP_RESPONSE { HTTP::header remove "Server" HTTP::header remove "X-Powered-By" HTTP::header remove "X-AspNet-Version" HTTP::header insert "Server" "F5 BIG-IP" }

触发事件:HTTP_RESPONSE效果:客户端看不到后端是 Nginx 还是 Tomcat


场景 8:IP 级速率限制

when HTTP_REQUEST { # 每 10 秒超过 100 次请求 → 429 if { [table lookup -subtable "rate_limit" [IP::client_addr]] > 100 } { HTTP::respond 429 content "Rate limit exceeded" \ "Content-Type" "text/plain" return } table incr -subtable "rate_limit" -timeout 10 [IP::client_addr] }

触发事件:HTTP_REQUEST效果:单个 IP 在 10 秒内超过 100 次请求,返回 429 Too Many Requests


场景 9:Cookie 会话保持

when HTTP_REQUEST { if { [HTTP::cookie exists "MY_SESSION"] } { set session_id [HTTP::cookie value "MY_SESSION"] persist uie $session_id # 固定到同一台后端 } } ​ when HTTP_RESPONSE { if { [HTTP::cookie exists "MY_SESSION"] } { persist uie [HTTP::cookie value "MY_SESSION"] } }

触发事件:HTTP_REQUEST+HTTP_RESPONSE效果:同一个 session 的请求始终发到同一台后端


五、创建和绑定 iRules

创建

# 方式一:从文件导入(推荐) tmsh create ltm rule my-url-rewrite from-local-file /shared/tmp/irule.tcl ​ # 方式二:直接创建 tmsh create ltm rule my-simple-rule # 进入交互模式,粘贴脚本后 Ctrl+C 退出

查看和修改

# 查看内容 tmsh list ltm rule my-url-rewrite ​ # 修改(重新导入) tmsh modify ltm rule my-url-rewrite from-local-file /shared/tmp/new-irule.tcl

绑定到 VS

# 创建 VS 时绑定 tmsh create ltm virtual myapp-vip-443 \ destination 10.0.0.100:443 \ pool myapp-pool \ rules { my-url-rewrite } ​ # 已有 VS 添加 tmsh modify ltm virtual myapp-vip-443 rules add { my-url-rewrite } ​ # 删除 tmsh modify ltm virtual myapp-vip-443 rules delete { my-url-rewrite } ​ # 查看绑了哪些 tmsh list ltm virtual myapp-vip-443 rules

删除 iRules

# 先从所有 VS 解绑 tmsh modify ltm virtual myapp-vip-443 rules delete { my-url-rewrite } ​ # 再删除规则 tmsh delete ltm rule my-url-rewrite

多个 iRules 的执行顺序

# iRules 按绑定顺序执行 tmsh modify ltm virtual myapp-vip-443 \ rules { ip-whitelist url-rewrite header-insert } ​ # 执行顺序: # 1. ip-whitelist 先执行(IP 过滤) # 2. url-rewrite 后执行(URL 改写) # 3. header-insert 最后执行(插请求头) # 任一规则 return 或 reject,后续规则不再执行

六、调试 iRules

写日志

when HTTP_REQUEST { log local0. "收到请求: [HTTP::uri]" log local0. "客户端 IP: [IP::client_addr]" }

看日志

# 实时查看 tmsh tail -f /var/log/ltm | grep -i "irule\|myapp" ​ # 打印所有请求头(排查用) when HTTP_REQUEST { foreach header [HTTP::header names] { log local0. "$header: [HTTP::header value $header]" } }

七、常见 iRules 错误

1. 没有事件声明

# ❌ 错误 set my_var "hello" HTTP::header insert "X-Test" $my_var ​ # ✅ 正确 when HTTP_REQUEST { set my_var "hello" HTTP::header insert "X-Test" $my_var }

2. 错误的事件中调用不支持的函数

# ❌ 错误:CLIENT_ACCEPTED 时还没有 HTTP 请求 when CLIENT_ACCEPTED { HTTP::header insert "X-Test" "test" } ​ # ✅ 正确:HTTP_REQUEST 时才能操作 HTTP 头 when HTTP_REQUEST { HTTP::header insert "X-Test" "test" }

3. reject 后没有 return

# ❌ 错误:reject 后代码继续执行 when HTTP_REQUEST { if { [IP::client_addr] eq "bad.ip" } { reject } pool special-pool # 即使 reject 了也会执行 } ​ # ✅ 正确 when HTTP_REQUEST { if { [IP::client_addr] eq "bad.ip" } { reject return } pool special-pool }

4. 变量嵌套错误

# ❌ 错误 set prefix "api" if { [string match "/$prefix/*" $uri] } { } # $p 被当成了变量 ​ # ✅ 正确:用 ${} 明确边界 if { [string match "/${prefix}/*" $uri] } { }

八、iRules 命令速查

请求处理

命令作用可用事件
HTTP::uri获取/设置请求 URIHTTP_REQUEST
HTTP::host获取请求的 HostHTTP_REQUEST
HTTP::header获取/设置/删除请求头HTTP_REQUEST
HTTP::cookie获取/设置 CookieHTTP_REQUEST
HTTP::redirect返回 301 跳转HTTP_REQUEST
HTTP::respond直接返回响应(不经过后端)HTTP_REQUEST
HTTP::status获取响应状态码HTTP_RESPONSE
IP::client_addr客户端 IP所有事件
IP::local_addrF5 自身 IP所有事件
reject拒绝连接CLIENT_ACCEPTED
pool选择后端 PoolHTTP_REQUEST

九、学习路径建议

阶段 1:读懂 → 能看懂别人写的 iRules → 知道 when HTTP_REQUEST 是什么意思 ​ 阶段 2:改 → 在现有 iRules 基础上改参数 → 改 URL 匹配规则、改 Pool 名称 ​ 阶段 3:写 → 独立写 10 行以内的简单 iRules → URL 重写、请求头插入、IP 白名单 ​ 阶段 4:组合 → 多个 iRules 组合使用 → 理解执行顺序和冲突

你现在看完这篇,大概在阶段 1~2 之间。把上面 9 个场景在测试环境跑一遍,就进入阶段 3 了。


十、小结

iRules 是 F5 最灵活、威力最大的功能之一。不需要精通 TCL 语言,记住 5 个基础语法 + 事件模型 + 9 个场景模板,就能应对日常工作中 80% 的流量控制需求。

相关阅读:

  • F5 VIP 上线全流程:Pool / VS / Profile 三板斧

  • F5 健康检查 Monitor 深入篇

  • F5 SSL Profile 证书卸载