前文中我們首先介紹了Docker網絡的相關技術和實現原理,但是我們知道在大規模集群部署的今天,如果我們只通過手動的方式去部署Docker是不現實的,而k8s是目前來說最好用的利器,對容器進行編排治理應用的非常廣泛。這里會首先介紹一些k8s的基礎知識,然后主要介紹一下k8s的網絡情況,主要是與Docker的網絡進行結合,希望看完本篇文章你能夠對雲的網絡情況能夠有更加清晰的理解。
一、k8s的部署
本小節會介紹一些k8s的部署以及一些原語的關系,目的有兩個:一是為了想簡單了解k8s的人能夠快速的了解,再一個是為后面提到的k8s的網絡模型做鋪墊,因為既然要聊到網絡,那么各個節點之間的關系和全局概念就會比較重要。所以為了避免聊起來沒有上下文,所以這第一小節我們就先鋪墊一些基礎的內容。
k8s提供了很多原語的概念,比如Pod ,Service , Controller 等等,他們都可以被看作為一種資源對象,幾乎所有的資源對象都可以通過kubectl工具的相關操作將其保存在etcd中進行持久化存儲,然后通過跟蹤個對比etcd里面保存的'期望資源狀態' 與 當前實際環境中的'實際資源狀態'的差異來實現自動控制和自動糾錯的高級功能。
我們先把k8s的架構圖放在這里,后面聊到每一個點的時候都可以在這里面找到對應關系。
首先介紹一下物理層面的:
- 首先k8s在部署的角度去講也是分主從關系的,主節點叫做Master , 主要負責整個集群的管理和控制基本上k8s的所有控制命令都發給他,他負責具體的執行過程。所以通常來說Master都運行在一個獨立的服務器上,為了HC一般都會部署3台以上。
在Master上面主要運行了3個非常重要的進程 :
- Kubernetes API Server (kube-apiserver) :主要提供了HTTP Rest接口的關鍵服務進程,是集群操作的入口進程
- Kubernetes Controller Manager (kube-controller-manager) : k8s里面所有資源的自動化控制中心,可以將其理解為資源對象的管家
- Kubernetes Scheduler (kube-scheduler) : 負責資源調度的進程,主要用於協調Pod資源
- Node節點 :在k8s集群中除了Master節點,剩下的都叫做Node節點,在集群中作為主要的工作節點而存在,在Node節點上主要運行着容器資源。每個Node 節點有着自己的核心進程,主要用於與Master節點進行通信以及本節點容器管理等操作。
- kubelet : 主要負責Pod對應的容器的創建,啟停等任務
- kube-proxy : 實現k8s Service的通信與負載均衡機制的重要實現着
- Docker Engine (docker) : Docker 引擎,負責本節點容器的創建和管理工作
因為Node 節點可以與Master進行通信,所以可以通過主動注冊機制完成Node節點的擴容,同時也需要向Master節點定期匯報自己節點上的容器運行情況,方便Master進行宏觀的調控。
再看一下邏輯層面的:
- Pod : pod是k8s中非常重要的一個概念,在一個Pod里面可以運行多個容器,他們之間公用Pod的資源,並且每個Pod在創建之后,里面會有一個叫做Pause的根容器,這個容器主要是用來承擔健康檢查的重要任務。
k8s會為每個Pod分配一個唯一的IP,一個Pod里面多個容器共享Pod IP , k8s要求底層網絡支持集群內的任意兩個Pod之間的TCP/IP通信,這通常采用虛擬二層網絡技術來實現,比如Flannel 。
所以需要記住的一點就是在k8s集群中,一個Pod里面的容器與另外主機上的Pod容器能夠直接通信。
- Replication Controller (RC) : rc也是k8s中非常重要的一個概念,簡單的來說,它其實定義了一個期望的場景,既聲明了某種Pod的副本數量在任意時刻都符合某個預期值,一個rc 也就是一個Pod的創建模板,k8s會根據模板的需求去創建或者銷毀一些pod,最終達到一種預期平衡狀態。
- Deployment : 是在1.2版本新引入的一個概念,用於解決Pod的編排問題,為此Deploment在內部使用了Replica Set來實現,所以整體來看可以看作為RC的一次升級,兩者的功能相似度很高,從功能的角度來看我們也可以理解為同一類資源。
Deploment相對於RC的一個最大的升級就是我們可以隨時知道當前Pod的部署進度,實際上有一個Pod的創建、調度、綁定節點以及在目標Node上啟動對應容器這一完整的過程需要一定的時間,所以我們在期待系統啟動目標個數的Pod副本狀態之前其實是一個變化的過程。
- Service : 也是k8s中的一個比較核心的概念,其實我們可以理解為在k8s中每一個Service(SVC) 就是我們經常提起的微服務架構中的一個微服務。之前提到的Pod , RC 等資源對象其實就是SVC的鋪墊。看一張他們之間的關系圖
從這里我們可以看到k8s定義了一個SVC的入口地址,這樣前端的應用就可以通過這個地址來訪問對應的SVC的服務,而SVC的背后是由一組Pod組成的集群實例。RC的作用是保證SVC的服務能力和服務質量始終都符合預期要求。
前面在說到Pod的時候我們提到了每一個Pod都會有一個獨立的Pod IP , 然后通過與內部容器的端口號就組成了一個全新的組合 Endpoint 。那多個Pod組成的Pod 集群怎么來處理前端的請求呢?其實這個負載均衡的操作是有Node上面的kube-proxy進程來完成的。在前端Service 接收到請求的時候,會把請求交給Node上面的kube-proxy,再有kube-proxy來將請求轉發給具體的某一個Pod來處理。每個Pod的創建和銷毀對應的Pod IP就會變化,而SVC一旦創建就會被分配一個Cluster IP ,這個地址在SVC的整個生命周期內是不會變化的。
二、k8s的網絡模型與Docker的關聯性
上面簡單的就介紹這么多,很多點也沒有細致的展開,后面會再單獨進行分享,但是上面的這些內容已經足夠我們理解k8s的網絡內容了。
K8s的網絡模型的一個非常重要的設計原則是:每個Pod都擁有一個獨立的IP,並假定所有的IP都是在一個可以直接連通的,扁平的網絡空間中。所以不管他們是否運行在同一個Node里面,都要求他們可以直接通過對方的IP進行訪問,這樣用戶就不需要考慮Pod之間如何通信,也不需要考慮如何將容器的端口映射到主機的端口上的問題了,這點相對於Docker來說比較的方便。而且這樣在同一個Pod里面的不同容器因為公用Pod的網絡空間,所以他們之間的通信可以用localhost來時間,減少了容器之間通信的麻煩。
我們知道Docker 的網絡主要是借助Iptables來完成通信,但是他的一個非常重要的問題就是在處理多主機互聯的網絡環境下不是很好。所以k8s在設計網絡的時候就充分的考慮到了這點。在k8s中關於網絡的問題主要在一下四個方面做了特殊的解決處理:
- 容器到容器之間的直接通信
- 抽象的Pod到Pod之間的通信
- Pod到Service之間的通信
- 集群外部與內部組件之間的通信
容器之間的通信:
同一個Pod內的容器是共享同一個網絡命名空間的,共享同一個Linux協議棧,所以對於這些容器來說他們就是運行在同一台主機上的應用,他們之間的通信可以直接使用localhost來進行通信,處理起來非常的高效。
Pod之間的通信:
每一個Pod 都有一個全局唯一的IP,所以在同一個Node中不同的Pod之間可以直接通過Pod IP進行通信,根本不需要其他的發現機制。其實這里也是通過docker0虛擬網橋來實現的,不同的Pod 也是通過veth對與宿主機的docker0虛擬網橋進行連接,如果請求的地址不是當前Pod的地址就交給docker0網橋來處理,從而實現了同一個Node不同Pod之間的通信。
那么存在於不同Node上面的Pod的通信呢?這個也是k8s要求的,需要他們之間也可以進行直接通信。那么k8s是怎么實現的呢?我們知道想要位於不同宿主機的兩個Pod進行通信,肯定是要通過宿主機的物理網卡進行通信才能實現,但是我們怎么知道位於不同宿主機上面的Pod的IP呢?
k8s會記錄所有正在運行的Pod的IP分配情況,並且將這些信息保存在etcd之中(作為SVC的Endpoint), 因為k8s的網絡模型是要求任何兩個Pod之間是可以通過Pod IP 進行通信的,那么就需要這些Pod IP是不能出現重復的,但是Pod IP 又是屬於docker0網段的,所以在k8s中docker0網段的分配是需要協調的。只保證了Pod IP 的唯一還不夠,我們還需要知道Pod IP 與 Node IP的映射關系,這樣在進行跨Node之間Pod請求的時候,會首先將數據發送到對應的Node上,然后再由Node將數據轉發到不同的Pod中,從而實現了跨Node之間Pod的直接訪問。
Pod與Service之間的通信:
Service通過Label的選擇在邏輯上將多個Pod抽象為一個服務,這樣在調用對應的服務的時候,就可以很方便的只通過Service Ip (Cluster IP)進行調用,而不需要去關心這個Service后面具體由哪些Pod來提供服務。在這個基礎上也提供了一套負載均衡的策略,可以更加方便的支持服務訪問和水平擴展。
我們知道每一個Service被創建的時候會被分配一個Cluster IP , 而在這個Service 的生命周期內這個IP是不會變的,那么正式因為這個不變的特性可以被用來做成DNS的映射,將Service的名稱和Cluster IP進行映射,而Service的名稱一般情況下我們也不會頻繁的去改變它,所以更加方便了我們直接通過Service name進行服務的訪問。
這個Cluster IP雖然使用起來很方便,但是我們需要注意一個點,就是這個IP只能在k8s的集群內部來使用,在外部是無法訪問這個IP的,而且這個IP也無法被ping通,只能與Pod的端口號一起組成一個Endpoint來提供服務。那么正因為這個Cluster IP 只能在k8s的內部進行使用,那么就可以知道請求的發起方也一定是集群內某個容器中的服務。
每個Service的負載均衡是由在每個Node上面運行的線程kube-proxy來實現的,這個線程通過watch的方式監聽着kube-apiserver寫入etcd中關於Pod的最近變化,一旦檢測到Pod發生了變化,就會立刻將變化體現到對應的iptables 或 ipvs規則中,這樣再進行負載均衡的時候才能保證服務的有效性。
總結的來說,就是在一個容器的內部發出請求,通過容器到對應的Pod,最后通過Pod所在節點的kube-proxy進程進行負載均衡轉發,直接將請求轉發到目標的Pod上面來完成服務的轉發。
集群外部與k8s集群的通信:
Service的Cluster IP在集群的內部很好的解決了多副本Pod之間的復雜均衡的調用問題,但是Cluster IP歸根結底還是一個只在內部使用的IP,在集群的外部是無法直接訪問的。所以怎么從k8s的集群外部來訪問集群內部的服務又是另外一個問題。那目前可使用的方案主要是有三種:NodePort Service , LoadBalance Service , Ingress。
- NordPort Service : Node IP是一個公網可見的物理節點,那么他對應的IP是可以在集群之外直接訪問的,然后將端口映射到具體的Service上,這樣在請求的時候通過Node上面的kube-proxy來實現請求的轉發和負載均衡,但是這樣做如果集群中節點比較多,維護起來會非常的麻煩,而且我們在直接把集群中的節點IP暴露給公網,也是有一定的安全隱患的。
- LoadBalance Service : 這種方案一般是會在公有雲中提供的,通常也是需要支付一定的費用。
- Ingress : 這個是k8s本身提供的一個暴露集群內部服務的解決方案,為訪問集群的請求提供路由規則的集合,簡單的來說就是提供外部可訪問集群的入口,將外部的http/https請求轉發到內部的Service服務上。
所以從狹義的角度去看,Ingress就是一個反向代理,和Nginx是一樣的角色。這里的Ingress我們是把他理解為一種解決方案,在k8s的內部是分成兩部分來實現的:一個是ingress(和解決方式是同樣的名字),另外一個是Ingress Controller 。Ingress 是反向代理的規則,在這里定義什么樣的請求需要轉發到哪個Service上,然后Ingress Controller才是真正的執行反向代理的程序,他負責解析Ingress的反向代理的規則,所有的Ingress Controller都會及時的更新規則,一旦匹配到了任意規則,就會將請求轉發到對應的Service中。