Service資源概述
Service是Kubernetes的核心資源類型之一,它通過規則定義出由多個Pod對象組合而成的邏輯集合,以及訪問這組Pod的策略。 由Deployment等控制器管理的Pod對象在中斷或擴縮容後,Pod組合的IP地址都會發生變化,而引入Service資源後,就可以基於標籤選擇器將一組Pod定義成一個邏輯組合,並通過自己的IP地址和端口調度代理請求至組內的Pod對象之上,它向客戶端隱藏了真實的、處理用戶請求的Pod資源,使得客戶端的請求看上去就像是由Service直接處理並進行響應的一樣。而且Service與Pod對象之間通過標籤選擇器以松耦合的方式關聯,它可以先於Pod對象創建而不會發生錯誤。
創建Service資源
Service資源基本的配置清單:
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
Service資源myapp-svc通過標籤選擇器關聯至標籤爲“app=myapp”的各Pod對象,它會自動創建名爲myapp-svc的Endpoints資源對象,並自動配置一個ClusterIP,暴露的端口由port字段進行指定,後端各Pod對象的端口則由targetPort給出。 分別查看service和endpoint的狀態:
kubectl get svc myapp-svc
kubectl get endpoints myapp-svc
向Service對象請求服務
Service資源的默認類型爲ClusterIP,它僅能接收來自於集羣中的Pod對象中的客戶端程序的訪問請求。所以爲了測試,會創建一個專用的Pod對象,利用其交互式接口來訪問service資源。
kubectl run cirros-$RANDOM --rm -it --image=cirros -- sh
啓動了一個運行CirrOS容器的Pod,CirrOS是設計用來進行雲計算環境測試的Linux微型發行版,它自帶HTTP客戶端工具curl等。 在容器的交互式接口訪問ClusterIP:Port
/ # curl http://10.105.246.145:80
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
Kubernetes集羣默認的Service代理模式爲iptables,而且使用隨機調度算法,因此Service會將客戶端請求隨機調度至與其關聯的某個後端Pod資源上。
Service會話粘性
Service資源還支持Session affinity(會話粘性)機制,它能夠將來自同一個客戶端的請求始終轉發至同一個後端的Pod對象,適用於需要基於客戶端身份保存某些私有信息,並根據這些私有信息追蹤用戶的活動等一類的需求。 Service資源通過spec.sessionAffinity和spec.sessionAffinityConfig兩個字段配置粘性會話:
- sessionAffinity字段用於定義要使用的粘性會話的類型,它僅支持使用“None”和“ClientIP”兩種屬性值。
- None:不使用sessionAffinity,默認值。
- ClientIP:基於客戶端IP地址識別客戶端身份,把來自同一個源IP地址的請求始終調度至同一個Pod對象。
- sessionAffinityConfig用於配置其會話保持的時長,其可用的時長範圍爲“1~86400”,默認爲10800秒
但是Service資源的Session affinity機制僅能基於客戶端IP地址識別客戶端身份,它會把經由同一個NAT服務器進行源地址轉換的所有客戶端識別爲同一個客戶端,調度粒度粗糙且效果不佳,因此實踐中並不推薦使用此種方法實現粘性會話。
服務發現
Service爲Pod中的服務類應用提供了一個穩定的訪問入口,但Pod客戶端中的應用如何得知某個特定Service資源的IP和端口呢,這就需要藉助服務發現機制來進行。 服務發現機制的基本實現,一般是事先部署好一個網絡位置較爲穩定的服務註冊中心(也稱爲服務總線),服務提供者(服務端)向註冊中心註冊自己的位置信息,並在變動後及時予以更新,服務消費者則週期性地從註冊中心獲取服務提供者的最新位置信息從而“發現”要訪問的目標服務資源。 在K8S中可以基於CoreDNS進行服務發現,甚至也可以使用簡單的環境變量方式。
服務暴露
Service的IP地址默認僅在集羣內可達,集羣外部想訪問服務,需要首先進行服務的暴露。
Service的類型
Service有四種類似ClusterIP、NodePort、LoadBalancer和ExternalName
- ClusterIP:通過集羣內部IP地址暴露服務,此地址僅在集羣內部可達,而無法被集羣外部的客戶端訪問;
- NodePort:建立在ClusterIP類型之上,其在每個Node的IP地址的某靜態端口(NodePort)暴露服務,NodePort的路由目標爲ClusterIP,簡單來說,NodePort類型就是在工作節點的IP地址上選擇一個端口用於將集羣外部的用戶請求轉發至目標Service的ClusterIP和Port,這種類型的Service既可如ClusterIP一樣受到集羣內部客戶端Pod的訪問,也會受到集羣外部客戶端通過套接字NodeIP:NodePort進行的請求;
- LoadBalancer:建構在NodePort類型之上,其通過cloud provider提供的負載均衡器將服務暴露到集羣外部,LoadBalancer類型的Service會指向關聯至Kubernetes集羣外部的某個負載均衡設備,該設備通過工作節點之上的NodePort向集羣內部發送請求流量,這種Service的優勢在於,能夠把來自於集羣外部客戶端的請求調度至所有節點(或部分節點)的NodePort之上,而不是依賴於客戶端自行決定連接至哪個節點,從而避免了因客戶端指定的節點故障而導致的服務不可用;
- ExternalName:通過將Service映射至由externalName字段的內容指定的主機名來暴露服務,此主機名需要被DNS服務解析至CNAME類型的記錄。這種類型並非定義由Kubernetes集羣提供的服務,而是把集羣外部的某服務以DNS CNAME記錄的方式映射到集羣內,從而讓集羣內的Pod資源能夠訪問外部的Service的一種實現方式,這種類型的Service沒有ClusterIP和NodePort,也沒有標籤選擇器用於選擇Pod資源。
NodePort
NodePort型的Service資源,其配置清單與前面默認的ClusterIP類型類似,只是要顯式地在spec下指定type: NodePort
此外還可以指定Node端口,但必須在30000-32767之間,而且推薦使用集羣自動分配的端口以避免端口的衝突。
LoadBalancer
NodePort類型的Service資源雖然能夠於集羣外部訪問得到,但外部客戶端必須得事先得知NodePort和集羣中至少一個節點的IP地址,且選定的節點發生故障時,客戶端還得自行選擇請求訪問其他的節點,此外有時節點並不允許外界訪問,LoadBalancer類型可將請求流量調度至各節點的NodePort之上。 創建LoadBalancer類型需要指定type: LoadBalancer
,
ExternalName
ExternalName類型的Service資源用於將集羣外部的服務發佈到集羣中以供Pod中的應用程序訪問。需要指定type: ExternalName
和externalName,比如externalName: redis.ilinux.io
,externalName屬性定義了一個CNAME記錄用於返回外部真正提供服務的主機的別名,而後通過CNAME記錄值獲取到相關主機的IP地址。
spec:
type: ExternalName
externalName: redis.ilinux.io
服務創建後通過serviceName訪問相應的服務,ClusterDNS會將此名稱以CNAME格式解析爲spec.externalName字段中的名稱,而後通過DNS服務將其解析爲相應的主機的IP地址。
Ingress和Ingress Controller
在Kubernetes中,Service資源和Pod資源的IP地址僅能用於集羣網絡內部的通信,所有的網絡流量都無法穿透邊界路由器以實現集羣內外通信。Kubernetes提供了兩種內建的雲端負載均衡機制(cloud loadbalancing)用於發佈公共應用:
- 一種是工作於傳輸層的Service資源,它實現的是“TCP負載均衡器”,無論是iptables還是ipvs模型的Service資源都配置於Linux內核中的Netfilter之上進行四層調度,是一種類型更爲通用的調度器,支持調度HTTP、MySQL等應用層服務。 但由於工作於傳輸層而功能有侷限,比如不支持基於URL的請求調度機制,而且Kubernetes也不支持爲其配置任何類型的健康檢查。
- 另一種便是Ingress資源,它實現的是“HTTP(S)負載均衡器”,屬於應用層負載均衡機制的一種,它提供了諸如可自定義URL映射和TLS卸載等功能,並支持多種類型的後端服務器健康狀態檢查機制。
Ingress是Kubernetes API的標準資源類型之一,它其實就是一組基於DNS名稱(host)或URL路徑把請求轉發至指定的Service資源的規則,用於將集羣外部的請求流量轉發至集羣內部完成服務發佈,它僅是一組路由規則的集合,Ingress控制器根據這些規則來匹配並路由請求流量。
Ingress資源
Ingress資源是基於HTTP虛擬主機或URL的轉發規則。Ingress資源的定義方式舉例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: www.ilinux.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-svc
port:
number: 80
spec字段下主要嵌套如下三個字段
- rules,用於定義當前Ingress資源的轉發規則列表;未由rules定義規則,或者沒有匹配到任何規則時,所有流量都會轉發到由backend定義的默認後端。
- backend,默認的後端用於服務那些沒有匹配到任何規則的請求;定義Ingress資源時,至少應該定義backend或rules兩者之一;此字段用於讓負載均衡器指定一個全局默認的後端。backend對象的定義由兩個必選的內嵌字段組成:serviceName和servicePort,分別用於指定流量轉發的後端目標Service資源的名稱和端口。
- tls,TLS配置,目前僅支持通過默認端口443提供服務;如果要配置指定的列表成員指向了不同的主機,則必須通過SNI TLS擴展機制來支持此功能。
Ingress資源類型
單Service資源型Ingress
暴露單個Service的方法可以使用NodePort、LoadBalancer,也可以使用Ingress來暴露服務,只需要爲Ingress指定“default backend”,這樣Ingress控制器會爲其分配一個IP地址接入請求流量,並將它們轉至指定的後段service,如:
spec:
backend:
serviceName: my-svc
servicePort: 80
基於URL路徑進行流量分發
Ingress也支持基於URL路徑進行流量分發,如:
spec:
rules:
- host: www.ilinux.io
http:
paths:
- path: /web
backend:
service:
name: myapp-svc-web
port:
number: 80
- path: /api
backend:
service:
name: myapp-svc-api
port:
number: 80
上面定義的規則會將對www.ilinux.io/web的請求轉發到前端service myapp-svc-web,而將www.ilinux.io/api請求轉發到後段service myapp-svc-api。
基於主機名稱的虛擬主機
如果服務按照域名進行劃分,可以將Ingress基於虛擬主機定義如下:
spec:
rules:
- host: web.ilinux.io
...
- host: api.ilinux.io
TLS類型的Ingress資源
如果需要以HTTPS發佈Service資源,也可以配置TLS協議的Ingress資源,但需要基於一個含有私鑰和證書的Secret對象,這在後面的章節會涉及到,在Ingress資源中引用此Secret即可讓Ingress控制器加載並配置爲HTTPS服務:
spec:
tls:
- secretName: ilinuxSecret
backend:
...
Ingress控制器
Ingress控制器自身是運行於Pod中的容器應用,一般是Nginx或Envoy一類的具有代理及負載均衡功能的守護進程,它監視着來自於API Server的Ingress對象狀態,並以其規則生成相應的應用程序專有格式的配置文件並通過重載或重啓守護進程而使新配置生效。例如,對於Nginx來說,Ingress規則需要轉換爲Nginx的配置信息。 既然Ingress控制器實際上也是Pod資源,那麼也需要接入外部流量,這可以使用NodePort或LoadBalancer類型的Service對象爲其接入集羣外部的請求流量;或者藉助於DaemonSet控制器,將Ingress控制器的Pod資源各自以單一實例的方式運行於集羣的所有或部分工作節點之上,並配置這類Pod對象以hostPort或hostNetwork的方式在當前節點接入外部流量。 以部署ingress-nginx並通過專用的Service接入流量爲例: 首先apply在線的mandatory.yaml來部署ingress-nginx所需的全部資源,它們的namespace:爲ingress-nginx:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.24.1/deploy/mandatory.yaml
然後定義專用的NodePort型Service資源,注意其selector要與nginx-ingress-controller的selector保持一致:
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
type: NodePort
selector:
app.kubernetes.io/name: ingress-nginx
ports:
- port: 80
name: http
nodePort: 30080
- port: 443
name: https
nodePort: 30443
然後就可以通過這個NodePort型Serviced的IP:30002訪問了。或者也可以根據ingress指定的host名稱配置etc/hosts,比如對於如下的ingress配置:
spec:
rules:
- host: tomcat.ilinux.io
sudo vim /etc/hosts
,添加一行
127.0.0.1 tomcat.ilinux.io
然後也就可以通過tomcat.ilinux.io:30002訪問了。
學習資料
《Kubernetes實戰進階》 馬永亮著