基本概念
當應用由單體架構轉向微服務架構時,應用被拆成很多小的互相協作的微服務,每個服務會以多個副本運行,副本數量會隨着系統所需的處理能力進行變化,這就是微服務的伸縮性。 微服務的負載均衡器對實現伸縮性起了十分重要的作用。
Service是Kubernetes最重要的資源對象。Kubernetes中的Service對象可以對應微服務架構中的微服務。Service定義了服務的訪問入口,服務的調用者Pod通過這個地址訪問Service后端的Pod副本實例。 Service通過Label Selector同后端的Pod副本建立關系,Replication Controller保證后端Pod副本的數量,也就是保證服務的伸縮性。
service代理模式
我們知道,kubernetes的node節點運行的時候,需要啟動兩個進程,分別是kubelet和kube-proxy。其中kubeproxy實際上就是一個智能的負載均衡器。發送到service的請求由kube-proxy轉發到后端在的某個pod實例上。
kubernetes為每個service分配一個全局唯一的虛擬IP,叫做ClusterIP,這樣在整個集群中,服務的調用者都通過ClusterIP和服務進行通信。
在Service的整個生命周期內Service的名稱和ClusterIP保持不變,因此通過引入域名服務將Service的名稱和ClusterIP建立DNS域名映射,服務的調用者可以通過使用服務的名稱來訪問服務。
kube-proxy作為一個集群內部的負載均衡器,支持多種代理模式:
- userspace代理模式
- iptables代理模式
- ipvs代理模式
userspace代理模式
這是kubernetes 1.1版本之前支持的模式,當前基本已棄用。
這種模式下,kube-proxy會watch kube-apiserver對Service對象和Endpoints對象的添加和移除。 並在所有node上為每一個service打開一個本地的隨機端口。然后在每個node上配置iptables規則來捕獲到達service的請求,並將其重定向至本地為該service打開的隨機端口,完成代理訪問。
iptables代理模式
這是當前kubernetes默認使用的service的代理模式。
和userspace模式一樣,其也使用iptables規則來捕獲對cluster ip的訪問,但是它會通過iptables的dnat規划直接請請求轉發至具體的backend pod,而不需要在node為每個service打開一個本地隨機端口。相對於userspace,其擁有更好的轉發性能,同時如果初始轉發的pod失敗沒有響應,Iptables代理能夠自動的重試另一個pod。
ipvs代理模式
ipvs模式在kubernetes 1.8版本開始引入,1.11版本正式GA。不過要啟用該模式,仍然需要修改kube-proxy配置。
這種模式通過ipvs實現轉輸層的負載均衡。相對於iptables代理模式,其優勢如下:
- 為大型集群提供了更好的可擴展性和性能
- 支持比iptables更復雜的復制均衡算法(最小負載、最少連接、加權等等)
- 支持服務器健康檢查和連接重試等功能
ipvs也依賴iptables,ipvs會使用iptables進行包過濾、SNAT、masquared(偽裝)。
配置Service
kubrenetes支持四種類型的service,可以通過ServicesTypes指定:
- ClusterIP:僅僅使用一個集群內部的地址,這也是默認值,使用該類型,意味着,service只能在集群內部被訪問
- NodePort:在集群內部的每個節點上,都開放這個服務。可以在任意的
:NodePort地址上訪問到這個服務 - LoadBalancer:這是當kubernetes部署到公有雲上時才會使用到的選項,是向雲提供商申請一個負載均衡器,將流量轉發到已經以NodePort形式開放的service上。
- ExternalName:ExternalName實際上是將service導向一個外部的服務,這個外部的服務有自己的域名,只是在kubernetes內部為其創建一個內部域名,並cname至這個外部的域名。
下面示例創建一個nginx的deployment,包含三個pod,后面所有創建的service都會與之關聯:
創建一個nginx的Deployment:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort:80
可以通過如下指令查看創建的pod:
kubectl get pod -l app=nginx -o wide
創建ClusterIP類型的Service
普通ClusterIP類型的Service
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
targetPort: 80
selector:
app: nginx
需要說明下三個端口的意義,其中port表示service監聽的端口,targetPort表示后端Pod監聽的端口,nodePort表示如果要將service暴露出來,外部訪問的端口。不指定ClusterIP,則默認使用ClusterIP方式創建Service,並自動生成一個ClusterIP。可以通過查看service來看到ClusterIP。
查看service:
kubectl get svc nginx
指定ClusterIP的Service
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
clusterIP: 10.254.0.100
ports:
- port: 80
targetPort: 80
selector:
app: nginx
默認情況下,ClusterIP的值是由k8s自動創建的,我們可以通過ClusterIP指定,在創建k8s中的dns的時候會用到。
headless service
創建一個headless service,即指定ClusterIP為None,這個時候,創建的Service沒有IP地址。
我們知道,在默認情況下創建的service,k8s會自動為其生成一個ip地址,並在dns中生成一條域名記錄指向該ip,當外部有請求到達時,由kubeproxy組件接受請求並轉發到后端的pods。而當ClusterIP為None時,k8s並不會為service生成一個IP,但是仍然會往dns里生成一條域名記錄,而這個域名的值會直接指向service所關聯的pods的IP地址,有多個pods,就會生成多條A記錄。這樣的好處是,當有請求到達時,會直接請求到指定的pods,而無需再通過kubeproxy轉發,從而提高了響應效率。缺點是負載均衡依賴於dns輪循,沒有更靈活的均衡方案。
示例:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
clusterIP: None
ports:
- port: 80
targetPort: 80
selector:
app: nginx
創建NodePort類型的service
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 80
selector:
app: nginx
創建ExternalName類型的service
下面直接給個示例:
kind: Service
apiVersion: v1
metadata:
name: database
spec:
type: ExternalName
externalName: database.example.com
創建一個service名為database,指向一個外部的服務,這個服務的域名為database.example.com
。我們在集群內部訪問database.default.svc
可直接跳轉至database.example.com
。實際就是個dns別名。
擴展service
sessionAffinity
主要是用於基於userspace(當前基本已廢棄)和iptables轉發模式下的pod調度算法,默認為none,此時的調度算法為輪詢, 可通過將設置為ClientIP以實現基於客戶端ip的親和性調度,但也會導致負載不均。
apiVersion: v1
kind: Service
metadata:
name: nginx-app
labels:
app: nginx-app
tier: nginx-app
spec:
ports:
- port: 80
targetPort: 80
selector:
app: nginx-app
tier: nginx-app
type: LoadBalancer
sessionAffinity: ClientIP
externalTrafficPolicy
默認情況下,目標容器看到的源Ip不是客戶端的源ip,如果要保留客戶端的源ip,可以配置externalTrafficPolicy選項如下:
- service.spec.externalTrafficPolicy - 如果這個服務需要將外部流量路由到 本地節點或者集群級別的端點,那么需要指明該參數。存在兩種選項:Cluster(默認)和 Local。 Cluster隱藏源 IP 地址,可能會導致第二跳(second hop)到其他節點,但是全局負載效果較好。Local保留客戶端源 IP 地址,避免 LoadBalancer 和 NodePort 類型服務的第二跳,但是可能會導致負載不平衡。
- service.spec.healthCheckNodePort - 定義服務的 healthCheckNodePort (數字端口號)。 如果沒有聲明,服務 API 后端會用分配的 nodePort 創建 healthCheckNodePort。如果客戶端 指定了 nodePort,則會使用用戶自定義值。這只有當類型被設置成 LoadBalancer並且 externalTrafficPolicy被設置成 Local時,才會生效。
ExternalIP
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
當從集群外部請求80.11.12.10這個Ip時,如果集群收到該請求,就會將流量轉發至my-service這個service。但這里有個前提, 即80.11.12.10這個Ip的流量能正確的路由到這個service上來,而這部分路由的功能kubernetes並不保證,需要集群管理人員自行做路由處理。
一般來講,externalIPs通常會與loadbalancer類型的service配合使用。