默认情况下,kube-proxy使用iptables实现service ip到后端pod的转换,可以参考之前写的这篇文章,分析了iptables规则。也可以使用ipvs来实现,今天搞一下后者,在现有k8s环境上修改下kube-proxy的配置即可。

ipvs dnat

kube-proxy的作用就是将访问service ip的报文转换成后端pod ip,相当于就是对报文做dnat,所以ipvs的dnat模式正好合适,简单说一下ipvs dnat原理。

如下图,client访问vip,报文到达director后,查找路由发现vip是本地的,走input流程,在input的hook点,ipvs生效,将vip转换成rip,对报文做完dnat后,将报文发给rs。




kubesphere 容器组固定ip kubectl port-forward_iptables


image.png

rs的响应报文目的ip为cip,源ip为rip,到达director后,查找路由发现不是本地的,走forward流程,在forward的hook点,ipvs生效,将rip转换成vip,对报文做完snat后,发给client。

kubesphere 容器组固定ip kubectl port-forward_kubesphere 容器组固定ip_02

image.png

这里的director就是配置ipvs的主机,vip也要配置在这个主机上。

可以通过下面的proc文件,查看ipvs的表项

root@master:~# cat /proc/net/ip_vs_conn
Pro FromIP   FPrt ToIP     TPrt DestIP   DPrt State       Expires PEName PEData
TCP AC12DB43 EDA8 0A600001 01BB C0A87A14 192B ESTABLISHED     898
TCP 0A600001 C626 0A600001 01BB C0A87A14 192B ESTABLISHED     883
TCP AC12DB44 AD66 0A600001 01BB C0A87A14 192B ESTABLISHED     898
TCP 0A600001 C68E 0A600001 01BB C0A87A14 192B ESTABLISHED     885
//其中关键字段含义如下
FromIP: client ip
FPrt: client port
ToIP:service ip
TPrt:service port
DestIP:real server ip
DPrt:real server port
Expires :表项超时时间

表项超时时间

//查看
root@master:~# ipvsadm -l --timeout
Timeout (tcp tcpfin udp): 900 120 300
//设置
root@master:~# ipvsadm --set 60 120 300

配置

kube-proxy提供如下两个参数

--proxy-mode: 指定使用哪种模式,不指定的话默认使用iptables
--ipvs-scheduler:如果指定使用ipvs模式,此参数指定调度模式,不指定的话默认是round-robin

但是我这个k8s使用的kubeadm搭建的,kube-proxy的参数使用configmap映射,所以只需要修改configmap就行,步骤如下

//编辑configmap
kubectl edit configmap kube-proxy -n kube-system
//修改mode参数,从""改成ipvs
mode: ipvs
//如果想修改调度模式,可修改下面的参数
scheduler: ""

//删除所有的kube-proxy pod,重新启动的pod会使用最新的配置
kubectl get pod -n kube-system
kubectl delete pod -n kube-system <pod-name>

//查看kube-proxy pod的log
kubectl logs -n kube-system kube-proxy-xczpc | grep "Using ipvs Proxier"

变化

ipvs生效后,node上会有如下几个变化
a. 每个node上,都会创建一个dummy类型的虚拟接口kube-ipvs0,并且配置了所有的service ip

6: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
    link/ether 16:cd:66:87:d5:13 brd ff:ff:ff:ff:ff:ff
    inet 10.96.0.10/32 brd 10.96.0.10 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
    inet 10.96.0.1/32 brd 10.96.0.1 scope global kube-ipvs0
       valid_lft forever preferred_lft forever
  1. local路由表中,增加了service ip路由信息
root@master:~# ip route show table local dev kube-ipvs0
local 10.96.0.1 proto kernel scope host src 10.96.0.1
local 10.96.0.10 proto kernel scope host src 10.96.0.10
  1. ipvs配置,增加了service ip及其对应的pod ip
//首先安装 ipvsadm命令
root@master:~# apt install ipvsadm
//查看ipvs配置,其中Forward字段指定了ipvs模式(此例中Masq表示nat模式),
//Scheduler指定了调度模式(此例中 rr 表示round-robin模式)
root@master:~# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  10.96.0.1:443 rr
  -> 192.168.122.20:6443          Masq    1      4          0
TCP  10.96.0.10:53 rr
  -> 172.18.219.67:53             Masq    1      0          0
  -> 172.18.219.68:53             Masq    1      0          0
TCP  10.96.0.10:9153 rr
  -> 172.18.219.67:9153           Masq    1      0          0
  -> 172.18.219.68:9153           Masq    1      0          0
UDP  10.96.0.10:53 rr
  -> 172.18.219.67:53             Masq    1      0          0
  -> 172.18.219.68:53             Masq    1      0          0

//查看下当前k8s中所有的service,可以和ipvs的配置完全对应起来
root@master:~# kubectl get svc -A
NAMESPACE     NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
default       kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP                  8d
kube-system   kube-dns     ClusterIP   10.96.0.10      <none>        53/UDP,53/TCP,9153/TCP   8d

例子

下面使用一个小例子分析下ipvs在k8s中作用,拓扑图如下



kubesphere 容器组固定ip kubectl port-forward_kube-proxy_03


image.png

使用此命令创建对应的pod: kubectl apply -f service.yaml

root@master:~# cat service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client
spec:
  selector:
    matchLabels:
      app: myapp1
  replicas: 1
  template:
    metadata:
      labels:
        app: myapp1
    spec:
      nodeName: master

      containers:
      - name: nginx
        image: nginx

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: server
spec:
  selector:
    matchLabels:
      app: myapp
  replicas: 2
  template:
    metadata:
      labels:
        app: myapp
    spec:
      nodeName: node1

      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 2222

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 2222
    targetPort: 2222

看一下变化

//虚拟网卡上增加了service ip 10.102.54.177
6: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
    link/ether 16:cd:66:87:d5:13 brd ff:ff:ff:ff:ff:ff
    ...
    inet 10.102.54.177/32 brd 10.102.54.177 scope global kube-ipvs0
       valid_lft forever preferred_lft forever

//local路由表也增加了service ip 10.102.54.177信息
root@master:~# ip route show table local dev kube-ipvs0
...
local 10.102.54.177 proto kernel scope host src 10.102.54.177

//ipvs配置也增加了service ip 10.102.54.177信息
root@master:~# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
...
TCP  10.102.54.177:2222 rr
  -> 172.18.166.134:2222          Masq    1      0          0
  -> 172.18.166.135:2222          Masq    1      0          0

client访问service流程如下图所示



kubesphere 容器组固定ip kubectl port-forward_iptables_04


image.png

client pod的报文发出后,首先到达node上的calixxx接口,根据目的ip vip查找路由发现是本地的(这就是为什么每个service ip都配置在虚拟接口kube-ipvs0的原因,否则报文不会经过INPUT流程,也就不会被ipvs处理了),走input流程,ipvs在这里对报文做dnat,将目的ip vip换成server pod ip,根据server pod ip重新查找路由,后面的流程和iptables模式的就一样了,封装成ipip报文,发送出去。

响应报文流程如下,就不过多解释了



kubesphere 容器组固定ip kubectl port-forward_iptables_05


image.png

总结:ipvs在k8s中的应用,访问service的pod相当于client,每个node都相当于director,service后端pod相当于real server。