我们一般的项目中,大多数已经容器化部署。服务之间访问,即同一个集群的东西向流量,直接通过 service 访问。而南北向流量,是通过 ingress 来解决。

Screen-Shot-2019-04-17-at-10.58.43-AM-1024x769.png

在东西向流量访问中,有一些大流量和高并发的业务,尤其是在pod扩缩的时候,经常出现丢包而导致接口错误率比较高。我们service 的backend 选择的是iptabels。所以我们怀疑是iptables带来的问题。

首先怀疑是端口复用,当端口耗尽的时候,没有多余的端口用于SNAT操作,就会出现数据包都丢弃或是拒绝的情况。我们看了一下conntrack表, 但是实际值远远没有达到上限值。

conntrack -S 发现大量的 nf_conntrack insert failed 错误。

这个问题在k8s早期,还是比较常见的。

NAT代码在POSTROUTING链上hook了两次。首先,通过更改源IP和PORT(IP或PORT)来修改数据包结构,然后,如果未在中间丢弃数据包,则将转换记录在conntrack表中。如果存在冲突,这意味着SNAT端口分配和表中的插入之间存在延迟,就会导致 nf_conntrack insert failed,结果导致数据包丢失

很多网络组件已经支持了 NF_NAT_RANGE_PROTO_RANDOM_FULLY, 比如Flannel

通过 NF_NAT_RANGE_PROTO_RANDOM_FULLY,可以大大减少插入错误的数量。在具有默认伪装规则和连接到同一主机的10至80个线程的Docker测试虚拟机上,conntrack表中插入失败的发生率为2%至4%。
在内核中强制采用完全随机性后,错误降至0(后来在实时群集中接近0)。

我们生产环境使用的是EKS,关于ENI也实现了类似的方案。对于ENI,在其部署选型中,有个配置项 AWS_VPC_K8S_CNI_RANDOMIZESNAT

翻译官方文档大致内容如下:

指定SNAT iptables规则是否应将连接的出端口随机化。当AWS_VPC_K8S_CNI_EXTERNALSNAT = false 时,应使用此选项。启用后(hashrandom),--random标志将添加到SNAT iptables规则。要使用伪随机数生成而不是基于散列(即--random-fully),请使用prng作为环境变量。对于不支持--random-完全的旧版本的iptables,此选项将退回到--random。如果您依赖于出站连接的顺序端口分配,请禁用(无)此功能。

当然出于生产环境稳定性,我们在已经有的集群当中选择了另外一个方案--绕过iptables。

如何绕过?

我们知道pod网络本身就是相通的, 大致实现方案有隧道,路由等。

pasted image 0.png

我们可以获取到Pod列表,然后做客户端负载均衡。由于我们这些项目本身协议就是grpc,service的4层负载均衡是没有意义的,所以我们的客户端已经实现了客户端负载均衡。

在Kubernetes中,有一种称为headless服务的特定服务。Headless服务不会为底层Pod提供单个IP和负载平衡,而只是具有DNS配置,该配置为我们提供了一个A记录,其中包含与标签选择器匹配的所有Pod的Pod IP地址。

不过该方案需要注意:

  • coredns 需要配置梯度扩缩。具体就是使用 cluster-proportional-autoscaler
  • 配置ndots。我们知道设置pod的dnsPolicy为ClusterFirst之后,所有的dns解析请求,都会请求到coredns。默认的ndots:5, 会导致接口的延时变大,很多时间浪费在dns请求上。通过抓包分析:

    ndots为5的时候:

    03:52:25.216518 IP access-6bf77d68df-sm87n.42375 > kube-dns.kube-system.svc.cluster.local.domain: 11402+ A? www.baidu.com.tsa.svc.cluster.local. (53)
    03:52:25.216916 IP access-6bf77d68df-sm87n.40506 > kube-dns.kube-system.svc.cluster.local.domain: 64764+ PTR? 10.0.100.10.in-addr.arpa. (42)
    03:52:25.217360 IP kube-dns.kube-system.svc.cluster.local.domain > access-6bf77d68df-sm87n.42375: 11402 NXDomain*- 0/1/0 (146)
    03:52:25.217489 IP access-6bf77d68df-sm87n.42327 > kube-dns.kube-system.svc.cluster.local.domain: 39517+ A? www.baidu.com.svc.cluster.local. (49)
    03:52:25.217660 IP kube-dns.kube-system.svc.cluster.local.domain > access-6bf77d68df-sm87n.40506: 64764*- 1/0/0 PTR kube-dns.kube-system.svc.cluster.local. (118)
    03:52:25.217877 IP kube-dns.kube-system.svc.cluster.local.domain > access-6bf77d68df-sm87n.42327: 39517 NXDomain*- 0/1/0 (142)
    03:52:25.217906 IP access-6bf77d68df-sm87n.48023 > kube-dns.kube-system.svc.cluster.local.domain: 56925+ A? www.baidu.com.cluster.local. (45)
    03:52:25.218056 IP kube-dns.kube-system.svc.cluster.local.domain > access-6bf77d68df-sm87n.48023: 56925 NXDomain*- 0/1/0 (138)
    03:52:25.218102 IP access-6bf77d68df-sm87n.35164 > kube-dns.kube-system.svc.cluster.local.domain: 13509+ A? www.baidu.com.ap-southeast-1.compute.internal. (63)
    03:52:25.219213 IP kube-dns.kube-system.svc.cluster.local.domain > access-6bf77d68df-sm87n.35164: 13509 NXDomain 0/0/0 (63)
    03:52:25.219280 IP access-6bf77d68df-sm87n.56466 > kube-dns.kube-system.svc.cluster.local.domain: 22697+ A? www.baidu.com. (31)
    03:52:25.221500 IP kube-dns.kube-system.svc.cluster.local.domain > access-6bf77d68df-sm87n.56466: 22697 4/0/0 CNAME www.a.shifen.com., CNAME www.wshifen.com., A 45.113.192.101, A 45.113.192.102 (181)
    03:52:25.223936 IP access-6bf77d68df-sm87n.59668 > kube-dns.kube-system.svc.cluster.local.domain: 33885+ PTR? 101.192.113.45.in-addr.arpa. (45)
    03:52:25.225784 IP kube-dns.kube-system.svc.cluster.local.domain > access-6bf77d68df-sm87n.59668: 33885 NXDomain 0/1/0 (122)

    ndots为2的时候:

    04:06:19.030624 IP access-d6f56b8db-2gxfd.34877 > kube-dns.kube-system.svc.cluster.local.domain: 21513+ A? www.baidu.com. (31)
    04:06:19.032784 IP kube-dns.kube-system.svc.cluster.local.domain > access-d6f56b8db-2gxfd.34877: 21513 4/0/0 CNAME www.a.shifen.com., CNAME www.wshifen.com., A 45.113.192.102, A 45.113.192.101 (181)
    04:06:19.035098 IP access-d6f56b8db-2gxfd.53602 > kube-dns.kube-system.svc.cluster.local.domain: 37598+ PTR? 102.192.113.45.in-addr.arpa. (45)
    04:06:19.036743 IP kube-dns.kube-system.svc.cluster.local.domain > access-d6f56b8db-2gxfd.53602: 37598 NXDomain 0/1/0 (122)

    对于一些外部域名,默认ndots为5,每次解析都要全部搜索一遍。ndots为2的时候 只搜索一次。

    FQDN是完整域名,一般来说,域名最终以.结束表示是FQDN,例如google.com.是FQDN,但google.com不是。

    对FQDN,操作系统会直接查询DNS server。那么非FQDN呢?这里就要用到search和ndots了。

    ndots表示的是域名中必须出现的.的个数,如果域名中的.的个数不小于ndots,则该域名为一个FQDN,操作系统会直接查询;如果域名中的.的个数小于ndots,操作系统会在search搜索域中进行查询。

    例如上面的例子,ndots为5,查询的域名google.com不以.结尾,且.的个数少于5,因此操作系统会依此在default.svc.cluster.local svc.cluster.local cluster.local lan四个域中进行了搜索,其中前面3个搜索域是由kubernetes注入的,最后的lan是操作系统默认的搜索域。

    ndots默认值为1,也就是说,只要域名中有一个.,操作系统就会认为是绝对域名,直接查询。

    ndots上限为15。

  • coredns 配置文件增加forward插件。

    forward . /etc/resolv.conf

    将外部的域名按照/etc/resolv.conf的解析规则解析。

总结

当然终极方案service mesh这种服务治理方案是最好的,如果是南北向流量,其实使用ingress就可以避免iptables。


iyacontrol
1.4k 声望2.7k 粉丝

专注kubernetes,devops,aiops,service mesh。