Service 是 Kubernetes 的核心資源類型之一,通常被看作微服務的一種實現。它事實上是一種抽象:通過規則定義出由多個 Pod 對象組合而成的邏輯集合,以及訪問這組 Pod 的策略。Service 關聯 Pod 資源的規則要借助標簽選擇器完成。
Service 對象的 IP 地址(可稱為 ClusterIP 或 ServiceIP)是虛擬 IP 地址,由 Kubernetes 系統在 Service 對象創建時在專用網絡(Service Network)地址中自動分配或由用戶手動指定,并且在 Service 對象的生命周期中保持不變。Service 基于端口過濾到達其 IP 地址的客戶端請求,并根據定義將請求轉發至其后端的 Pod 對象的相應端口之上,因此這種代理機制也稱為“端口代理”或四層代理,工作于 TCP/IP 協議棧的傳輸層。
Service 并不直接連接至 Pod 對象,它們之間還有一個中間層——Endpoints 資源對象,該資源對象是一個由 IP 地址和端口組成的列表,這些 IP 地址和端口則來自由 Service 的標簽選擇器匹配到的 Pod 對象。
一個 Service 對象對應于工作節點內核之中的一組 iptables 或/和 ipvs 規則,這些規則能夠將到達 Service 對象的 ClusterIP 的流量調度轉發至相應 Endpoint 對象指向的 IP 地址和端口之上。內核中的 iptables 或 ipvs 規則的作用域僅為其所在工作節點的一個主機,因而生效于集群范圍內的 Service 對象就需要在每個工作節點上都生成相關規則,從而確保任一節點上發往該 Service 對象請求的流量都能被正確轉發。
每個工作節點的 kube-proxy 組件通過 API Server 持續監控著各 Service 及其關聯的 Pod 對象,并將 Service 對象的創建或變動實時反映至當前工作節點上相應的 iptables 或 ipvs 規則上。
Service 對象的 ClusterIP 事實上是用于生成 iptables 或 ipvs 規則時使用的 IP 地址,它僅用于實現 Kubernetes 集群網絡內部通信,且僅能夠以規則中定義的轉發服務的請求作為目標地址予以響應,這也是它之所以被稱作虛擬 IP 的原因之一。kube-proxy 把請求代理至相應端點的方式有 3 種:userspace、iptables 和 ipvs。
userspace 代理模型,kube-proxy 負責跟蹤 API Server 上 Service 和 Endpoints 對象的變動(創建或移除),并據此調整 Service 資源的定義。對于每個 Service 對象,它會隨機打開一個本地端口(運行于用戶空間的 kube-proxy 進程負責監聽),任何到達此代理端口的連接請求都將被代理至當前 Service 資源后端的各 Pod 對象,至于哪個 Pod 對象會被選中則取決于當前 Service 資源的調度方式,默認調度算法是輪詢(round-robin)。
iptables 代理模型,客戶端發來請求將直接由相關的 iptables 規則進行目標地址轉換(DNAT)后根據算法調度并轉發至集群內的 Pod 對象之上,而無須再經由 kube-proxy 進程進行處理。
ipvs 代理模型,kube-proxy 跟蹤 API Server 上 Service 和 Endpoints 對象的變動,并據此來調用 netlink 接口創建或變更 ipvs(NAT)規則,它與 iptables 規則的不同之處僅在于客戶端請求流量的調度功能由 ipvs 實現,余下的其他功能仍由 iptables 完成。
ipvs 代理模型中 Service 的服務發現和負載均衡功能均基于內核中的 ipvs 規則實現。類似于 iptables,ipvs 也構建于內核中的 netfilter 之上,但它使用 hash 表作為底層數據結構且工作于內核空間,因此具有流量轉發速度快、規則同步性能好的特性,適用于存在大量 Service 資源且對性能要求較高的場景。ipvs 代理模型支持 rr、lc、dh、sh、sed 和 nq 等多種調度算法。
Service 資源都可統一根據其工作邏輯分為 ClusterIP、NodePort、LoadBalancer 和 ExternalName 這 4 種類型。
通過將 Service 映射至由 externalName 字段的內容指定的主機名來暴露服務,此主機名需要被 DNS 服務解析至 CNAME 類型的記錄中。此種類型不是定義由 Kubernetes 集群提供的服務,而是把集群外部的某服務以 DNS CNAME 記錄的方式映射到集群內,從而讓集群內的 Pod 資源能夠訪問外部服務的一種實現方式,這種類型的 Service 沒有 ClusterIP 和 NodePort,沒有標簽選擇器用于選擇 Pod 資源,也不會有 Endpoints 存在。
若需要將 Service 資源發布至集群外部,應該將其配置為 NodePort 或 Load-Balancer 類型,而若要把外部的服務發布于集群內部供 Pod 對象使用,則需要定義一個 ExternalName 類型的 Service 資源。