VIP PaaS在接近兩年時間裏,基於kubernetes主要經歷四次網絡方案的變遷:node
1. kubernetes + flannelgit
2. 基於Docker libnetwork的網絡定製github
3. kubernetes + contiv + kube-haproxydocker
4. 應用容器IP固定api
先簡單說一下背景,PaaS平臺的應用管理包括應用配置管理,應用的運行態管理。一個應用的運行態對應kubernetes的一個Replication Controller(後面使用RC簡稱)和一個Service,應用實例對應kubernetes中的Pod, 咱們基於這樣的管理方式,須要提供應用之間的相互調用,同時對部分應用要提供基於http/tcp的直接訪問。網絡
首先說一下kubernetes + flannel。架構
flannel主要提供了跨主機間的容器通訊;dom
在kubernetes的Pod、Service模型裏,kube-proxy又藉助iptables實現了Pod和Service間通訊。tcp
基於這種網絡訪問功能,咱們平臺提供瞭如下功能:測試
基於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鏈接);
網絡訪問關係圖以下:
在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. 啓動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總體部署結構:
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 訪問設計圖:
在原有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時序圖以下:
Pod的建立在PaaS裏主要有兩種情形:
總體刪除過程爲:由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固定方案會繼續增強穩定性並根據需求進行優化。