从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数据包。
- -p: --protocol,指定协议类型。 例如,
- 处理动作
- -j: --jump,指定处理动作。例如,
-j ACCEPT
表示接受数据包,-j DROP
表示丢弃数据包。-j LOG
表示记录日志。-j RETURN
表示返回,不再继续匹配后续规则。
- -j: --jump,指定处理动作。例如,
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规则的拓扑关系,便于理解和调试。
评论区