kubernetes 簡介:kube-proxy 和 service

penghc 7年前發布 | 45K 次閱讀 iptables Kubernetes

簡介

在 kubernetes 集群中,網絡是非常基礎也非常重要的一部分。對于大規模的節點和容器來說,要保證網絡的連通性、網絡轉發的高效,同時能做的 ip 和 port 自動化分配和管理,并讓用戶用直觀簡單的方式來訪問需要的應用,這是需要復雜且細致設計的。

kubernetes 在這方面下了很大的功夫,它通過 service 、 dns 、 ingress 等概念,解決了服務發現、負載均衡的問題,也大大簡化了用戶的使用和配置。

這篇文章就講解如何配置 kubernetes 的網絡,最終從集群內部和集群外部都能訪問應用。

跨主機網絡配置:flannel

一直以來,kubernetes 并沒有專門的網絡模塊負責網絡配置,它需要用戶在主機上已經配置好網絡。kubernetes 對網絡的要求是:容器之間(包括同一臺主機上的容器,和不同主機的容器)可以互相通信,容器和集群中所有的節點也能直接通信。

至于具體的網絡方案,用戶可以自己選擇,目前使用比較多的是 flannel,因為它比較簡單,而且剛好滿足 kubernetes 對網絡的要求。我們會使用 flannel vxlan 模式,具體的配置我在博客之前有文章介紹過,這里不再贅述。

以后 kubernetes 網絡的發展方向是希望通過插件的方式來集成不同的網絡方案, CNI 就是這一努力的結果,flannel 也能夠通過 CNI 插件的形式使用。

kube-proxy 和 service

配置好網絡之后,集群是什么情況呢?我們可以創建 pod,也能通過 ReplicationController 來創建特定副本的 pod(這是更推薦也是生產上要使用的方法,即使某個 rc 中只有一個 pod 實例)。可以從集群中獲取每個 pod ip 地址,然后也能在集群內部直接通過 podIP:Port 來獲取對應的服務。

但是還有一個問題: pod 是經常變化的,每次更新 ip 地址都可能會發生變化 ,如果直接訪問容器 ip 的話,會有很大的問題。而且進行擴展的時候,rc 中會有新的 pod 創建出來,出現新的 ip 地址,我們需要一種更靈活的方式來訪問 pod 的服務。

Service 和 cluster IP

針對這個問題,kubernetes 的解決方案是“服務”(service),每個服務都一個固定的虛擬 ip(這個 ip 也被稱為 cluster IP),自動并且動態地綁定后面的 pod,所有的網絡請求直接訪問服務 ip,服務會自動向后端做轉發。Service 除了提供穩定的對外訪問方式之外,還能起到負載均衡(Load Balance)的功能,自動把請求流量分布到后端所有的服務上,服務可以做到對客戶透明地進行水平擴展(scale)。

而實現 service 這一功能的關鍵,就是 kube-proxy。kube-proxy 運行在每個節點上,監聽 API Server 中服務對象的變化,通過管理 iptables 來實現網絡的轉發。

NOTE: kube-proxy 要求 NODE 節點操作系統中要具備 /sys/module/br_netfilter 文件,而且還要設置 bridge-nf-call-iptables=1,如果不滿足要求,那么 kube-proxy 只是將檢查信息記錄到日志中,kube-proxy 仍然會正常運行,但是這樣通過 Kube-proxy 設置的某些 iptables 規則就不會工作。

kube-proxy 有兩種實現 service 的方案:userspace 和 iptables

  • userspace 是在用戶空間監聽一個端口,所有的 service 都轉發到這個端口,然后 kube-proxy 在內部應用層對其進行轉發。因為是在用戶空間進行轉發,所以效率也不高
  • iptables 完全實現 iptables 來實現 service,是目前默認的方式,也是推薦的方式,效率很高(只有內核中 netfilter 一些損耗)。

這篇文章通過 iptables 模式運行 kube-proxy,后面的分析也是針對這個模式的,userspace 只是舊版本支持的模式,以后可能會放棄維護和支持。

kube-proxy 參數介紹

kube-proxy 的功能相對簡單一些,也比較獨立,需要的配置并不是很多,比較常用的啟動參數包括:

參數 含義 默認值
–alsologtostderr 打印日志到標準輸出 false
–bind-address HTTP 監聽地址 0.0.0.0
–cleanup-iptables 如果設置為 true,會清理 proxy 設置的 iptables 選項并退出 false
–healthz-bind-address 健康檢查 HTTP API 監聽端口 127.0.0.1
–healthz-port 健康檢查端口 10249
–iptables-masquerade-bit 使用 iptables 進行 SNAT 的掩碼長度 14
–iptables-sync-period iptables 更新頻率 30s
–kubeconfig kubeconfig 配置文件地址  
–log-dir 日志文件目錄/路徑  
–masquerade-all 如果使用 iptables 模式,對所有流量進行 SNAT 處理 false
–master kubernetes master API Server 地址  
–proxy-mode 代理模式, userspace 或者 iptables , 目前默認是 iptables ,如果系統或者 iptables 版本不夠新,會 fallback 到 userspace 模式 iptables
–proxy-port-range 代理使用的端口范圍, 格式為 beginPort-endPort ,如果沒有指定,會隨機選擇 0-0
–udp-timeout UDP 空連接 timeout 時間,只對 userspace 模式有用 250ms
–v 日志級別 0

kube-proxy 的工作模式可以通過 --proxy-mode 進行配置,可以選擇 userspace 或者 iptables 。

實例啟動和測試

我們可以在終端上啟動 kube-proxy ,也可以使用諸如 systemd 這樣的工具來管理它,比如下面就是一個簡單的 kube-proxy.service 配置文件

[root@localhost]# cat /usr/lib/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Proxy Service
Documentation=http://kubernetes.com
After=network.target
Wants=network.target

[Service] Type=simple EnvironmentFile=-/etc/sysconfig/kube-proxy ExecStart=/usr/bin/kube-proxy \ --master=http://172.17.8.100:8080 \ --v=4 \ --proxy-mode=iptables TimeoutStartSec=0 Restart=on-abnormal

[Install] WantedBy=multi-user.target</pre>

為了方便測試,我們創建一個 rc,里面有三個 pod。這個 pod 運行的是 cizixs/whoami 容器 ,它是一個簡單的 HTTP 服務器,監聽在 3000 端口,訪問它會返回容器的 hostname。

[root@localhost ~]# cat whoami-rc.yml
apiVersion: v1
kind: ReplicationController
metadata:
  name: whoami
spec:
  replicas: 3
  selector:
    app: whoami
  template:
    metadata:
      name: whoami
      labels:
        app: whoami
        env: dev
    spec:
      containers:

  - name: whoami
    image: cizixs/whoami:v0.5
    ports:
    - containerPort: 3000
    env:
      - name: MESSAGE
        value: viola</pre> 

我們為每個 pod 設置了兩個 label: app=whoami 和 env=dev ,這兩個標簽很重要,也是后面服務進行綁定 pod 的關鍵。

為了使用 service,我們還要定義另外一個文件,并通過 kubectl create -f ./whoami-svc.yml 來創建出來對象:

apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami
  name: whoami
spec:
  ports:
    - port: 3000
      targetPort: 3000
      protocol: TCP
  selector:
    app: whoami
    env: dev

其中 selector 告訴 kubernetes 這個 service 和后端哪些 pod 綁定在一起,這里包含的鍵值對會對所有 pod 的 labels 進行匹配,只要完全匹配,service 就會把 pod 作為后端。也就是說,service 和 rc 并不是對應的關系,一個 service 可能會使用多個 rc 管理的 pod 作為后端應用。

ports 字段指定服務的端口信息:

  • port :虛擬 ip 要綁定的 port,每個 service 會創建出來一個虛擬 ip,通過訪問 vip:port 就能獲取服務的內容。這個 port 可以用戶隨機選取,因為每個服務都有自己的 vip,也不用擔心沖突的情況
  • targetPort :pod 中暴露出來的 port,這是運行的容器中具體暴露出來的端口,一定不能寫錯
  • protocol :提供服務的協議類型,可以是 TCP 或者 UDP

創建之后可以列出 service ,發現我們創建的 service 已經分配了一個虛擬 ip (10.10.10.28),這個虛擬 ip 地址是不會變化的(除非 service 被刪除)。查看 service 的詳情可以看到它的 endpoints 列出,對應了具體提供服務的 pod 地址和端口。

[root@localhost ~]# kubectl get svc
NAME         CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
kubernetes   10.10.10.1    <none>        443/TCP    19d
whoami       10.10.10.28   <none>        3000/TCP   1d

[root@localhost ~]# kubectl describe svc whoami
Name:                   whoami
Namespace:              default
Labels:                 name=whoami
Selector:               app=whoami
Type:                   ClusterIP
IP:                     10.10.10.28
Port:                   <unset> 3000/TCP
Endpoints:              10.11.32.6:3000,10.13.192.4:3000,10.16.192.3:3000
Session Affinity:       None
No events.

默認的 service 類型是 ClusterIP ,這個也可以從上面輸出看出來。在這種情況下,只能從集群內部訪問這個 IP,不能直接從集群外部訪問服務。如果想對外提供服務,我們后面會講解決方案。

測試一下,訪問 service 服務的時候可以看到它會隨機地訪問后端的 pod,給出不同的返回:

[root@localhost ~]# curl http://10.10.10.28:3000
viola from whoami-8fpqp
[root@localhost ~]# curl http://10.10.10.28:3000
viola from whoami-c0x6h
[root@localhost ~]# curl http://10.10.10.28:3000
viola from whoami-8fpqp
[root@localhost ~]# curl http://10.10.10.28:3000
viola from whoami-dc9ds

默認情況下,服務會隨機轉發到可用的后端。如果希望保持會話(同一個 client 永遠都轉發到相同的 pod),可以把 service.spec.sessionAffinity 設置為 ClientIP 。

NOTE: 需要注意的是,服務分配的 cluster IP 是一個虛擬 ip,如果你嘗試 ping 這個 IP 會發現它沒有任何響應,這也是剛接觸 kubernetes service 的人經常會犯的錯誤。實際上,這個虛擬 IP 只有和它的 port 一起的時候才有作用,直接訪問它,或者想訪問該 IP 的其他端口都是徒勞。

外部能夠訪問的服務

上面創建的服務只能在集群內部訪問,這在生產環境中還不能直接使用。如果希望有一個能直接對外使用的服務,可以使用 NodePort 或者 LoadBalancer 類型的 Service。我們先說說 NodePort ,它的意思是在所有 worker 節點上暴露一個端口,這樣外部可以直接通過訪問 nodeIP:Port 來訪問應用。

我們先把剛才創建的服務刪除:

[root@localhost ~]# kubectl delete rc whoami
replicationcontroller "whoami" deleted

[root@localhost ~]# kubectl delete svc whoami
service "whoami" deleted

[root@localhost ~]# kubectl get pods,svc,rc
NAME         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   10.10.10.1   <none>        443/TCP   14h

對我們原來的 Service 配置文件進行修改,把 spec.type 寫成 NodePort 類型:

[root@localhost ~]# cat whoami-svc.yml
apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami
  name: whoami
spec:
  ports:
    - port: 3000
      protocol: TCP
      # nodePort: 31000
  selector:
    app: whoami
  type: NodePort

因為我們的應用比較簡單,只有一個端口。如果 pod 有多個端口,也可以在 spec.ports 中繼續添加,只有保證多個 port 之間不沖突就行。

重新創建 rc 和 svc:

[root@localhost ~]# kubectl create -f ./whoami-svc.yml
service "whoami" created
[root@localhost ~]# kubectl get rc,pods,svc
NAME        DESIRED   CURRENT   READY     AGE
rc/whoami   3         3         3         10s

NAME              READY     STATUS    RESTARTS   AGE
po/whoami-8zc3d   1/1       Running   0          10s
po/whoami-mc2fg   1/1       Running   0          10s
po/whoami-z6skj   1/1       Running   0          10s

NAME             CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
svc/kubernetes   10.10.10.1     <none>        443/TCP          14h
svc/whoami       10.10.10.163   <nodes>       3000:31647/TCP   7s

需要注意的是,因為我們沒有指定 nodePort 的值,kubernetes 會自動給我們分配一個,比如這里的 31647 (默認的取值范圍是 30000-32767)。當然我們也可以刪除配置中 # nodePort: 31000 的注釋,這樣會使用 31000 端口。

nodePort 類型的服務會在所有的 worker 節點(運行了 kube-proxy)上統一暴露出端口對外提供服務,也就是說外部可以任意選擇一個節點進行訪問。比如我本地集群有三個節點: 172.17.8.100 、 172.17.8.101 和 172.17.8.102 :

[root@localhost ~]# curl http://172.17.8.100:31647
viola from whoami-mc2fg
[root@localhost ~]# curl http://172.17.8.101:31647
viola from whoami-8zc3d
[root@localhost ~]# curl http://172.17.8.102:31647
viola from whoami-z6skj

有了 nodePort ,用戶可以通過外部的 Load Balance 或者路由器把流量轉發到任意的節點,對外提供服務的同時,也可以做到負載均衡的效果。

nodePort 類型的服務并不影響原來虛擬 IP 的訪問方式,內部節點依然可以通過 vip:port 的方式進行訪問。

LoadBalancer 類型的服務需要公有云支持,如果你的集群部署在公有云(GCE、AWS等)可以考慮這種方式。

service 原理解析

目前 kube-proxy 默認使用 iptables 模式,上述展現的 service 功能都是通過修改 iptables 實現的。

我們來看一下從主機上訪問 service:port 的時候發生了什么(通過 iptables-save 命令打印出來當前機器上的 iptables 規則)。

所有發送出去的報文會進入 KUBE-SERVICES 進行處理

*nat
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

KUBE-SERVICES 每條規則對應了一個 service,它告訴繼續進入到某個具體的 service chain 進行處理,比如這里的 KUBE-SVC-OQCLJJ5GLLNFY3XB

-A KUBE-SERVICES -d 10.10.10.28/32 -p tcp -m comment --comment "default/whoami: cluster IP" -m tcp --dport 3000 -j KUBE-SVC-OQCLJJ5GLLNFY3XB

更具體的 chain 中定義了怎么轉發到對應 endpoint 的規則,比如我們的 rc 有三個 pods,這里也就會生成三個規則。這里利用了 iptables 隨機和概率轉發的功能

-A KUBE-SVC-OQCLJJ5GLLNFY3XB -m comment --comment "default/whoami:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-VN72UHNM6XOXLRPW
-A KUBE-SVC-OQCLJJ5GLLNFY3XB -m comment --comment "default/whoami:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-YXCSPWPTUFI5WI5Y
-A KUBE-SVC-OQCLJJ5GLLNFY3XB -m comment --comment "default/whoami:" -j KUBE-SEP-FN74S3YUBFMWHBLF

我們來看第一個 chain,這個 chain 有兩個規則,第一個表示給報文打上 mark;第二個是進行 DNAT(修改報文的目的地址),轉發到某個 pod 地址和端口。

-A KUBE-SEP-VN72UHNM6XOXLRPW -s 10.11.32.6/32 -m comment --comment "default/whoami:" -j KUBE-MARK-MASQ
-A KUBE-SEP-VN72UHNM6XOXLRPW -p tcp -m comment --comment "default/whoami:" -m tcp -j DNAT --to-destination 10.11.32.6:3000

因為地址是發送出去的,報文會根據路由規則進行處理,后續的報文就是通過 flannel 的網絡路徑發送出去的。

nodePort 類型的 service 原理也是類似的,在 KUBE-SERVICES chain 的最后,如果目標地址不是 VIP 則會通過 KUBE-NODEPORTS :

Chain KUBE-SERVICES (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 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

而 KUBE-NODEPORTS chain 和 KUBE-SERVICES chain 其他規則一樣,都是轉發到更具體的 service chain,然后轉發到某個 pod 上面。

-A KUBE-NODEPORTS -p tcp -m comment --comment "default/whoami:" -m tcp --dport 31647 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/whoami:" -m tcp --dport 31647 -j KUBE-SVC-OQCLJJ5GLLNFY3XB

不足之處

看起來 service 是個完美的方案,可以解決服務訪問的所有問題,但是 service 這個方案(iptables 模式)也有自己的缺點。

首先,如果轉發的 pod 不能正常提供服務,它不會自動嘗試另一個 pod,當然這個可以通過 readiness probes 來解決。每個 pod 都有一個健康檢查的機制,當有 pod 健康狀況有問題時,kube-proxy 會刪除對應的轉發規則。

另外, nodePort 類型的服務也無法添加 TLS 或者更復雜的報文路由機制。

參考資料

 

來自:http://cizixs.com/2017/03/30/kubernetes-introduction-service-and-kube-proxy

 

 本文由用戶 penghc 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!