Kubernetes 网络

微服务是多进程、多服务部署,无法通过 IPC 进程内调用,必然通过网络调用,这将带来很多问题:不可靠、有带宽、协议设计。无论是 TCP、HTTP、RPC,无论是东西流量还是南北流量,涉及限流、熔断、域名及路径上下文,都需要 Kubernetes 或者第三方产品给出解决方案。网络是 Kubernetes 的难点之一。

  一、Kubernetes 自家解决方案

1   三个网络

  1. Node
  2. Pod,同一Pod 可以和进程间 IPC 通信,可以直接用 localhost 访问同一 Pod 中的其它容器。但不同 Pods 采用了不同的虚拟 IP。共同点是都不需要 NAT 下通信,他们共享网络命名空间,他们之间的通信不需要宿主机的端口映射,Pod ip 对 Kubernetes 网络内部还是宿主机都是一样的。
  3. Service,服务发现与负载均衡。ClusterIP 虚拟网络只对 Kubernetes 内部可见

以上三种网络是互不相交的。Kubernetes 并没有原生内置某种 Pod 网络实现,而是开放给了第三方厂商依据 CNI(Container Network Interface)规则接口实现。这是不同的容器接口可以调用相同的网络组件实现通信。 安装 Docker 容器后会有 /opt/cni/bin 目录。一般有三种实现方式:

  • 二层交换
  • 三层路由
  • Overlay 网络,在原有的网络上设计虚拟网络,实现解耦,但传输性能二法与二层和三层网络方案相比,实现上是分成 underlay 和 overlay 实现数据包地分发。 备注:via 是导向网络 Default via 192.168.1.1,选择下一跳;dev 是导向设备 10.1.0.0/16 dev bridge,进行分发。

以三层路由网络举例,有 node1 和 node2 节点,有以下两种方案:

  1. 方案一,网关路由:
  • Gateway(192.168.1.1): RouteTable(10.1.1.0/24 via 192.168.1.100, 10.1.2.0/24 via 192.168.1.101)
  • node1(192.168.1.100/24): Container bridge(10.1.1.1/24), RouteTable(Default via 192.168.1.1)
  • pod1: 10.1.1.2
  • pod2: 10.1.1.3
  • node2(192.168.1.101/24): Container bridge(10.1.2.1/24), RouteTable(Default via 192.168.1.1)
  • pod1: 10.1.2.2
  • pod2: 10.1.2.3
  1. 方案二,主机路由:
  • Gateway(192.168.1.1):
  • node1(192.168.1.100/24): Container bridge(10.1.1.1/24), RouteTable(Default via 192.168.1.1, 10.1.2.0/24 via 192.168.1.101)
  • pod1: 10.1.1.2
  • pod2: 10.1.1.3
  • node2(192.168.1.101/24): Container bridge(10.1.2.1/24), RouteTable(Default via 192.168.1.1, 10.1.1.0/24 via 192.168.1.100)
  • pod1: 10.1.2.2
  • pod2: 10.1.2.3

2   服务发现

为了承担起 DNS 解析任务,Kubernetes 环境 master 节点都会有一个 DNS 服务(一般多个 Pod),其 selector 为 k8s-app: kube-dns,

[yhdodo19@instance-1 ~]$ kubectl get svc -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   27d

[yhdodo19@instance-1 ~]$ kubectl get po -n kube-system --show-labels | grep k8s-app=kube-dns
coredns-fb8b8dccf-2rdns              1/1     Running   6          27d   k8s-app=kube-dns,pod-template-hash=fb8b8dccf
coredns-fb8b8dccf-nkqtv              1/1     Running   6          27d   k8s-app=kube-dns,pod-template-hash=fb8b8dccf

3   Service 网络

3.1   NodePort

3.1.1   原理

  • 创建 socket 对外监听,但要求监听端口大于 30000
  • 基于 iptables 的流量转换,也可以认为是 socket 转发,通过 iptables Chain 实现
  • 基于 iptables 的简单负载均衡,通过 iptables 概率实现

{% img http://img.jemper.cn/2019/06/nodeport.png 300 %}

3.1.2   实现

外部请求 -> NodeIP:NodePort -> ClusterIP:Port -> PodIP:TargetPort,它是通过 kube-proxy 实现:

  • kube-proxy 是以 daemonset pod 的形式运行在集群内的每个节点上,监听服务(Service)和端点(Endpoint)的变化设定和更新 iptables 规则
  • kube-proxy 通过 iptables 实现 nodePort 到集群内部 Service Port 再到 Pod 中的 Container targetPort 的流量转发
  • kube-proxy 是通过 iptables 实现 Service 流量转发到 Pod 的负载均衡

由以上的处理方式可见 NodePort 暴露端口方案是一个 4 层网络方案,无法处理 7 层网络,不能设置 80 等常用端口,它只能用来进行一些开发、测试工作,比如映射 3306:30001 进行数据操作或者数据迁移。

3.1.3   分析

kube-proxy 是基于 iptable 来实现的,它是防火墙的一部分;Linux 防火墙可以对数据进行过滤、更改、转发操作,它由两个组件组成:核心层 netfilters 和用户层 iptables。 iptables 在用户层设置、变更和维护过滤规则,并最终由 netfilters 执行,iptables 主要由 一组 table 和 table 里的 Chain 组成,chain 有默认也可自定义。

接下来我们以 外部请求 -> NodeIP:NodePort(35.237.188.250:30001) -> ClusterIP:Port(10.96.191.193:81) -> PodIP:TargetPort(10.36.0.10:80, ···) 来分析 iptables 是怎样实现转发和负载均衡的。

[yhdodo19@instance-1 ~]$ kubectl describe svc goapi
Name:                     goapi
Namespace:                default
Labels:                   env=goapi
Annotations:              kubectl.kubernetes.io/last-applied-configuration:
                            {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"env":"goapi"},"name":"goapi","namespace":"default"},"spec":{"p...
Selector:                 app=goweb
Type:                     NodePort
IP:                       10.96.191.193
Port:                     http  81/TCP
TargetPort:               80/TCP
NodePort:                 http  30001/TCP
Endpoints:                10.36.0.10:80,10.36.0.11:80,10.36.0.5:80 + 2 more...
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

[yhdodo19@instance-1 ~]$ sudo lsof -i tcp:30001
COMMAND    PID USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
kube-prox 5307 root   21u  IPv6 9386976      0t0  TCP *:pago-services1 (LISTEN)

可以看到 NodePort 就是 kube-proxy 创建并监听的,该服务一样有 5 个 Endpoints。接下执行命名 iptables -t nat -vnL --line-number > iptables-nat.txt 导出查看:

Chain PREROUTING (policy ACCEPT 34 packets, 3918 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1    2796K  327M KUBE-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */

Chain KUBE-SERVICES (2 references)
47      23  2744 KUBE-NODEPORTS  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

Chain KUBE-NODEPORTS (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/goapi:http */ tcp dpt:30001
2        0     0 KUBE-SVC-4MT5SRJPZGU2FACQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/goapi:http */ tcp dpt:30001
3        0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:tls */ tcp dpt:30614
4        0     0 KUBE-SVC-S4S242M2WNFIAT6Y  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:tls */ tcp dpt:30614
5        0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/web: */ tcp dpt:32063
6        0     0 KUBE-SVC-BIJGBSD4RZCCZX5R  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/web: */ tcp dpt:32063
7        0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https-prometheus */ tcp dpt:32105
8        0     0 KUBE-SVC-VCO3RXEEJXVGNRLL  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https-prometheus */ tcp dpt:32105
9        0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https-grafana */ tcp dpt:30523
10       0     0 KUBE-SVC-MZX34IYCYJRMNTMQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https-grafana */ tcp dpt:30523
11       0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:http2 */ tcp dpt:31380
12       0     0 KUBE-SVC-G6D3V5KS3PXPUEDS  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:http2 */ tcp dpt:31380
13       0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https */ tcp dpt:31390
14       0     0 KUBE-SVC-7N6LHPYFOVFT454K  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https */ tcp dpt:31390
15       0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:tcp */ tcp dpt:31400
16       0     0 KUBE-SVC-62L5C2KEOX6ICGVJ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:tcp */ tcp dpt:31400
17       0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https-kiali */ tcp dpt:32227
18       0     0 KUBE-SVC-Y4Y3QMSBONEWNEDG  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https-kiali */ tcp dpt:32227
19       0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:status-port */ tcp dpt:30642
20       0     0 KUBE-SVC-TFRZ6Y6WOLX5SOWZ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:status-port */ tcp dpt:30642
21       0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https-tracing */ tcp dpt:32308
22       0     0 KUBE-SVC-U67ZB3ILROLSW2OD  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* istio-system/istio-ingressgateway:https-tracing */ tcp dpt:32308

可以找到 KUBE-SVC-4MT5SRJPZGU2FACQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/goapi:http */ tcp dpt:30001, 继续找 KUBE-SVC-4MT5SRJPZGU2FACQ Chain

Chain KUBE-SERVICES (2 references)
num   pkts bytes target     prot opt in     out     source               destination
2        0     0 KUBE-SVC-4MT5SRJPZGU2FACQ  tcp  --  *      *       0.0.0.0/0            10.96.191.193        /* default/goapi:http cluster IP */ tcp dpt:81

Chain KUBE-SVC-4MT5SRJPZGU2FACQ (2 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 KUBE-SEP-2GRZXFB4YOVSQMTA  all  --  *      *       0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.20000000019
2        0     0 KUBE-SEP-FHKPE4W4K4BU4T6A  all  --  *      *       0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.25000000000
3        0     0 KUBE-SEP-X6ZXAKDCXPAUMTPF  all  --  *      *       0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.33332999982
4        0     0 KUBE-SEP-GXKTNAJ66D3R7K2Y  all  --  *      *       0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.50000000000
5        0     0 KUBE-SEP-2JFIICOQDPEEFOFI  all  --  *      *       0.0.0.0/0            0.0.0.0/0

以上 5 条规则对应了 5 个 Endpoints,并且每一条都有概率设置实现的负载均衡。到此分析结束了。

3.2   LoadBalancer

需要第三方的负载均衡支持,以 GCE 为例,在“网络服务 -> 负载均衡”提供了三种类型的负载均衡:

  • HTTP(S) 负载平衡:适用于 HTTP 和 HTTPS 应用的第 7 层负载平衡
  • TCP 负载平衡:适用于依赖 TCP/SSL 协议的应用的第 4 层负载平衡或代理
  • UDP 负载平衡:适用于依赖 UDP 协议的应用的第 4 层负载平衡

{% img http://img.jemper.cn/2019/06/loadbalancer.png 300 %}

3.3   Ingress

我们知道 Service 的表现形式为 IP:Port,即工作在 TCP/IP 层,仅适用于依赖 TCP/SSL 协议的应用的第 4 层负载平衡或代理。而对于基于 HTTP 的服务来说,不同的 URL 地址经常对应到不同的后端服务或者虚拟服务器,这些应用的转发机制仅通过 Kubernetes 的 Service 机制是无法实现的。Kubernetes 提供了 Ingress 适用于 HTTP 和 HTTPS 应用的第 7 层负载平衡:

  • 外部可访问 URL
  • 负载均衡
  • SSL / TLS
  • 基于域名的虚拟主机

其实就是实现了 nginx 的功能,更重要的是可以通过 ingress 配置文件直接控制 nginx。

{% img http://img.jemper.cn/2019/06/ingress.png 300 %}

2.3.1   Ingress Controller

控制器监听 Kubernetes API Ingress 资源的变化(反向代理和负载均衡),并实时的感知后端 service、pod 等变化(服务发现),对内置的反向代理进行设置和更新。 这里我们使用官方提供的 ingress-nginx-controller 进行实践,首先是安装 ingress-nginx-controller。只需要运行 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml 但因为上面的配置文件没有开放 80、443 端口,所以必须得下载下来进行修改, 添加以下信息,添加 nodeSelector 是因为它是 deployment 部署 1 个副本,为了配合域名解析的稳定性所以固定某一个 node 节点上运行 ingress-nginx。

...
    spec:
      hostNetwork: true
      nodeSelector:
        kubernetes.io/hostname: instance-3
...

kubectl apply -f mandatory.yaml,等待 pod 启动完成。

3.3.2   Ingress 配置

  • ingress 配置的命名空间必须得在服务所在的空间,毕竟 ingress 的 backend 无法指定服务的命名空间。
  • 如果域名和路径相同,则先部署的 ingress 优先。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-nginx
spec:
  rules:
  - host: kube.jemper.cn
    http:
      paths:
      - backend:
          serviceName: goapi
          servicePort: 81
        path: /
---

  二、Istio 解决方案

Istio社区意识到了Ingress和Mesh内部配置割裂的问题,因此从0.8版本开始,社区采用了 Gateway 资源代替K8s Ingress来表示流量入口。

Istio Gateway资源本身只能配置L4-L6的功能,例如暴露的端口,TLS设置等;但Gateway可以和绑定一个VirtualService,在VirtualService 中可以配置七层路由规则,这些七层路由规则包括根据按照服务版本对请求进行导流,故障注入,HTTP重定向,HTTP重写等所有Mesh内部支持的路由规则。

  三、API Gateway 解决方案

参考文献 [1] Kubernetes Ingress with Nginx Example. https://matthewpalmer.net/kubernetes-app-developer/articles/kubernetes-ingress-guide-nginx-example.html