目 录CONTENT

文章目录

从K8s故障排查到iptables深度解析:手把手教你玩转Linux防火墙规则

Administrator
2025-08-04 / 0 评论 / 0 点赞 / 17 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

从K8s故障排查到iptables深度解析:手把手教你玩转Linux防火墙规则

背景

在昨日处理一起Kubernetes集群故障时,技术团队遭遇典型挑战:
某工作节点通过NodePort方式访问集群服务时出现连接失败现象。
历经数小时深入排查,最终锁定故障根源为节点防火墙规则集异常配置。
此次排障过程充分暴露了传统iptables命令行工具在复杂场景下的局限性,并催生了可视化诊断工具的诞生。

iptables基础知识

四表五链

四表:

  • filter: 过滤数据包,用于防火墙规则。
  • nat: 网络地址转换,用于修改数据包的源或目的IP地址。
  • mangle: 数据包内容修改,用于修改数据包的内容或者优先级等。
  • raw: 控制不经过连接跟踪的数据包处理方式。

五链(chain):

  • PREROUTING: 对于目标地址是本机的数据包的处理;
  • INPUT: 对于进入本机并被路由到本地的数据包的处理;
  • FORWARD: 对于所有转发出去的数据包的处理;
  • OUTPUT: 对于本地产生的向外发送的数据包的处理;
  • POSTROUTING: 对于离开本机的数据包的处理。

默认的四表的顺序为:raw -> mangle -> nat -> filter
简记为:rmnf-->制导(五笔编码)
iptables四表五链
iptables规则处理流程

▌入站路由前阶段(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
      

脚本

面对海量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规则的拓扑关系,便于理解和调试。

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区