
从K8s故障排查到iptables深度解析:手把手教你玩转Linux防火墙规则
背景
在昨日处理一起Kubernetes集群故障时,技术团队遭遇典型挑战:
某工作节点通过NodePort方式访问集群服务时出现连接失败现象。
历经数小时深入排查,最终锁定故障根源为节点防火墙规则集异常配置。
此次排障过程充分暴露了传统iptables命令行工具在复杂场景下的局限性,并催生了可视化诊断工具的诞生。
iptables基础知识
四表五链
四表:
- • filter: 过滤数据包,用于防火墙规则。
- • nat: 网络地址转换,用于修改数据包的源或目的IP地址。
- • mangle: 数据包内容修改,用于修改数据包的内容或者优先级等。
- • raw: 控制不经过连接跟踪的数据包处理方式。
五链(chain):
- • PREROUTING: 对于目标地址是本机的数据包的处理;
- • INPUT: 对于进入本机并被路由到本地的数据包的处理;
- • FORWARD: 对于所有转发出去的数据包的处理;
- • OUTPUT: 对于本地产生的向外发送的数据包的处理;
- • POSTROUTING: 对于离开本机的数据包的处理。
默认的四表的顺序为:raw -> mangle -> nat -> filter
简记为:rmnf–>制导(五笔编码)
▌入站路由前阶段(PREROUTING)
外部流量进入 → ├─ raw表(优先级1):处理连接跟踪例外 ├─ mangle表(优先级2):修改TOS/TTL等包头 └─ nat表(优先级3):执行DNAT目标地址转换 顺序为:123
▌路由决策阶段
根据路由表判断走向 → ├─ 本机处理分支 → INPUT链 │ ├─ mangle表(优先级2):最终包标记调整 # 有一种说法是NAT表在INPUT链中也执行 │ └─ filter表(优先级4):实施入站过滤策略 │ 顺序为:234 │ └─ 转发处理分支 → FORWARD链 ├─ mangle表(优先级2):支持复杂包修改 └─ filter表(优先级4):定义转发策略(默认拒绝) 顺序为24
▌本机外发阶段(OUTPUT)
本地进程产生流量 → ├─ raw表(优先级1):出站连接跟踪例外 ├─ mangle表(优先级2):修改出站包头 ├─ nat表(优先级3):执行SNAT源地址转换 └─ filter表(优先级4):最终出站过滤 顺序为1234
▌出站路由后阶段(POSTROUTING)
准备离开本机 → ├─ mangle表(优先级2):最后修改机会(如TTL) └─ nat表(优先级3):完成SNAT/MASQUERADE 顺序为23
iptables命令格式
iptables -t 表名 [-A|-D|-F|-L|-Z|-N|-X|-P|-E|-I] 链名 [匹配条件] [-j 处理动作]
- • 表名
- • -t: –table,指定要操作的表。如果不加,默认为filter表。
- • commands
- • -A: –append,向指定的链中追加一条规则。
- • -D: –delete,从指定的链中删除一条规则。
- • -F: –flush,清空指定链的所有规则。
- • -L: –list,列出指定链中的所有规则。
- • -Z: –zero,清空指定链的计数器。
- • -N: –new-chain,创建一条新的链。
- • -X: –delete-chain,删除一条自定义的链。
- • -P: –policy,设置链的默认策略。
- • -E: –rename-chain,重命名一条链。
- • -I: –insert,在指定的链中插入一条规则。
- • 匹配条件
- • -p: –protocol,指定协议类型。 例如,-p tcp表示只匹配TCP协议的数据包。
- • -s: –source,指定源IP地址。例如,-s 192.168.1.100表示只匹配来自该IP的数据包。
- • -d: –destination,指定目的IP地址。例如,-d 192.168.1.100表示只匹配到该IP的数据包。
- • -i: –in-interface,指定进入本机的网络接口。例如,-i eth0表示只匹配通过该接口的数据包。
- • -o: –out-interface,指定离开本机的网络接口。例如,-o eth0表示只匹配通过该接口的数据包。
- • –sport, –sport: 指定源端口。例如,–sport 80表示只匹配来自该端口的TCP数据包。
- • –dport, –dport: 指定目的端口。例如,–dport 80表示只匹配到该端口的TCP数据包。
- • 处理动作
- • -j: –jump,指定处理动作。例如,-j ACCEPT表示接受数据包,-j DROP表示丢弃数据包。-j LOG表示记录日志。-j RETURN表示返回,不再继续匹配后续规则。
iptables常用命令
- • 查看iptables规则
- • 查看所有链的规则(-L不写链是所有链的规则,不加-t是默认filter表)
iptables -L
- • 查看指定表的规则
iptables -t nat -L
- • 查看指定链的规则
iptables -L INPUT
- • 查看指定链的规则编号
iptables -L INPUT --line-numbers
- • 查看指定链的规则编号和计数器
iptables -L INPUT --line-numbers --verbose
- • 查看指定链的规则编号和计数器,并以树状显示
iptables -L INPUT --line-numbers --verbose --list
- • 查看所有链的规则(-L不写链是所有链的规则,不加-t是默认filter表)
脚本
面对海量iptables规则带来的排查困境,传统命令行工具暴露出三大核心缺陷:
- • 信息过载问题:数千条规则以线性文本呈现,链间跳转逻辑难以追溯
- • 语义断裂现象:-j KUBE-SERVICES等目标链缺乏上下文解释
- • 变更风险隐患:直接编辑生产规则易引发服务中断
- 针对以上问题,编写以下脚本,该脚本通过树状可视化与颜色标记,智能检测循环引用,支持交互式选择,兼容多环境,自动清理临时文件,显著提升iptables规则排查效率。
以下脚本仅在centos7.6(3.10.0-957.el7.x86_64)上实验通过。
脚本内容
vim show_iptables.sh #!/bin/bash #脚本用于动态分析指定iptables表的链关系并以树状结构展示 #set-x #定义颜色(兼容更多终端) RED=$'\033[31m' GREEN=$'\033[32m' YELLOW=$'\033[33m' BLUE=$'\033[34m' PURPLE=$'\033[35m' CYAN=$'\033[36m' GRAY=$'\033[90m' NC=$'\033[0m' #临时文件 TEMP_FILE="/tmp/iptables_rules.txt" #全局关联数组(显式声明) declare -A VISITED_CHAINS #获取所有可用表 get_tables() { if [[ -f /proc/net/ip_tables_names ]]; then cat /proc/net/ip_tables_names 2>/dev/null else # 兼容旧系统 iptables -L -n 2>/dev/null | grep -Po 'Table: \K\w+' | sort -u fi } #提取链名(增加过滤) extract_chains() { grep -E "^:[A-Za-z0-9_-]+ " "$TEMP_FILE" | cut -d ' ' -f 1 | tr -d ':' | grep -v '^$' } #获取链的规则(增强过滤) find_rules_for_chain() { local chain=$1 [[ -z "$chain" ]] && return grep -E "^-A $chain " "$TEMP_FILE" | sed '/^#/d' } #提取目标链(严格校验) extract_targets() { local rule=$1 echo "$rule" | grep -oP '\s-(j|g)\s+\K[^\s]+' | grep -E '^[A-Za-z0-9_-]+$' } #规则格式化(防御性处理) format_rule() { local rule=$1 # 移除链声明和注释 rule=$(echo "$rule" | sed -E 's/^-A [^ ]* //; s/(--comment "[^"]*")//g') # 高亮关键元素 echo "$rule" | sed -E \ -e "s/(-j |-g )([^ ]+)/${RED}\1${YELLOW}\2${NC}/g" \ -e "s/(-[pm] |--(src|dport|sport|destination|match))/${CYAN}\1${NC}/g" } #树状打印(关键修复) print_tree() { local chain=$1 local prefix=$2 local visited=$3 local depth=$4 # 空链名防御 if [[ -z "$chain" ]]; then echo -e "${prefix}${RED} 无效空链名${NC}" return fi # 循环检测 if [[ "$visited" == *"|$chain|"* ]]; then echo -e "${prefix}${RED}└── 循环引用: $chain${NC}" return fi # 深度限制 if (( depth > 15 )); then echo -e "${prefix}${YELLOW}└── 达到最大深度${NC}" return fi # 记录访问链(安全写入) if [[ -n "$chain" ]]; then VISITED_CHAINS["$chain"]=1 fi # 获取规则 local rules=() while IFS= read -r rule; do rules+=("$rule") done <<< "$(find_rules_for_chain "$chain")" # 提取子链 local targets=() for rule in "${rules[@]}"; do while IFS= read -r target; do if [[ -n "$target" && ! " ${targets[*]} " =~ " $target " ]]; then targets+=("$target") fi done <<< "$(extract_targets "$rule")" done # 打印当前链 local color case $((depth % 6)) in 0) color=$BLUE;; 1) color=$GREEN;; 2) color=$PURPLE;; 3) color=$CYAN;; 4) color=$YELLOW;; *) color=$RED;; esac echo -e "${prefix}${color}├── ${chain}${NC}" # 打印规则 local rule_prefix="│ " for rule in "${rules[@]}"; do echo -e "${prefix}${rule_prefix}${GRAY}├─ ▪ ${NC}$(format_rule "$rule")" done # 打印子链 local total=${#targets[@]} for i in "${!targets[@]}"; do local target=${targets[$i]} if (( i == total - 1 )); then print_tree "$target" "${prefix} └── " "${visited}|$chain|" $((depth + 1)) else print_tree "$target" "${prefix} ├── " "${visited}|$chain|" $((depth + 1)) fi done } #主程序 main() { echo -e "${GREEN}■ iptables链关系拓扑 (规则内联显示) ■${NC}" echo -e "${YELLOW}说明:" echo -e " ${GRAY}▪ 灰色条目为规则${NC}" echo -e " ${RED}红色${NC}表示跳转目标" echo -e " ${CYAN}青色${NC}表示匹配条件\n" echo -e "${BLUE}▏ 链 [${selected_chain}] 拓扑:${NC}" print_tree "$selected_chain" "" "" 0 echo "" } #执行流程 #1. 选择表 tables=($(get_tables)) if [[ ${#tables[@]} -eq 0 ]]; then echo -e "${RED} 错误:未找到任何iptables表${NC}" exit 1 fi echo "可用iptables表:" select selected_table in "${tables[@]}"; do if [[ -n "$selected_table" ]]; then break else echo -e "${RED} 无效选择,请重新输入${NC}" fi done #2. 选择链 iptables-save -t "$selected_table" > "$TEMP_FILE" chains=($(extract_chains)) if [[ ${#chains[@]} -eq 0 ]]; then echo -e "${RED} 错误:表 ${selected_table} 中未找到任何链${NC}" rm -f "$TEMP_FILE" exit 1 fi echo "表 ${selected_table} 的可用链:" select selected_chain in "${chains[@]}"; do if [[ -n "$selected_chain" ]]; then break else echo -e "${RED} 无效选择,请重新输入${NC}" fi done #3. 执行分析 main rm -f "$TEMP_FILE"
测试
- • 生成两条规则查看输出
#在filter表的INPUT链创建测试规则 sudo iptables -t filter -A INPUT -p tcp --sport 12345 -j LOG --log-prefix "FILTER_TEST " #创建测试规则(记录日志但永不匹配实际流量) sudo iptables -t filter -A cali-INPUT -p tcp --sport 65535 -j LOG --log-prefix "CALI_TEST_RULE "
- • 执行脚本
./show_iptables.sh 可用iptables表: 1) raw 2) mangle 3) filter 4) nat #? 3 表 filter 的可用链: 1) INPUT 19) cali-from-hep-forward 2) FORWARD 20) cali-from-host-endpoint 3) OUTPUT 21) cali-from-wl-dispatch 4) DOCKER 22) cali-fw-cali163c2dd037c 5) DOCKER-ISOLATION-STAGE-1 23) cali-fw-caliceb7f36db92 6) DOCKER-ISOLATION-STAGE-2 24) cali-pri-_56duOTW9GxmBnwvgZx 7) DOCKER-USER 25) cali-pri-_RRPF6JYgiXDfvzOhm- 8) KUBE-EXTERNAL-SERVICES 26) cali-pri-_pJvVwNmnIJS_Hgp2My 9) KUBE-FIREWALL 27) cali-pro-_56duOTW9GxmBnwvgZx 10) KUBE-FORWARD 28) cali-pro-_RRPF6JYgiXDfvzOhm- 11) KUBE-KUBELET-CANARY 29) cali-pro-_pJvVwNmnIJS_Hgp2My 12) KUBE-NODEPORTS 30) cali-to-hep-forward 13) KUBE-PROXY-CANARY 31) cali-to-host-endpoint 14) KUBE-SERVICES 32) cali-to-wl-dispatch 15) cali-FORWARD 33) cali-tw-cali163c2dd037c 16) cali-INPUT 34) cali-tw-caliceb7f36db92 17) cali-OUTPUT 35) cali-wl-to-host 18) cali-cidr-block #? 1 ■ iptables链关系拓扑 (规则内联显示) ■ 说明: ▪ 灰色条目为规则 红色表示跳转目标 青色表示匹配条件 ▏ 链 [INPUT] 拓扑: ├── INPUT │ ├─ ▪ -m comment -j cali-INPUT │ ├─ ▪ -m comment -j KUBE-NODEPORTS │ ├─ ▪ -m conntrack --ctstate NEW -m comment -j KUBE-EXTERNAL-SERVICES │ ├─ ▪ -j KUBE-FIREWALL │ ├─ ▪ -p tcp -m tcp --sport 12345 -j LOG --log-prefix "FILTER_TEST " ├── ├── cali-INPUT ├── │ ├─ ▪ -p ipv4 -m comment -m comment -m set --match-set cali40all-hosts-net src -m addrtype --dst-type LOCAL -j ACCEPT ├── │ ├─ ▪ -p ipv4 -m comment -m comment -j DROP ├── │ ├─ ▪ -i cali+ -m comment -g cali-wl-to-host ├── │ ├─ ▪ -m comment -m mark --mark 0x10000/0x10000 -j ACCEPT ├── │ ├─ ▪ -m comment -j MARK --set-xmark 0x0/0xf0000 ├── │ ├─ ▪ -m comment -j cali-from-host-endpoint ├── │ ├─ ▪ -m comment -m comment -m mark --mark 0x10000/0x10000 -j ACCEPT ├── │ ├─ ▪ -p tcp -m tcp --sport 65535 -j LOG --log-prefix "CALI_TEST_RULE "
- • 清理测试规则
sudo iptables -t filter -D INPUT -p tcp --sport 12345 -j LOG --log-prefix "FILTER_TEST " sudo iptables -t filter -D cali-INPUT -p tcp --sport 65535 -j LOG --log-prefix "CALI_TEST_RULE "
通过以上脚本,能够快速的查看iptables规则的拓扑关系,便于理解和调试。