1. 程式人生 > >唯品會基於Kubernetes(k8s)網路方案演進_Kubernetes中文社群

唯品會基於Kubernetes(k8s)網路方案演進_Kubernetes中文社群

VIP PaaS在接近兩年時間裡,基於kubernetes主要經歷四次網路方案的變遷:

1. kubernetes + flannel

2. 基於Docker libnetwork的網路定製

3. kubernetes + contiv + kube-haproxy

4. 應用容器IP固定

先簡單說一下背景,PaaS平臺的應用管理包括應用配置管理,應用的執行態管理。一個應用的執行態對應kubernetes的一個Replication Controller(後面使用RC簡稱)和一個Service,應用例項對應kubernetes中的Pod, 我們基於這樣的管理方式,需要提供應用之間的相互呼叫,同時對部分應用要提供基於http/tcp的直接訪問。

首先說一下kubernetes + flannel。

flannel主要提供了跨主機間的容器通訊;

在kubernetes的Pod、Service模型裡,kube-proxy又藉助iptables實現了Pod和Service間通訊。

基於這種網路訪問功能,我們平臺提供了以下功能:

基於gorouter提供的平臺域名的訪問 – watch k8s endpoints event管理router資訊;

基於skydns並定製化kube2sky元件和kubelet,提供同一名稱空間下應用(Pod)之間基於業務域名的訪問 – kube2sky基於k8s Service annotation解析並註冊域名資訊、kubelet設定容器啟動時的domain search及外部dns;

實現容器tty訪問控制檯 – 每臺k8s node部署平臺元件 tty agent(根據Pod所屬node資訊, 建立對應k8s結點的tty連線);

網路訪問關係圖如下:

tu1
在k8s + flannel的模型下,容器網路是封閉子網,可以提供平臺內部應用之間基於4層和7層的呼叫,同時對外部提供應用基於域名(工作在七層)的直接訪問,但無法滿足使用者在平臺外部需要直接使用IP訪問的需求。

在flannel網路穩定使用後,開始研究network plugin以使應用服務例項以public IP 方式供使用者直接使用。

當時docker的版本為1.8, 本身還不支援網路外掛.同時 kubernetes本身提供一套基於CNI的網路外掛, 但本身有bug[CNI delete invoked twice with non-infra container id #20379]。

於是我們嘗試從docker network plugin的角度入手,結合libnetwork從docker原始碼的角度進行定製。

整個架構分為三層:

  1. Client Layer – Docker CLI和kubernetes(Docker client);
  2. Docker Layer – Docker daemon 並在程式碼層面整合libnetwork(內建OVS driver);
  3. Controller Layer – ovsdb-server及network controller(自開發IPAM);

整體訪問結構圖:

tu2

整個方案包括以下三個流程:

1. 啟動Docker Daemon:

初始化network controller -> 載入OVS Driver -> OVS Driver呼叫libovsdb建立docker0-ovs Bridge -> OVS Driver將主機上的一物理網絡卡attach到docker0-ovs上;

2. 啟動容器:

OVS Driver 建立veth pair 用於連線network namespaces -> OVS Driver呼叫network controller獲取容器IP和VLAN Tag -> OVS Driver將veth pair的一端新增到docker0-ovs上,並設定VLAN Tag -> OVS Driver設定容器內interface的IP,Mac Address以及路由 -> 設定各network interface為up;

3. 停止容器:

OVS Driver呼叫network controller釋放容器IP -> 刪除network link -> OVS Driver呼叫libovsdb刪除port;

libnetwork工作完成了測試階段但沒有經歷上線,隨著Docker版本的推進,Docker1.9開始支援 contiv netplugin,我們開始研究contiv應用,在期間我們也完成了使用haproxy替換kube-proxy的開發[https://github.com/AdoHe/kube2haproxy],並最後採用docker1.10+contiv上線。

這裡根據我們實際網路訪問關係再描述下PaaS在contiv整體部署結構:

tu3

Kube-haproxy替代了kube-proxy,主要是提供服務ip的公共呼叫,同時避免了容器數量增加後帶來的iptables規則的大量增長,方便除錯。

contiv帶來的方便是使用者可以根據例項IP直接進行訪問;我們在使用過程中整體比較穩定,中間出現過一次問題: 機房停電導致了部分IP的分配狀態不正確,而且contiv當時還沒有提供檢視已分配IP的介面。

Docker 1.10版本支援指定IP啟動容器,並且由於部分應用對例項IP固定有需求,我們開始著手容器IP固定方案的設計與開發。

前面提到應用執行時,對應k8s內一個ReplicationController以及一個Service。 應用的重新部署目前採用的策略主要是重建策略。 重建的流程包括刪除RC及RC下所有Pod,更新並建立新的RC(kubernetes會根據RC配置產生新的POD)。

在預設的k8s+contiv的網路環境下,容器(Pod)的IP網路連線是由contiv network plugin來完成的, contiv master只實現了簡單的IP地址分配和回收,每次部署應用時,並不能保證Pod IP不變。所以我們引入了新的Pod層面的IPAM,以保證同一個應用多次發生部署時,Pod IP始終是不變的。

作為Pod層面的IPAM,我們把這一功能直接整合在了kubernetes。Pod作為k8s的最小排程單元,原有的k8s Pod Registry(主要負責處理所有與Pod以及Pod subresource相關的請求:Pod的增刪改查,Pod的繫結及狀態更新,exec/attach/log等操作) 並不支援在建立Pod時為Pod分配IP,Pod IP是通過獲取Pod Infra Container的IP來獲取的,而Pod Infra Container的IP即為contiv動態分配得來的。

Pod Registry 訪問設計圖:

tu4

在原有kubernetes程式碼基礎上,我們修改了Pod結構(在PodSpec中加入PodIP)並重寫了Pod Registry 同時引入了兩個新的資源物件:

1. Pod IP Allocator: Pod IP Allocator是一個基於etcd的IP地址分配器,主要實現Pod IP的分配與回收。
Pod IP Allocator通過點陣圖記錄IP地址的分配情況,並且將該點陣圖持久化到Etcd;

2. Pod IP Recycler: Pod IP Recycler是一個基於etcd的IP地址回收站,也是實現PodConsistent IP的核心。Pod IP Recycler基於RC全名(namespace + RC name)記錄每一個應用曾經使用過的IP地址,並且在下一次部署的時候預先使用處於回收狀態的IP。

Pod IP Recycler只會回收通過RC建立的Pod的IP,通過其他controller或者直接建立的Pod的IP並不會記錄,所以通過這種方式建立的Pod的IP並不會保持不變; 同時Pod IP Recycle檢測每個已回收IP物件的TTL,目前設定的保留時間為一天。

這裡對kubelet也進行了改造,主要包括根據Pod Spec中指定IP進行相關的容器建立(docker run加入IP指定)以及Pod刪除時釋放IP操作。

建立和刪除Pod的UML時序圖如下:

tu5

Pod的建立在PaaS裡主要有兩種情形:

  1. 應用的第一次部署及擴容,這種情況主要是從IP pool中隨機分配;
  2. 應用的重新部署:在重新部署時,已經釋放的IP已根據RC全名存放於IP Recycle列表中,這裡優先從回收列表中獲取IP,從而達到IP固定的效果。

tu6

整體刪除過程為:由PaaSNg或kube-controller-manager呼叫apiserver Pod Delete並設定DeletionTimestamp, kubelet監聽到刪除時間並獲取GracefulDeletiontime,刪除應用容器, 通知apiserver釋放IP(釋放IP時獲取Pod所屬RC,根據是否有對應RC 名稱決定是否存放在IP Recycle列表),刪除Pause Pod,通知apiserver 刪除Pod物件。

另外為了防止IP固定方案中可能出現的問題,我們在kubernetes中加入了額外的REST api: 包括對已分配IP的查詢,手動分配/釋放IP..。

對目前方案的總結:

容器IP固定方案已上線,執行基本沒問題,但穩定性有待提升。主要表現為偶然性不能在預期時間內停止舊Pod,從而無法釋放IP造成無法複用(初步原因是由於Docker偶爾的卡頓造成無法在規定時間內停止容器)。我們短期的work around是使用額外新增的REST apiss手動修復,後期IP固定方案會繼續加強穩定性並根據需求進行優化。