在Kubernetes平臺上,Pod是有生命週期,為了可以給客戶端一個固定的訪問端點,因此需要在客戶端和Pod之間添加一箇中間層,這個中間層稱之為Service
Service是什麼?
在Kubernetes中,每個節點都安裝了kube-proxy,kube-proxy通過kubernetes中固有的watch請求方法持續監聽apiserver。一旦有service資源發生變動(增刪改查)kube-proxy可以及時轉化為能夠調度到後端Pod節點上的規則,這個規則可以是iptables也可以是ipvs,取決於service實現方式
Kubernetes 三大IP
- Node Network 節點網絡 節點網絡地址是配置在節點網絡之上
- Pod Network Pod網絡 Pod網絡地址是配置在Pod網絡之上節點網絡和Pod網絡都是配置在某個設備之上,可以是硬件也可以是虛擬網絡
- Cluster Network(svc network) virtual IP svc ip沒有配置在某個網絡接口上,它只是存在service的規則當中
Service 工作原理
k8s在創建Service時,會根據標籤選擇器selector(lable selector)來查找Pod,據此創建與Service同名的endpoint對象,當Pod 地址發生變化時,endpoint也會隨之發生變化,service接收前端client請求的時候,就會通過endpoint,找到轉發到那個Pod進行訪問的地址。(至於轉發到哪個節點的Pod,由負載均衡kube-proxy期初就決定好的)
<code>#查看endpoint可以通過下面的命令
[root@k8s-01 ~]# kubectl get ep
NAME ENDPOINTS AGE
fuseim.pri-ifs <none> 31d
kubernetes 192.168.0.10:6443,192.168.0.11:6443,192.168.0.12:6443 33d
myapp 172.30.168.6:80,172.30.192.7:80,172.30.216.7:80 7m56s
myapp-svc 172.30.168.4:80,172.30.192.5:80,172.30.216.6:80 18m/<none>/<code>
1.endpoint是k8s集群中的一個資源對象,存儲在etcd中,用來記錄一個service對應的所有pod的訪問地址2.只有當service配置selector(選擇器),endpoint controller才會自動創建對應的endpoint對象,否則,不會生成endpoint對象3.在k8s集群中創建webapp的service,就會生成一個同名的endpoint對象,endpoint就是service關聯的Pod的ip地址和端口
Service 工作模式
userpace在這種模式下,kube-proxy監視Kubernetes主服務器以添加和刪除Service和Endpoint對象。對於每個服務,它都會在本地節點上打開一個端口(隨機選擇)。與此代理端口的任何連接都代理到服務的後端Pod。SessionAffinity在決定使用哪個後端Pod時,kube-proxy會考慮服務的設置。最後,用戶控件代理安裝iptables規則,以獲取服務clusterIP和流量port。規則將流量重定向到代理後端Pod的端口。
簡單點來說Service的請求先從用戶空間進入內核iptables轉發到這個端口,然後再回到用戶空間,由kube-proxy完成後端endpoint的選擇和代理,這樣流量會有從用戶空間進入內核的過程,效率低,有明顯的性能瓶頸
image_1e2i15977imggfddjb1dgsfbem.png-155.2kB
iptables
目前默認的方案,完全以內核iptables的nat方式實現service負載均衡。該方式在大規模情況下存在一些性能問題;首先,iptables沒有增量更新的功能,更新一條規則需要整體flush,更新時間長,這段時間之內流量會有不同程度的影響;此外,iptables規則串行匹配,沒有預料到Kubernetes這種在一個機器上會有很多規則的情況,流量需要經過所有規則的匹配後在進行轉發,對時間和內存都是極大的小號,尤其在大規模情況下對性能的影響十分明顯
image_1e2i1b3cf1fja1kkrmravbtcms13.png-148.8kB
ipvs
與iptables、userspace 模式一樣,kube-proxy 依然監聽Service以及Endpoints對象的變化, 不過它並不創建反向代理, 也不創建大量的 iptables 規則, 而是通過netlink 創建ipvs規則,並使用k8s Service與Endpoints信息,對所在節點的ipvs規則進行定期同步; netlink 與 iptables 底層都是基於 netfilter 鉤子,但是 netlink 由於採用了 hash table 而且直接工作在內核態,在性能上比 iptables 更優
同時,ipvs負載均衡除了簡單rr規則還有很多選擇,適合在大型集群中使用,而缺點是帶來了額外的配置維護操作
image_1e2i1j2661kavs7d13bg119g1hvm1g.png-113.3kB
簡單說一下kube-proxy和service之間的關係
訪問Service的請求,不論是Cluster IP還是TargerPort方式;還是NodePort方式,都被Node節點的Iptables規則重定向到kube-proxy監聽Service服務代理端口。kube-proxy接受Service的訪問請求後,根據負載策略轉發到後端的Pod
<code>-> kube-proxy其實就是管理service的訪問入口,包括集群內Pod到Service的訪問和集群外訪問service。
-> kube-proxy管理sevice的Endpoints,該service對外暴露一個Virtual IP,也成為Cluster IP, 集群內通過訪問這個Cluster IP:Port就能訪問到集群內對應的serivce下的Pod。
-> service是通過Selector選擇的一組Pods的服務抽象,其實就是一個微服務,提供了服務的LB和反向代理的能力,而kube-proxy的主要作用就是負責service的實現。
-> service另外一個重要作用是,一個服務後端的Pods可能會隨著生存滅亡而發生IP的改變,service的出現,給服務提供了一個固定的IP,而無視後端Endpoint的變化。/<code>
kube-proxy負責為Service提供cluster內部的服務發現和負載均衡,它運行在每個Node計算節點上,負責Pod網絡代理, 它會定時從etcd服務獲取到service信息來做相應的策略,維護網絡規則和四層負載均衡工作。在K8s集群中微服務的負載均衡是由Kube-proxy實現的,它是K8s集群內部的負載均衡器,也是一個分佈式代理服務器,在K8s的每個節點上都有一個,這一設計體現了它的伸縮性優勢,需要訪問服務的節點越多,提供負載均衡能力的Kube-proxy就越多,高可用節點也隨之增多。
Service 服務發現
https://i4t.com/4478.html
Service 集群服務暴露
服務發現解決了集群內部訪問Pod問題,但很多時候,Pod提供的服務也要對集群外部來暴露訪問。
ExternalName
簡單的來說ExternalName代理外部的請求,讓Pod訪問svc實際上訪問的就是集群外部的服務,讓Pod訪問集群外部的服務像集群內部服務一樣方便
<code>apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: default
spec:
type: ExternalName
externalName: my.database.example.com/<code>
這裡externalName實際上就是cname的域名,前提需要節點可以訪問到,或者映射到公網上
集群內部可以通過my-service.default.svc.cluster.local訪問,請求會被cname映射到my.database.example.com
ClusterIP
clusterIP又可以分為普通Service和Headless Service兩類
- 普通Service 通過Kubernetes的Service分配一個集群內部可訪問的固定虛擬IP(Cluster IP),也可以自己指定clusterIP,從而實現集群內部的訪問
- Headless Service 無頭服務 該服務不會分配ClusterIP,也不會通過kube-proxy做反向代理和負載均衡。而是通過DNS提供穩定的網絡ID來訪問,DNS會將headless service的後端直接解析為PodIP列表,主要提供StatefulSet使用
普通Cluster IP演示
這裡我們創建一個svc和deployment,具體參數不在解釋,如有不懂可以參考 Kubernetes Service
<code>apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: myapp-svc
spec:
replicas: 3
selector:
matchLabels:
app: myapp-svc
template:
metadata:
labels:
app: myapp-svc
spec:
containers:
- name: myapp-svc
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
namespace: default
spec:
selector:
app: myapp-svc
type: ClusterIP
ports:
- port: 80
targetPort: 80/<code>
創建完畢後,service會隨機代理pod。 普通service也是比較常見的svc
<code>[root@k8s-01 ~]# kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/myapp-svc-5f8fc8545f-6svln 1/1 Running 0 79s
pod/myapp-svc-5f8fc8545f-kwc7x 1/1 Running 0 79s
pod/myapp-svc-5f8fc8545f-rp8vg 1/1 Running 0 79s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 33d
service/myapp-svc ClusterIP 10.254.223.73 <none> 80/TCP 81s
[root@k8s-01 ~]#
[root@k8s-01 ~]# curl 10.254.223.73/hostname.html
myapp-svc-5f8fc8545f-kwc7x/<none>/<none>/<code>
Headkess Service演示
如果我們上面創建了clusterIP模式,需要修改svc模式,需要將之前svc刪除從新創建,如果使用apply是無法刷新svc配置的
<code>[root@k8s-01 ~]# cat k8s-svc.yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: myapp-svc
spec:
replicas: 3
selector:
matchLabels:
app: myapp-svc
template:
metadata:
labels:
app: myapp-svc
spec:
containers:
- name: myapp-svc
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
namespace: default
spec:
clusterIP: "None"
selector:
app: myapp-svc
ports:
- port: 80
targetPort: 80/<code>
在無頭模式中,clusterip為none,因為不需要clusterip所以這裡是沒有cluster ip
<code>[root@k8s-01 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 33d
myapp-svc ClusterIP None <none> 80/TCP 4m33s/<none>/<none>/<code>
這裡通過dig命令進行查看
參數需要說明,myapp-svc為svc的名稱,default為命名空間, 後面@10.254.0.2我這裡使用的是coredns,同樣也是coredns svc ip
<code>## 查看svc及Pod名稱,一會需要對比
[root@k8s-01 ~]# kubectl get pod,svc -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/myapp-svc-5f8fc8545f-26c8x 1/1 Running 0 7m22s 172.30.168.4 k8s-03 <none> <none>
pod/myapp-svc-5f8fc8545f-hd7pp 1/1 Running 0 7m22s 172.30.216.6 k8s-01 <none> <none>
pod/myapp-svc-5f8fc8545f-kd6xl 1/1 Running 0 7m22s 172.30.192.5 k8s-02 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 33d <none>
service/myapp-svc ClusterIP None <none> 80/TCP 7m22s app=myapp-svc
## 查看dns svc ip
[root@k8s-01 ~]# kubectl get svc -n kube-system|grep dns
kube-dns ClusterIP 10.254.0.2 <none> 53/UDP,53/TCP,9153/TCP 33d
##查看dns解析是否為3個pod,以及對應的IP
[root@k8s-01 ~]# dig -t A myapp-svc.default.svc.cluster.local. @10.254.0.2
...
;; ANSWER SECTION:
myapp-svc.default.svc.cluster.local. 5 IN A 172.30.168.4
myapp-svc.default.svc.cluster.local. 5 IN A 172.30.216.6
myapp-svc.default.svc.cluster.local. 5 IN A 172.30.192.5
.../<none>/<none>/<none>/<none>/<none>/<none>/<none>/<none>/<none>/<none>/<code>
如果我們使用普通service,這裡dig只會出現一個pod名稱
NodePort
除了cluster ip之外,還可以通過service的port映射到集群內每個節點的相同端口,實現通過nodeIP:nodePort 從集群外訪問服務
在NodePort中,client請求先到達NodeIP+NodePort-->ClusterIP:ServicePort-->PodIP:ContainerPort
這裡我們繼續創建webapp
<code>apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: myapp
labels:
addonmanager.kubernetes.io/mode: Reconcile
spec:
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: my-nginx
image: ikubernetes/myapp:v1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
selector:
app: myapp
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080/<code>
我們映射nodeport端口30080,type類型為Nodeport,也是我們之前常用的
<code>[root@k8s-01 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 33d
myapp NodePort 10.254.233.67 <none> 80:30080/TCP 33s/<none>/<none>/<code>
接下來我們就可以在任意節點訪問Pod
<code>[root@k8s-01 ~]# while true;do curl http://192.168.0.10:30080/hostname.html; sleep 1; done
myapp-xr9m2
myapp-gbcvh
myapp-6gwh9
myapp-xr9m2
myapp-gbcvh/<code>
LoadBalancer
和nodeport類似,不過除了使用一個Clusterip和nodeport之外,還會向所使用的公有云申請一個負載均衡器,實現從集群外通過LB訪問服務
更多參數可以參考官方文檔https://kubernetes.io/docs/concepts/services-networking/service/#externalname
Service Session會話保持
session affinity會話保持,又稱為會話親和性,常用於Web Service的微服務部署中。就和我們nginx中的會話保持一樣
可以將來自同一個客戶端的請求始終轉發至同一個後端的Pod對象,這意味著它會影響調度算法的流量分發功用,進而降低其負載均衡的效果。因此,當客戶端訪問Pod中的應用程序時,如果有基於客戶端身份保存某些私有信息,並基於這些私有信息追蹤用戶的活動等一類的需求時,那麼應該啟用session affinity機制。
Service affinity的效果僅僅在一段時間內生效,默認值為10800秒,超出時長,客戶端再次訪問會重新調度。該機制僅能基於客戶端IP地址識別客戶端身份,它會將經由同一個NAT服務器進行原地址轉換的所有客戶端識別為同一個客戶端,由此可知,其調度的效果並不理想。Service 資源 通過. spec. sessionAffinity 和. spec. sessionAffinityConfig 兩個字段配置粘性會話。 spec. sessionAffinity 字段用於定義要使用的粘性會話的類型,它僅支持使用“ None” 和“ ClientIP” 兩種屬性值。如下:
<code>[root@k8s-01 ~]# kubectl explain svc.spec.sessionAffinity
KIND: Service
VERSION: v1
FIELD: sessionAffinity <string>
DESCRIPTION:
Supports "ClientIP" and "None". Used to maintain session affinity. Enable
client IP based session affinity. Must be ClientIP or None. Defaults to
None. More info:
https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
[root@k8s-01 ~]#/<string>/<code>
sessionAffinity支持ClientIP和None 兩種方式,默認是None(隨機調度) ClientIP是來自於同一個客戶端的請求調度到同一個pod中
<code>[root@k8s-01 ~]# cat webapp.yml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: myapp
labels:
addonmanager.kubernetes.io/mode: Reconcile
spec:
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: my-nginx
image: ikubernetes/myapp:v1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
selector:
app: myapp
type: NodePort
sessionAffinity: ClientIP ##參數位置
ports:
- port: 80
targetPort: 80
nodePort: 30080/<code>
此時我們通過curl請求svc時,只會返回一個節點的pod
<code>[root@k8s-01 ~]# while true;do curl http://192.168.0.10:30080/hostname.html;sleep 1;done
myapp-cg4lq
myapp-cg4lq
myapp-cg4lq
myapp-cg4lq
myapp-cg4lq/<code>
也可以使用打補丁的方式進行修改yaml內的內容,如下:
<code>kubectl patch svc myapp -p '{"spec":{"sessionAffinity":"ClusterIP"}}' #session保持,同一ip訪問同一個pod
kubectl patch svc myapp -p '{"spec":{"sessionAffinity":"None"}}' #取消session /<code>
如果我們取消session配置,那麼請求svc時會隨機調度給pod相當於sessionAffinity=None
<code>[root@k8s-01 ~]# while true;do curl http://192.168.0.10:30080/hostname.html;sleep 1;done
myapp-pplfn
myapp-wkfqz
myapp-cg4lq
myapp-pplfn
myapp-wkfqz/<code>
說明一點Service屬於四層調度,無論是iptables還是ipvs都是無法實現https請求或者7層的操作
原文:https://i4t.com/4567.html
閱讀更多 DevOps運維 的文章