網絡介紹:Kubernetes設計文檔

模型和動機node

Kubernetes從Docker默認的網絡模型中獨立出來造成一套本身的網絡模型。該網絡模型的目標是:每個pod都擁有一個扁平化共享網絡命名空間的IP,經過該IP,pod就可以跨網絡與其它物理機和容器進行通訊。一個pod一個IP模型建立了一個乾淨、反向兼容的模型,在該模型中,從端口分配、網絡、域名解析、服務發現、負載均衡、應用配置和遷移等角度,pod都可以被當作虛擬機或物理機。linux

另外一方面,動態端口分配須要如下方面的支持:web

固定端口(例如:用於外部可訪問服務)和動態分配端口;
分割集中分配和本地獲取的動態端口;  不過,這不但使調度複雜化(由於端口是一種稀缺資源),並且應用程序的配置也將變得複雜,具體表現爲端口衝突、重用和耗盡;
使用非標準方法進行域名解析(例如:etcd而不是DNS);
對使用標準域名/地址解析機制的程序(例如:web瀏覽器)使用代理和/或重定向;
除了監控和緩存實例的非法地址/端口變化外,還要監控用戶組成員變化以及阻止容器/pod遷移(例如:使用CRIU)。
NAT將地址空間分段的作法引入了額外的複雜性,這將帶來諸如破壞自注冊機制等問題。

在一個pod一個IP模型中,從網絡角度看,在一個pod中的全部用戶容器都像是在同一臺宿主機中那樣。它們可以在本地訪問其它用戶容器的端口。暴露給主機網卡的端口是經過普通Docker方式實現的。全部pod中的容器可以經過他們「10」網段(10.x.x.x)的IP地址進行通訊。docker

除了可以避免上述動態端口分配帶來的問題,該方案還能使應用平滑地從非容器環境(物理機或虛擬機)遷移到同一個pod內的容器環境。在同一臺宿主機上運行應用程序棧這種場景已經找到避免端口衝突的方法(例如:經過配置環境變量)並使客戶端可以找到這些端口。瀏覽器

該方案確實下降了pod中容器之間的隔離性——儘管端口可能存在衝突並且也不存在pod內跨容器的私有端口,可是對於須要本身的端口範圍的應用程序能夠運行在不一樣的pod中,而對於須要進行私有通訊的進程則能夠運行在同一個容器內。另外,該方案假定的前提條件是:在同一個pod中的容器共享一些資源(例如:磁盤卷、處理器、內存等),所以損失部分隔離性也在可接受範圍以內。此外,儘管用戶可以指定不一樣容器歸屬到同一個pod,但通常狀況下不能指定不一樣pod歸屬於同一臺主機。緩存

當任意一個容器調用SIOCGIFADDR(發起一個獲取網卡IP地址的請求)時,它所得到的IP和與之通訊的容器看到的IP是同樣的——每一個pod都有一個可以被其它pod識別的IP。經過無差異地對待容器和pdo內外部的IP和端口,咱們建立了一個非NAT的扁平化地址空間。」ip addr show」可以像預期那樣正常工做。該方案可以使全部現有的域名解析/服務發現機制:包括自注冊機制和分配IP地址的應用程序在容器外可以正常運行(咱們應該用etcd、Euraka(用於Acme Air)或Consul等軟件測試該方案)。對pod之間的網絡通訊,咱們應該持樂觀態度。在同一個pod中的容器之間更傾向於經過內存卷(例如:tmpfs)或IPC(進程間通訊)的方式進行通訊。服務器

該模型與標準Docker模型不一樣。在該模型中,每一個容器會獲得一個「172」網段(172.x.x.x)的IP地址,並且經過SIOCGIFADDR也只能看到一個「172」網段(172.x.x.x)的IP地址。若是這些容器與其它容器鏈接,對方容器看到的IP地址與該容器本身經過SIOCGIFADDR請求獲取的IP地址不一樣。簡單地說,你永遠沒法在容器中註冊任何東西或服務,由於一個容器不可能經過其私有IP地址被外界訪問到。網絡

咱們想到的一個解決方案是增長額外的地址層:以pod爲中心的一個容器一個IP模式。每一個容器只擁有一個本地IP地址,且只在pod內可見。這將使得容器化應用程序可以更加容易地從物理/虛擬機遷移到pod,但實現起來很複雜(例如:要求爲每一個pod建立網橋,水平分割/虛擬私有的 DNS),並且能夠預見的是,因爲新增了額外的地址轉換層,將破壞現有的自注冊和IP分配機制。負載均衡

當前實現測試

Google計算引擎(GCE)集羣配置了高級路由,使得每一個虛擬機都有額外的256個可路由IP地址。這些是除了分配給虛擬機的經過NAT用於訪問互聯網的「主」IP以外的IP。該實如今Docker外部建立了一個叫作cbr0的網橋(爲了與docker0網橋區別開),該網橋只負責對從容器內部流向外部的網絡流量進行NAT轉發。

目前,從「主」IP(即互聯網,若是制定了正確的防火牆規則的話)發到映射端口的流量由Docker的用戶模式進行代理轉發。將來,端口轉發應該由Kubelet或Docker經過iptables進行:Issue #15。

啓動Docker時附加參數:DOCKER_OPTS=」–bridge cbr0 –iptables=false」。

並用SaltStack在每一個node中建立一個網橋,代碼見container_bridge.py:

cbr0:
container_bridge.ensure:
– cidr: {{ grains[‘cbr-cidr’] }}
…
grains:
roles:
– kubernetes-pool
cbr-cidr: $MINION_IP_RANGE

在GCE中,咱們讓如下IP地址可路由:

gcloud compute routes add 「${MINION_NAMES[$i]}」 \
–project 「${PROJECT}」 \
–destination-range 「${MINION_IP_RANGES[$i]}」 \
–network 「${NETWORK}」 \
–next-hop-instance 「${MINION_NAMES[$i]}」 \
–next-hop-instance-zone 「${ZONE}」 &

以上代碼中的MINION_IP_RANGES是以10.開頭的24位網絡號IP地址空間(10.x.x.x/24)。

儘管如此,GCE自己並不知道這些IP地址的任何信息。

這些IP地址不是外部可路由的,所以,那些有與外界通訊需求的容器須要使用宿主機的網絡。若是外部流量要轉發給虛擬機,它只會被轉發給虛擬機的主IP(該IP不會被分配給任何一個pod),所以咱們使用docker的-p標記把暴露的端口映射到主網卡上。該方案的帶來的反作用是不容許兩個不一樣的pod暴露同一個端口(更多相關討論見Issue #390)。

咱們建立了一個容器用於管理pod的網絡命名空間,該網絡容器內有一個本地迴環設備和一塊虛擬以太網卡。全部的用戶容器從該pod的網絡容器那裏獲取他們的網絡命名空間。

Docker在它的「container」網絡模式下從網橋(咱們爲每一個節點上建立了一個網橋)那裏分配IP地址,具體步驟以下:

使用最小鏡像建立一個普通容器(從網絡角度)並運行一條永遠阻塞的命令。這不是一個用戶定義的容器,給它一個特別的衆所周知的名字;
 建立一個新的網絡命名空間(netns)和本地迴路設備;
 建立一對新的虛擬以太網設備並將它綁定到剛剛建立的網絡命名空間;
 從docker的IP地址空間中自動分配一個IP;

在建立用戶容器時指定網絡容器的名字做爲它們的「net」參數。Docker會找到在網絡容器中運行的命令的PID並將它綁定到該PID的網絡命名空間去。

其餘網絡實現例子

其餘用於在GCE外部實現一個pod一個IP模型的方案:

OpenVSwitch with GRE/VxLAN
Flannel

挑戰和將來的工做

Docker API

目前,docker inspect並不展現容器的網絡配置信息,由於它們從另外一個容器提取該信息。該信息應該以某種方式暴露出來。

外部IP分配

咱們但願可以從Docker外部分配IP地址(Docker issue #6743),這樣咱們就沒必要靜態地分配固定大小的IP範圍給每一個節點,並且即便網絡容器重啓也能保持容器IP地址固定不變(Docker issue #2801),這樣就使得pod易於遷移。目前,若是網絡容器掛掉,全部的用戶容器必須關閉再重啓,由於網絡容器重啓後其網絡命名空間會發生變化並且任何一個隨後重啓的用戶容器會加入新的網絡命名空間進而致使沒法與對方容器通訊。另外,IP地址的改變將會引起DNS緩存/生存時間問題。外部IP分配方案會簡化DNS支持(見下文)。

域名解析,服務發現和負載均衡

除了利用第三方的發現機制來啓用自注冊外,咱們還但願自動創建DDNS(Issue #146)。hostname方法,$HOSTNAME等應該返回一個pod的名字(Issue #298),gethostbyname方法應該要可以解析其餘pod的名字。咱們可能會創建一個DNS解析服務器來作這些事情(Docker issue #2267),因此咱們沒必要動態更新/etc/hosts文件。

服務端點目前是經過環境變量獲取的。Docker連接兼容變量和kubernetes指定變量({NAME}_SERVICE_HOST和{NAME}_SERVICE_BAR)都是支持的,並會被解析成由服務代理打開的端口。事實上,咱們並不使用docker特使模式來連接容器,由於咱們不須要應用程序在配置階段識別全部客戶端。雖然現在的服務都是由服務代理管理的,可是這是一個應用程序不該該依賴的實現細節,客戶端應該使用服務入口IP(以上環境變量會被解析成該IP)。然而,一個扁平化的服務命名空間沒法伸縮,並且環境變量不容許動態更新,這使得經過應用隱性的順序限制進行服務部署變得複雜。咱們打算在DNS中爲每一個服務註冊入口IP,而且但願這種方式可以成爲首選解決方案。

咱們也但願適應其它的負載均衡方案(例如:HAProxy),非負載均衡服務(Issue #260)和其它類型的分組(例如:線程池等)。提供監測應用到pod地址的標記選擇器的能力使有效地監控用戶組成員狀態變得可能,這些監控數據將直接被服務發現機制消費或同步。用於監聽/取消監聽事件的事件鉤(Issue #140)將使上述目標更容易實現。

外部可路由性

咱們但願跨節點的容器間網絡通訊使用pod IP地址。假設節點A的容器IP地址空間是10.244.1.0/24,節點B的容器的IP地址空間是10.244.2.0/24,且容器A1的的IP地址是10.244.1.1,容器B1的IP地址是10.244.2.1。如今,咱們但願容器A1可以直接與容器B1通訊而不經過NAT,那麼容器B1看到IP包的源IP應該是10.244.1.1而不是節點A的主宿主機IP。這意味着咱們但願關閉用於容器之間(以及容器和虛擬機之間)通訊的NAT。

咱們也但願從外部互聯網可以直接路由到pod。然而,咱們還沒法提供額外的用於直接訪問互聯網的容器IP。所以,咱們不將外部IP映射到容器IP。咱們經過讓終點不是內部網絡(!10.0.0.0/8)的流量通過主宿主機IP走NAT來解決這個問題,這樣與因特網通訊時就能經過GCE網絡實現1:1NAT轉換。相似地,從因特網流向內部網絡的流量也可以經宿主機IP實現NAT轉換/代理。

所以,讓咱們用三個用例場景結束上面的討論:

容器->容器或容器< ->虛擬機。該場景須要直接使用以10.開頭的地址(10.x.x.x),而且不須要用到NAT;

容器->因特網。容器IP須要映射到主宿主機IP好讓GCE知道如何向外發送這些流量。這裏就有兩層NAT:容器IP->內部宿主機IP->外部主機IP。第一層結合IP表發生在客戶容器上,第二層則是GCE網絡的一部分。第一層(容器IP->內部宿主機IP)進行了動態端口分配,而第二層的端口映射是1:1的;

因特網->容器。該場景的流量必須經過主宿主機IP並且理想狀態下也擁有兩層NAT。可是,目前的流量路徑是:外部主機IP -> 內部主機IP -> Docker) -> (Docker -> 容器IP),即Docker是中間代理。一旦(issue #15)被解決,那麼就應該是:外部主機IP -> 內部主機IP -> 容器IP。可是,爲了實現後者,就必須爲每一個管理的端口創建端口轉發的防火牆(iptables)。

若是咱們有辦法讓外部IP路由到集羣中的pod,咱們就能夠採用爲每一個pod建立一個新的宿主機網卡別名的方案。該方案可以消除使用宿主機IP地址帶來的調度限制。

IPv6

IPv6將會是一個有意思的選項,但咱們還無法使用它。支持IPv6已經被提到Docker的日程上了:Docker issue #2974,Docker issue #6923,Docker issue #6975。另外,主流雲服務提供商(例如:AWS EC2,GCE)目前也不支持直接給虛擬機綁定IPv6地址。儘管如此,咱們將很高興接受在物理機上運行Kubernetes的用戶提交的代碼。

相關文章
相關標籤/搜索