k8s 網路轉發實現

振翅飛翔發表於2019-06-29

假標題

寫這篇文章的原因是:

  1. 一段時間後,老忘記 k8s 的 IP,ClusterIP,NodePort,Port 等等是什麼意思
  2. 週末沒事做 -.-

環境說明

dnsDomain: cluster.local
podSubnet: 192.167.0.0/16
serviceSubnet: 10.96.0.0/12

k8s (v1.15.0) 部署在 3 臺主機上,網路外掛使用的是 flannel
centos0  192.168.20.70 [master]
centos1  192.168.20.71
centos2  192.168.20.72

k8s 例子使用的是 https://kubernetes.io/docs/tutorials/state...

$ kubectl get service -o wide
NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE   SELECTOR
frontend       NodePort    10.99.173.89     <none>        80:30193/TCP   13h   app=guestbook,tier=frontend
kubernetes     ClusterIP   10.96.0.1        <none>        443/TCP        20h   <none>
redis-master   ClusterIP   10.106.223.234   <none>        6379/TCP       13h   app=redis,role=master,tier=backend
redis-slave    ClusterIP   10.106.94.73     <none>        6379/TCP       13h   app=redis,role=slave,tier=backend

$ kubectl describe service frontend
Name:                     frontend
...
Type:                     NodePort
IP:                       10.99.173.89
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30193/TCP
Endpoints:                192.167.1.2:80,192.167.1.3:80,192.167.1.4:80 + 2 more...
Session Affinity:         None
External Traffic Policy:  Cluster

k8s 模組

在開始下面的分析之前,先看看 k8s 的模組大概有哪些?

k8s-networking-1.png

主要有 3 個模組, Node, Service, Pod

  1. Node 即我們本機,NodeIP 就是我們本機的 IP(對應我們上面部署的主機 centos0/centos1/centos2),NodePort 即 Service 提供的服務對映到我們本機的埠號
  2. 總所周知 Pod 是一組容器的集合(從網路的角度來看,Pod 相當於一個 docker,Pod 裡面的數個 docker 的網路名稱空間是共享的)
  3. Service 存在的意義:因為在 k8s 中,Pod 的 IP 是變化的(比如 增/減 pod 個數),Pod 的 IP 有多個。因此抽象出一個 Service,以一個 Service IP 對應多個 Pod IP(例如輪詢),ServicePort 對應 Pod 的 Port。另外 Service 在一些場景下,也叫 Cluster,ClusterIP 即 ServiceIP。還有個另外,ServiceIP 是虛擬的,這在最後會解釋

Service

Service 型別:

  1. ClusterIP
    不暴露埠,即 ServiceIP 相當於只能在 Service 內部訪問
  2. NodePort
    暴露埠,實際上在任何一個 Node 上都可以根據該埠訪問到

再看看 Service 的資訊

$ kubectl get service -o wide
NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE   SELECTOR
frontend       NodePort    10.99.173.89     <none>        80:30193/TCP   13h   app=guestbook,tier=frontend
redis-master   ClusterIP   10.106.223.234   <none>        6379/TCP       13h   app=redis,role=master,tier=backend

frontend 的型別是 NodePort,ClusterIP(即 ServiceIP)為 10.99.173.89,ServicePort 為 80,NodePort 30193。可以通過兩種方式訪問 Frontend Service

  1. 直接訪問 Service
$ curl http://10.99.173.89:80
  1. 通過 Node 訪問,再轉發到 Service
$ curl http://192.168.20.70:30193
$ curl http://192.168.20.71:30193
$ curl http://192.168.20.72:30193

redis-master 的型別是 ClusterIP,訪問方式只能通過 Service 直接訪問

$ redis-cli -h 10.106.223.234
$ kubectl describe service frontend
Type:       NodePort                                                  # 型別是 NodePort
IP:         10.99.173.89                                              # ServiceIP
Port:       <unset>  80/TCP                                           # ServicePort
TargetPort: 80/TCP                                                    # 對應的 Pod Port
NodePort:   <unset>  30193/TCP                                        # NodePort
Endpoints:  192.167.1.2:80,192.167.1.3:80,192.167.1.4:80 + 2 more...  # 對應 Pod 的 IP:Port

實現

k8s 預設是通過 iptables (也有 使用者空間[不懂] 的 和 ipvs [需要設定才能開啟])來實現轉發的

上面,我們說過,ServiceIP 是虛擬的。虛擬的意思是我們找不到該地址對應的網路裝置
當我們要訪問 ServiceIP 的時候,iptables 匹配到該 ServiceIP 之後,就會轉發到對應的 Pod。原理就這麼簡單,看得懂 iptables 規則的自然也就看懂了,看不懂的說了等於白說 - -

$ iptables-save -t nat      # 也可以使用 iptables -t nat -L 檢視
...

01. -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
02. -A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING

03. -A KUBE-SERVICES ! -s 192.167.0.0/16 -d 10.99.173.89/32 -p tcp -m comment --comment "default/frontend: cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
04. -A KUBE-SERVICES -d 10.99.173.89/32 -p tcp -m comment --comment "default/frontend: cluster IP" -m tcp --dport 80 -j KUBE-SVC-GYQQTB6TY565JPRW

05. -A KUBE-SVC-GYQQTB6TY565JPRW -m statistic --mode random --probability 0.20000000019 -j KUBE-SEP-5M6IMKFOUATBNJIE
06. -A KUBE-SVC-GYQQTB6TY565JPRW -m statistic --mode random --probability 0.25000000000 -j KUBE-SEP-TOJEKTNBMDCO7OBL
07. -A KUBE-SVC-GYQQTB6TY565JPRW -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-2TZ5ELMBBIM6AEEL
08. -A KUBE-SVC-GYQQTB6TY565JPRW -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-BXGUNY74Y2MJVOFH
09. -A KUBE-SVC-GYQQTB6TY565JPRW -j KUBE-SEP-U2DUTXQ5GM4LVCNQ

10. -A KUBE-SEP-TOJEKTNBMDCO7OBL -s 192.167.1.3/32 -j KUBE-MARK-MASQ
11. -A KUBE-SEP-TOJEKTNBMDCO7OBL -p tcp -m tcp -j DNAT --to-destination 192.167.1.3:80
12. -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
13. -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE

相關文章