Think Deep,Work Lean

istio之iptables流量拦截失败问题排查

Posted on By zack

背景

公司内部连续测试8轮,在封版之前最后几次测试,突然发现istio流量监控及Tracing不正常。距离11月30号发版日只剩到计时3天时,突然出现问题。

问题定位过程

  1. 应用访问正常,业务未受影响。
  2. 进容器 curl 127.0.0.1:15000/stats/prometheus 未看到istio_requests_total指标,但是有istio_开头的指标。
  3. 检查 envoyfilter正常存在,检查envoy的指标,存在 istio.stats filter。
  4. istio-proxy容器无不正常日志,istiod无不正常日志,相关pod无不正常日志。
  5. 使用Iptables命令,手动查看iptables规则为空。

第5步的时候发现了异常,说明iptables规则未正常下发。检查 istio-init容器,可以看到有以下报错:

[root@prctyjs4jwzkvv ~]# kubectl -n xxy-1127aa logs -f xx-557d868d7d-sz2fm  -c istio-init
 
2021-11-27T13:43:36.924014Z     error   Command error output: iptables-restore v1.8.4 (legacy): iptables-restore: unable to initialize table 'nat'
 
Error occurred at line: 1
Try `iptables-restore -h' or 'iptables-restore --help' for more information.

该日志说明在执行 iptables-restore时失败。

查看一个正常的 istio-init 容器日志,对比:

[root@prctyjs4jwzkvv ~]# kubectl -n xxy-1127aa logs -f xx-557d868d7d-427s5  -c istio-init
2021-11-27T14:38:35.714344Z     info    Istio iptables environment:
ENVOY_PORT=
INBOUND_CAPTURE_PORT=
ISTIO_INBOUND_INTERCEPTION_MODE=
ISTIO_INBOUND_TPROXY_ROUTE_TABLE=
ISTIO_INBOUND_PORTS=
ISTIO_OUTBOUND_PORTS=
ISTIO_LOCAL_EXCLUDE_PORTS=
ISTIO_EXCLUDE_INTERFACES=
ISTIO_SERVICE_CIDR=
ISTIO_SERVICE_EXCLUDE_CIDR=
ISTIO_META_DNS_CAPTURE=
2021-11-27T14:38:35.714403Z     info    Istio iptables variables:
PROXY_PORT=15001
PROXY_INBOUND_CAPTURE_PORT=15006
PROXY_TUNNEL_PORT=15008
PROXY_UID=1337
PROXY_GID=1337
INBOUND_INTERCEPTION_MODE=REDIRECT
INBOUND_TPROXY_MARK=1337
INBOUND_TPROXY_ROUTE_TABLE=133
INBOUND_PORTS_INCLUDE=*
INBOUND_PORTS_EXCLUDE=15090,15021,15020
OUTBOUND_IP_RANGES_INCLUDE=*
OUTBOUND_IP_RANGES_EXCLUDE=
OUTBOUND_PORTS_INCLUDE=
OUTBOUND_PORTS_EXCLUDE=
KUBEVIRT_INTERFACES=
ENABLE_INBOUND_IPV6=false
DNS_CAPTURE=false
CAPTURE_ALL_DNS=false
DNS_SERVERS=[],[]
OUTPUT_PATH=
NETWORK_NAMESPACE=
CNI_MODE=false
EXCLUDE_INTERFACES=
 
2021-11-27T14:38:35.716883Z     info    Writing following contents to rules file: /tmp/iptables-rules-1638023915714479778.txt526996950
* nat
-N ISTIO_INBOUND
-N ISTIO_REDIRECT
-N ISTIO_IN_REDIRECT
-N ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp --dport 15008 -j RETURN
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A PREROUTING -p tcp -j ISTIO_INBOUND
-A ISTIO_INBOUND -p tcp --dport 22 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15020 -j RETURN
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_OUTPUT -o lo -s 127.0.0.6/32 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
COMMIT
2021-11-27T14:38:35.716934Z     info    Running command: iptables-restore --noflush /tmp/  iptables-rules-1638023915714479778.txt526996950  # 该步骤失败
2021-11-27T14:38:35.982415Z     info    Writing following contents to rules file: /tmp/ip6tables-rules-1638023915982320176.txt160569149
 
2021-11-27T14:38:35.982855Z     info    Running command: ip6tables-restore --noflush /tmp/ip6tables-rules-1638023915982320176.txt160569149
2021-11-27T14:38:38.040088Z     info    Running command: iptables-save
2021-11-27T14:38:38.042112Z     info    Command output:
# Generated by iptables-save v1.8.4 on Sat Nov 27 14:38:38 2021
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:ISTIO_INBOUND - [0:0]
:ISTIO_IN_REDIRECT - [0:0]
:ISTIO_OUTPUT - [0:0]
:ISTIO_REDIRECT - [0:0]
-A PREROUTING -p tcp -j ISTIO_INBOUND
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 22 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
COMMIT
# Completed on Sat Nov 27 14:38:38 2021
 
[root@prctyjs4jwzkvv ~]#

从正常的日志中可以看到,istio会先把文件写到一个文件中 /tmp/xxxx,然后执行 iptables-restore --noflush /tmp/xxx 下发iptables的规则。但是在该步骤时失败了。

问题诊断

检查操作系统版本,发现是centos8.2

root@prctyjs4jwzkvv ~]# uname -a
Linux prctyjs4jwzkvv 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Tue Nov 16 14:42:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
[root@prctyjs4jwzkvv ~]# uname -r
4.18.0-348.2.1.el8_5.x86_64
[root@prctyjs4jwzkvv ~]# cat /etc/redhat-release
CentOS Linux release 8.2.2004 (Core)

centos8使用iptables-nft替换centos7的iptables-legacyiptablesiptables-restore均是legacy模式下的命令。

[root@prctyjs4jwzkvv ~]# iptables -V
iptables v1.8.4 (nf_tables)
[root@prctyjs4jwzkvv ~]#  which iptables
/usr/sbin/iptables
[root@prctyjs4jwzkvv ~]# ls -l /usr/sbin/iptables
lrwxrwxrwx 1 root root 17 Aug 25 07:13 /usr/sbin/iptables -> xtables-nft-multi

问题根本原因:centos8默认使用iptables-nft模式,并没有开启iptables legacy,而istio使用legacy模式,导致报错。

更专业的说法:

在centos 8上,更准确地说是在Linux 4.x内核里,nftables已经取代了iptables。用户态iptables命令还可以用,完全是出于兼容原来的一些用法,调用到内核还是走nftables。所以,有nf_nat模块没有iptables_nat模块是正常的。如果上层强依赖iptables_nat这个内核模块,反而不正常了,这样的话上层在标准的CentOS 8上运行就有问题。nf_nat和nft_chain_nat结合起来完全可以实现iptables nat原来的功能。nf是netfilter,nft是nftables。

解决办法:启用iptable_nat内核模块

modprobe iptable_nat

但是笔者是启用了所有netfilter相关的内核:

modprobe br_netfilter ; modprobe nf_nat ; modprobe xt_REDIRECT ; modprobe xt_owner; modprobe iptable_nat

所有的node节点开启该内核后,重启pod,sidecar正常,initContainer不会再报 iptables-restore 的错误。

再次检查监控及Tracing,数据正常。

插曲:使用iptables命令仍然看不到iptables中nat表的规则?

手动进入容器网络协议栈,还是看不到相关规则

[root@prctyjs4jwzkvv ~]# kubectl -n xxy-1127aa get po xx-5c698bff6b-4wq57  -owide
NAME                  READY   STATUS    RESTARTS   AGE     IP          NODE          NOMINATED NODE   READINESS GATES
xx-5c698bff6b-4wq57   2/2     Running   0          8m56s   10.0.1.39   192.168.0.3   <none>           <none>
 
 
[root@prctyjs4jwzkvv ~]# kubectl -n xxy-1127aa get po xx-5c698bff6b-4wq57  -owide^C
[root@prctyjs4jwzkvv ~]# ssh 192.168.0.3
Last login: Sun Nov 28 10:59:10 2021 from 192.168.0.6
[root@prafnkteojp7uc ~]# nsenter -n -t 1011551
[root@prafnkteojp7uc ~]# iptables -vS
-P INPUT ACCEPT -c 0 0
-P FORWARD ACCEPT -c 0 0
-P OUTPUT ACCEPT -c 0 0
# Warning: iptables-legacy tables present, use iptables-legacy to see them
[root@prafnkteojp7uc ~]# exit
logout
[root@prafnkteojp7uc ~]# nsenter -n -t 1011551
[root@prafnkteojp7uc ~]# iptables -V
iptables v1.8.4 (nf_tables)

原因:centos8使用 iptables-nft,cetons7是iptables-legacy,iptables的命令是 centos7的(legacy),看不到 iptables-nft的规则,此时需要装nft的命令才能查看规则。或是在单独再启动一个centos7的容器,网络使用container in container模式,然后在该容器中使用iptables命令查看规则。

root@rhel-8 # iptables -V
iptables v1.8.4 (nf_tables) # 这个是 iptable-nft,centos8使用该版本
 
root@rhel-7 # iptables -V
iptables v1.4.21  # centos7 使用iptable-legacy

详细区别
https://developers.redhat.com/blog/2020/08/18/iptables-the-two-variants-and-their-relationship-with-nftables#two_variants_of_the_iptables_command