深入理解 iptables 的流量控制

什麼是 iptables?

在 Linux 系統中,iptables 是一個強大的防火牆工具,用於配置對 IPv4 數據包過濾規則和網絡地址轉換(NAT)。它允許系統管理員控制進入、離開或經過系統的網絡數據包的流向和處理方式,從而增強系統的安全性、穩定性和性能。

iptables 是 Linux 內核中 Netfilter 子系統的一部分,它通過在數據包經過網絡協議棧時應用規則來實現過濾和轉發。通過 iptables,用戶可以定義規則集(也稱爲表)來決定如何處理不同類型的數據包,比如允許或拒絕特定 IP 地址或端口的數據包。

iptables 支持多種不同的表和鏈,每個表和鏈都有特定的功能和用途。用戶可以根據需要創建自定義規則,或使用預定義的規則集來快速配置網絡安全策略。

在本文中,我們將介紹 iptables 的基本概念、工作原理和常見用法,以及 iptables 在 k8s 中流量轉發的相關流程。

** iptables 五鏈四表**

首先看一下 iptables 對流量控制的大體流程, 如下圖:

對流量控制,iptables 通常通過五個主要的鏈(表)來處理,分別是:

當流量進入主機後,首先通過 prerouting 模塊判斷 是否爲本機的請求,如果流量屬於本機,則 通過 input 模塊 進入到用戶空間,然後通過 output 模塊,最後通過 postrouting 模塊轉發到網卡,如果不是本機的流量則通過 forward 將流量發送到 postrouting 轉發到網卡。

postrouting 階段結束後,iptables 將數據包發送到相應的鏈中處理。每個鏈都包含一系列規則,iptables 會按順序檢查這些規則,並根據規則的動作(接受、拒絕、轉發等)來處理數據包。

當一個規則匹配成功時,iptables 會根據規則的動作來處理數據包。常見的動作包括接受(ACCEPT)、拒絕(REJECT/DROP)、轉發(FORWARD)、重定向(REDIRECT)等。

如果沒有規則匹配成功,iptables 將根據表的默認策略來處理數據包。默認策略通常是接受或拒絕。

所以在 iptables 中主要的概念有 3 個:鏈(chain),表(table),動作(target)

在圖中出現的 prerouting,input,output,forward,postrouting。就是鏈 (chain)

其中鏈包括上面提到的五個 prerouting,input,output,forward,postrouting。表包括 filter 表,nat 表,mangle 表和 raw 表,他們分表代表什麼呢

鏈和表有什麼關係呢,一個鏈中可以有多個表,詳細見下圖

那麼動作 (target) 又是什麼,比如,我們在過濾一個 ip 的請求的時候,是否通過,就是處理的動作,它包括以下幾種:

在 iptables 中,鏈、表和動作之間的關係可以類比爲一個 API 服務開發結構。就像在 API 服務中會有各種接口,對應 iptables 中的鏈,而接口的實現則相當於 iptables 中的規則,而表則對應於數據庫的表。舉例來說,假設要實現一個規則允許 192.168.1.2 訪問 3306 端口,當請求到達時,首先會被轉發到 input 鏈進行處理,在這裏,input 鏈會去 filter 表中查看是否允許 192.168.1.2 訪問 3306 端口。在這個過程中,鏈充當了規則的集合,表用於存儲規則信息,而動作則表示了對規則的操作,類似於 API 接口的請求響應過程。

而這五鏈也是對應 netfilter 結構設計的重要部分,在 netfilter 中 的 hook 點也對應了這五鏈,分別如下

下面我整理了一些常用的命令。

常用命令

查看命令

一般查看命令用 -L

[root@web-01 ~]# iptable -L
-bash: iptable: command not found
You have new mail in /var/spool/mail/root
[root@web-01 ~]# iptables -L 
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
DROP       all  --  192.168.3.100        anywhere            
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
ACCEPT     icmp --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

這樣我們可以查看所有的規則,那麼我們可不可以查看某一個表的內容呢,當然是可以的, 用 -t 來指定是哪一個表。‍‍‍‍‍‍‍

[root@web-01 ~]# iptables -L -t filter
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
DROP       all  --  192.168.3.100        anywhere            
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
ACCEPT     icmp --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

當然也可以顯示詳細信息

[root@web-01 ~]# iptables -nvL -t filter
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  *      *       192.168.3.100        0.0.0.0/0           
2329K  304M ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
 7952  700K ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0           
    0     0 ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0           
    1    52 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            state NEW tcp dpt:22
13181  730K REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
   46  2760 REJECT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            reject-with icmp-host-prohibited
Chain OUTPUT (policy ACCEPT 2508K packets, 821M bytes)
 pkts bytes target     prot opt in     out     source               destination

插入規則

iptables -t filter -I INPUT -s 192.168.3.100 -j DROP

插入 input 鏈 使用 -I。-s 爲源地址,-j 爲動作,也就是,插入 input 一條規則,當 源地址爲 192.168.3.100 是‍‍丟棄掉。DROP 和 REJECT 的區別是 DROP 直接丟棄掉,REJECT 則返回一個數據包丟棄的響應信息。

刪除規則‍‍

第一種刪除規則是,直接把 -I 參數改成 -D 比如

iptables -t filter -I INPUT -s 192.168.3.100 -j DROP

另外一種 在查詢命令後面加 --line 通過編號刪除

[root@web-01 ~]# iptables -L -t filter --line
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    DROP       all  --  192.168.3.100        anywhere            
2    ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
3    ACCEPT     icmp --  anywhere             anywhere            
4    ACCEPT     all  --  anywhere             anywhere            
5    ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
6    REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination         
1    REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination         
[root@web-01 ~]# iptables -t filter -D INPUT 1
[root@web-01 ~]# iptables -L -t filter --line 
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
2    ACCEPT     icmp --  anywhere             anywhere            
3    ACCEPT     all  --  anywhere             anywhere            
4    ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
5    REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination         
1    REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination

iptables 如何來控制流量

那麼是如何來實現下圖中的流量控制呢?

我們一起來看兩個例子,假設一個場景,我們有幾臺機器,其中只有一臺可以上外網,需要用 NAT 的方式可以讓其餘的幾臺機器,也可以上網。

我們來模擬一下這個場景,首先準備一臺不能上網的服務器。由於我是用,本機的虛擬機模擬的,所以,將這臺服務器的網關注釋掉,也就是在這個文件中。

[root@gitlab ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0

GATEWAY 註釋掉

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
NAME=eth0
UUID=e8f82195-3fa4-4f6c-a397-527fa5af44fc
DEVICE=eth0
ONBOOT=yes
IPADDR=192.168.3.30
NETMASK=255.255.255.0
# GATEWAY=192.168.3.1

嘗試 ping 一下 baidu,即便是已經配置了 DNS 依然會找不到 service。

[root@gitlab ~]# cat /etc/resolv.conf
nameserver 114.114.114.114
[root@gitlab ~]# 
[root@gitlab ~]# 
[root@gitlab ~]# ping www.baidu.com
ping: www.baidu.com: Name or service not known

然後我們再‍‍準備一臺,可以上網的虛擬機。開啓內核的轉發功能,配置如下。

[root@nacos ~]# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf 
[root@nacos ~]# sysctl -p
net.ipv4.ip_forward =

然後,配置 iptables 規則,允許 eth0 接口進行轉發。

# 放行Forward的數據包
iptables -I FORWARD -i eth0 -j ACCEPT
# 開啓網卡混雜模式(允許任意流向的數據包)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

查看 iptables 的信息

[root@nacos ~]# iptables -L -t nat                               
...
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  anywhere             anywhere

已經有 POSTROUTING 的規則了。然後,我們回到那臺不能上網的機器,配置路由。

root@gitlab ~]# route add -net 0.0.0.0/0 gw 192.168.3.20

然後,在 ping 百度

[root@gitlab ~]# ping www.baidu.com
ping: www.baidu.com: Name or service not known
[root@gitlab ~]# ping www.baidu.com
PING www.a.shifen.com (110.242.68.4) 56(84) bytes of data.
64 bytes from 110.242.68.4 (110.242.68.4): icmp_seq=1 ttl=52 time=23.1 ms
64 bytes from 110.242.68.4 (110.242.68.4): icmp_seq=2 ttl=52 time=23.0 ms
64 bytes from 110.242.68.4 (110.242.68.4): icmp_seq=3 ttl=52 time=23.4 ms
64 bytes from 110.242.68.4 (110.242.68.4): icmp_seq=4 ttl=52 time=23.1 ms
^C
--- www.a.shifen.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 12036ms
rtt min/avg/max/mdev = 23.099/23.217/23.494/0.194 ms

已經可以 ping 通了。我們再看一個例子,如果我想進行地址的轉換,比如,我有一臺服務器上面有一個數據庫,但是外網沒法訪問,用另外一臺服務器跳轉一下接口,這個需要怎麼來配置呢。

首先,先在一臺服務器上面,安裝數據庫服務。‍‍

[root@gitlab ~]# yum install mariadb-server -y

嘗試登錄

[root@gitlab ~]# systemctl status mysql
Unit mysql.service could not be found.
[root@gitlab ~]# systemctl status mariadb
● mariadb.service - MariaDB database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
[root@gitlab ~]# 
[root@gitlab ~]# 
[root@gitlab ~]# systemctl start mariadb 
[root@gitlab ~]# mysql -uroot -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 5.5.68-MariaDB MariaDB Server
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> 
MariaDB [(none)]> 
MariaDB [(none)]> 
MariaDB [(none)]> exit
Bye

然後創建一個測試的數據庫

MariaDB [(none)]> create database test123;
Query OK, 1 row affected (0.00 sec)
MariaDB [(none)]> 
MariaDB [(none)]> 
MariaDB [(none)]>

創建數據庫賬號

MariaDB [(none)]> GRANT SELECT ON*.* TO 'test'@'%' IDENTIFIED BY '123456a?';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> 
MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

然後去另外一臺服務器,配置 iptables

首先,要保證的是,這一臺服務器可以進行正常的轉發,所以上面的例子的配置依然需要。

# 放行Forward的數據包
iptables -I FORWARD -i eth0 -j ACCEPT
# 開啓網卡混雜模式(允許任意流向的數據包)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

然後配置它的轉發策略。‍‍‍‍‍‍

iptables -t nat -A PREROUTING -d 192.168.3.20 -p tcp --dport 13306 -j DNAT --to-destination 192.168.3.30:3306

嘗試連接

查詢

已經可以查出數據來,到這裏可能還有一些疑問,在整個配置過程中,好像沒有配置,准入的 filter 規則。但是也能通,因爲匹配的順序前面說過,先匹配 nat 表,然後再‍‍匹配 filter 表,所以,nat 表命中後,就轉發走了,這裏不用插入 filter 的規則。

當然,配置完成後,要保存配置,可以‍‍使用如下命令。

[root@nacos ~]# service iptables save
iptables: Saving firewall rules to /etc/sysconfig/iptables:[  確定  ]
[root@nacos ~]#

他其實是保存在這個文件中 /etc/sysconfig/iptables . 當然,也可以使用 iptables-save 命令進行備份, 使用 iptables-restore 進行回放。

iptables-save > 文件名
iptables-restore < 文件名

所以保存 iptables 規則的時候,其實也可以使用這樣的命令

iptables-save > /etc/sysconfig/iptables

到現在我們會發現一個問題,在鏈,表和 動作,我們是否可以對這些內容進行更改呢,其實是可以的,我們可以自定義創建一些鏈去插入一些規則在他所相應的表。

自定義一條新的規則鏈 chain

[root@localhost ~]# iptables -N test_chain

在 nat 表中自定義一條新的規則鏈

[root@localhost ~]# iptables -N test_chain -t nat

關聯自定義鏈 chain

[root@localhost ~]# iptables -A INPUT -s 172.31.0.0/16 -j TEST_CHAIN

自定義 chain 有什麼使用的場景呢,在 k8s 的 service 服務中,就使用這種場景,去進行負載均衡。下面我們來仔細看一下 service 是怎麼來實現的

Kubernetes service 中 iptables 如何實現流量控制

首先創建一個 三個副本的 pod。
[root@k8s-master ~]# kubectl get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP               NODE         NOMINATED NODE   READINESS GATES
nginx-deployment-9456bbbf9-jqtlq   2/2     Running   0          23h   10.233.3.8       k8s-node02   <none>           <none>
nginx-deployment-9456bbbf9-k4px2   2/2     Running   0          23h   10.233.85.207    k8s-node01   <none>           <none>
nginx-deployment-9456bbbf9-rdvst   2/2     Running   0          23h   10.233.235.239   k8s-master   <none>           <none>
[root@k8s-master ~]# kubectl get svc -o wide
NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                        AGE    SELECTOR
kubelet       ClusterIP   None           <none>        10250/TCP,10255/TCP,4194/TCP   170d   <none>
kubernetes    ClusterIP   10.96.0.1      <none>        443/TCP                        297d   <none>
nginx-basic   ClusterIP   10.97.59.135   <none>        80/TCP                         23h    app=nginx

每一次訪問 10.97.59.135:80 地址的時候,他都會負載到三個節點的某一個。我們可以用 iptables-save 來查看命令 

[root@k8s-master ~]# iptables-save -t nat

由於輸出的文件比較多,接下來拆分出需要查看的內容

-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

PREROUTING 規則的意思是,所有進來的數據包要跳轉到 KUBE-SERVICES 這個 chain 中看一下是否有匹配的規則。

OUTPUT 規則的意思是,所有離開的數據包也要去 KUBE-SERVICES 這個 chain 中看一下,是否有匹配的規則。

然後在 KUBE-SERVICES 這個 chain 中是否能找到關於 10.97.59.135 這個 ip 的信息。

-A KUBE-SERVICES -d 10.97.59.135/32 -p tcp -m comment --comment "default/nginx-basic:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-WWRFY3PZ7W3FGMQW

果然是找到了,這條的意思是,所有訪問 10.97.59.135/32 這個地址的,目的端口是 80 的,跳轉到 KUBE-SVC-WWRFY3PZ7W3FGMQW 這個 chain,我們來看一下這個 chain 下面有什麼內容。

-A KUBE-SVC-WWRFY3PZ7W3FGMQW -m comment --comment "default/nginx-basic:http" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-4QU3V6S26CDI3HQO
-A KUBE-SVC-WWRFY3PZ7W3FGMQW -m comment --comment "default/nginx-basic:http" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-MT2IMDR4KXPUNLAK
-A KUBE-SVC-WWRFY3PZ7W3FGMQW -m comment --comment "default/nginx-basic:http" -j KUBE-SEP-YPGP2UQLOHOOHIPJ

這一段是什麼意思呢,就是有 0.33333333349 的概率命中 KUBE-SEP-4QU3V6S26CDI3HQO 這個 chain,有 0.50000000000 的概率命中 KUBE-SEP-MT2IMDR4KXPUNLAK 這個 chain。如果上面兩個都沒有命中,則會命中 KUBE-SEP-YPGP2UQLOHOOHIPJ 這個 chain

我們看一下 KUBE-SEP-4QU3V6S26CDI3HQO 這個 chain 包含什麼規則。

-A KUBE-SEP-4QU3V6S26CDI3HQO -p tcp -m comment --comment "default/nginx-basic:http" -m tcp -j DNAT --to-destination 10.233.235.239:80

也就是 目的地址轉發到 10.233.235.239:80。所有的三個 chain 如下:

-A KUBE-SEP-4QU3V6S26CDI3HQO -p tcp -m comment --comment "default/nginx-basic:http" -m tcp -j DNAT --to-destination 10.233.235.239:80
-A KUBE-SEP-MT2IMDR4KXPUNLAK -p tcp -m comment --comment "default/nginx-basic:http" -m tcp -j DNAT --to-destination 10.233.3.8:80
-A KUBE-SEP-YPGP2UQLOHOOHIPJ -p tcp -m comment --comment "default/nginx-basic:http" -m tcp -j DNAT --to-destination 10.233.85.207:80

正好對應的是 pod 的三個 ip 地址,所以在 k8s 爲什麼會把網絡模式會改爲 ipvs ,因爲在 pod 數逐漸變多的情況下,iptables 的這種規則效率比較低。首包,會逐個去匹配 iptables 的規則,如果有 1000 個規則,這個首包就會很慢,當首包進行分發後,雖然後面的請求會被 raw 表記錄,但是首包的很慢也是很影響性能的。‍‍

在本文中,我們深入探討了 iptables 的基礎知識和基本命令,以及如何在 Linux 系統中使用 iptables 進行流量控制和安全管理。我們還通過一個 NAT 流量轉發的例子,演示了 iptables 在網絡環境中的實際應用。

此外,我們分析了在 Kubernetes 中 iptables 是如何實現負載均衡的,探討了其工作原理和實現方式。瞭解這些知識不僅可以幫助我們更好地理解 Kubernetes 中網絡的工作機制,也可以幫助我們更好地優化和管理 Kubernetes 集羣的網絡性能。

最後,我們要強調在使用 iptables 時要謹慎小心,確保規則設置正確,以避免對網絡造成不必要的影響。iptables 是一個強大而靈活的工具,熟練掌握它將爲您的網絡管理工作帶來極大的便利和效率。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/-cwp7VXLDhWbnEQM3sc1-w