
K8sのトラブルシューティングからiptablesの詳細分析まで:Linuxファイアウォールのルールをハンズオンで学ぶ
コンテキスト
昨日、Kubernetesクラスターの障害に対処していたとき、技術チームは典型的な課題に遭遇した:
ワーカー・ノードがNodePortメソッド経由でクラスタ・サービスにアクセスすると、接続に失敗します。
数時間にわたる徹底的な調査の結果、障害の根本的な原因は、最終的にノードのファイアウォールルールセットの異常な設定であることが判明した。
このトラブルシューティングの過程で、複雑なシナリオにおける従来のiptablesコマンドラインツールの限界が完全に露呈し、視覚的な診断ツールの作成が生まれました。
iptablesの基本
4つの時計と5つの鎖
テーブルが4つ:
- - filter:ファイアウォールルールのためにパケットをフィルターする。
- - nat: Network Address Translation。パケットの送信元または宛先IPアドレスを変更するために使用される。
- - mangle:パケット内容の変更。パケットの内容や優先度などを変更するのに使われる。
- - raw:コネクション・トラッキングなしでパケットを処理する方法を制御する。
5本の鎖(チェーン):
- - PREROUTING: 宛先アドレスがローカルであるパケットの処理;
- - INPUT:マシンに入ってきてローカルにルーティングされるパケットの処理;
- - FORWARD:すべての転送パケット;
- - OUTPUT:ローカルで生成された送信パケットの処理;
- - POSTROUTING:マシンから出るパケットの処理。
4つのテーブルのデフォルトの順序は、raw -> mangle -> nat -> filterである。
略称:rmnf->guidance(5ストロークコード)
インバウンドルーティングのプレルーティングフェーズ (PREROUTING)
外部トラフィックの流入 → ├─ rawテーブル(優先度1):コネクション・トラッキングの例外処理 ├─ mangleテーブル(優先度2):TOS/TTLなどのパケット・ヘッダの変更 └─ natテーブル(優先度3):DNATによる宛先アドレス変換の実行 順序:123
ルーティング決定段階
mangle table (priority 2): 複雑なパケット変更をサポート └─ filter table (priority 4): 転送ポリシーを定義 (default deny) │ └─ filter table (priority 4): 転送ポリシーを定義 (default deny) │ └─ order: 24
ローカル発信フェーズ (OUTPUT)
ローカルプロセスがトラフィックを生成 → ├─ rawテーブル(優先度1):アウトバウンドのコネクション・トラッキング例外 ├─ mangleテーブル(優先度2):アウトバウンドのパケット・ヘッダを変更 ├─ natテーブル(優先度3):SNATソース・アドレス変換を実行 └─ filterテーブル(優先度4):アウトバウンドの最終フィルタリング順序は1234
ポストアウトバウンドルーティングフェーズ(POSTROUTING)
マシンから離れる準備 → ├─ mangleテーブル(優先度2):最後の修正チャンス(TTLなど) └─ natテーブル(優先度3):SNAT/MASQUERADEオーダー完了 23
iptables コマンド形式
iptables -t table-name [-A|-D|-F|-L|-Z|-N|-X|-P|-E|-I] chain-name [match-criteria] [-j process-action].
- - テーブル名
- -t: -table, 操作するテーブルを指定する。指定しない場合、デフォルトはフィルター・テーブルとなる。
- - コマンド
- -A: -append、指定したチェーンにルールを追加する。
- -D: -delete、指定したチェーンからルールを削除する。
- -F: -flush, 指定されたチェーンのすべてのルールをクリアする。
- -L: -list, 指定されたチェーンのすべてのルールをリストする。
- -Z: -ゼロ、指定したチェーンのカウンターをクリアする。
- -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 なしはデフォルトのフィルタテーブル)
iptables -L
- - 指定したテーブルのルールを表示する
iptables -t nat -L
- - 指定されたチェーンのルールを表示する
iptables -L INPUT
- - 指定したチェーンのルール番号を表示
iptables -L INPUT --行番号
- - 指定したチェーンのルール番号とカウンターを見る
iptables -L INPUT --line-numbers --verbose
- - 指定したチェーンのルール番号とカウンターを表示し、ツリーとして表示する。
iptables -L INPUT --line-numbers --verbose --list
- - 全チェーンのルールを表示 (-L チェーンなしは全チェーンのルール、-t なしはデフォルトのフィルタテーブル)
スクリプト
膨大なiptablesルールがもたらすトラブルシューティングのジレンマに直面して、従来のコマンドラインツールは3つの核心的欠陥を露呈した:
- - 情報過多の問題:何千ものルールが直線的なテキストで提示され、チェーン間のジャンプのロジックを追跡するのは難しい。
- - 意味的断絶: -j KUBE-SERVICESのようなターゲットチェーンの文脈的解釈の欠如。
- - 変更のリスク:生産ルールの直接編集は、サービスの中断につながる可能性がある。
- このスクリプトは、ツリーの視覚化とカラーマーキング、インテリジェントな循環 参照の検出、対話的選択のサポート、複数の環境との互換性、一時ファイルの 自動クリーンアップなどを通じて、iptablesルールチェックの効率を大幅に向 上させるように設計されている。
以下のスクリプトは、centos 7.6 (3.10.0-957.el7.x86_64)上でのみ実験的にパスします。
スクリプトの内容
vim show_iptables.sh #!/bin/bash #指定したiptablesテーブルの連鎖を動的に解析し、ツリー構造で表示するスクリプト #セット-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'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: \Kw+' | 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'.} #target chain (strict checksum) 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" ¦ "s/(-[pm] |--(src|dport|sport|destination| match|)match))/${CYAN}\1${NC}/g" } #ツリー印刷 (重大な修正) print_tree() { local chain=$1 local prefix=$2 local visited=$3 local depth=$4 # ヌルチェーン名の防御 if [[ -z "$chain" ]]; thenecho -e "${prefix}${RED} Invalid null link name ${NC}" return fi # ループ検出 if [[ "$visited" == *"|$chain|"* ]]; then echo -e "${prefix}${RED} └── Circular reference: $chain${NC}" return fi # 深度制限 if (( depth > 15 )); then echo -e "${prefix}${YELLOW} └── Maximum depth reached ${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 -rtarget; do if [[ -n "$target" && ! " ${targets[*]} " =~ " $arget " ]]; then targets+=("$arget") fi done <<< "$(extract_targets "$rule")" done # print current chain localcolor case $((depth % 6)) in 0) color=$BLUE;; 1) color=$GREEN;; 2) color=$PURPLE;; 3) color=$CYAN;; 4) color=$YELLOW.カラー=$YELLOW;; esac echo -e "${prefix}${colour}├─ ${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} description:" echo -e " ${GRAY} ▪ ルール${NC}のグレーエントリ" echo -e "${RED} 赤 ${NC} ジャンプ対象を示す" echo -e " ${CYAN} シアン ${NC} 一致条件を示す" echo -e "${BLUE} ▏チェーン [${selected_chain}] トポロジ: ${NC}" print_jptree "$selected_chain" "" 0 echo "" }. #実施プロセス #1. select テーブル tables=($(get_tables)) if [[ ${#tables[@]} -eq 0 ]]; then echo -e "${RED} error: no iptables table ${NC} found" 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. select_chains iptables-save -t "$selected_table" > "$TEMP_FILE" chains=($(extract_chains)) if [[ ${#chains[@]} -eq 0 ]]; then echo -e"${RED} error: no chain ${NC} found in table ${selected_table}" rm -f "$TEMP_FILE" exit 1 fi echo "Chains available in table ${selected_table}:" selected_chain in "${#{#chains[@]} -eq 0]]; then echo -echain in "${chains[@]}"; do if [[ -n "$selected_chain" ]]; then break else echo -e "${RED} is not validly selected, please re-enter ${NC}" fi done #3.実行解析メイン rm -f "$TEMP_FILE"
試験
- - 出力を見るために2つのルールを生成する
#フィルターテーブルの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 テーブルフィルタで使用可能なチェーン: 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-fwcali163c2dd037c 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-_pJvVwNIJS_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 #チェーン [INPUT] トポロジー: ・グレーのエントリーはルール 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 "├── ├── ├── │ ├── ▪ -p ipv4 -m comment -m comment -m set --match-set cali40all-hosts-net src -m addrtype --dst-type LOCAL -j ACCEPT ├── │ ├── ▪ -pipv4 -m comment -m comment -j DROP ├─ │ ├─ ▪ -i cali+ -m comment -g cali-wl-to-host ├─ │ ├─ ▪ -m comment -m mark --mark 0x10000/0x10000 -j ACCEPT ├─ │ ├─ ▪ -mcomment -j mark --set-xmark 0x0/0xf0000 ├─ │ ├─ ▪ -m comment -j cali-from-host-endpoint ├─ │ ├─ ▪ -m comment -m mark --mark 0x10000/0x10000 -jACCEPT ├─ │ ├─ ▪ -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 --ログ接頭辞 "CALI_TEST_RULE "
上記のスクリプトを使用すると、iptablesルールのトポロジカルな関係をすばやく表示でき、理解やデバッグが容易になる。