一、K8S雲原生服務集群問題
(一)負載均衡原理
在之前的文章說過,每一個Pod都是獨立的IP、HostName、存儲,同時Pod是隨時可以被動態創建和回收的,那么就有個問題,我們如何知道Pod的IP並進行訪問的呢?其實K8S是使用Service VIP技術的虛擬ip + kube-proxy來解決這個問題,其中service VIP用來轉發請求,kube-proxy用來監控pod狀態,並且會及時修改pod的ip。
service是K8S的資源對象,service資源對象運行在每一個node節點上,每一個node節點都有一個service進程,service有自己的IP地址(虛擬IP),而service VIP相當於一個網關,所有的請求都要經過service VIP,通過service VIP進行轉發,從而實現負載均衡。
service VIP一旦被創建,是不會被修改的,除非刪除service后重新創建service;同時由於service信息存儲在高可用的etcd中,且service實例運行在多個node節點上,因此Service VIP不存在單點故障的問題;由於service VIP中使用的是虛擬IP,因此Service VIP只能在局域網內部進行訪問,不能通過外網進行訪問,如果想要進行外網訪問,則需要借助物理網卡進行端口映射轉發。
在K8S中IP資源的分類如下:
Node IP:Node物理節點IP
Pod IP:物理機內部運行的一個虛擬容器pod的ip
cluster IP:集群IP,也是個虛擬IP,是K8S抽象出來的一個service的IP。此IP只能局域網內部訪問,不能通過外網訪問,如果要使用外網訪問,就必須開辟nodeport類型的IP地址。如下圖所示,外網訪問物理IP,然后將訪問請求映射到service VIP上,service VIP從etcd上獲取endpoints中pod的IP,然后使用負載均衡策略選擇一個pod進行調用
(二)Pod服務發現
Pod服務發現借助kube-proxy實現,該組件實現了三件事情:監控pod;pod如果發生了變化,及時修改映射關系;修改映射關系的同時,修改路由規則,以便在負載均衡時可以選擇到新的pod。
二、負載均衡方案(四層負載)
K8S的負載均衡方案有三種:kube-proxy(userspace)、iptables(防火牆)、ipvs。
1、kube-proxy
使用kube-proxy的負載方案是使用kube-proxy來監控pod的狀態,如果pod發生了變化,則需要kube-proxy去修改service和pod的映射關系(endpoints),同時修改路由規則,並且由kube-proxy轉發請求,這種方式kube-proxy的壓力比較大,性能可能會出現問題。
2、IPtables
IPtables是K8S默認采用的負載策略,這種方式中,kube-proxy同樣用來監控pod和修改映射關系以及修改路由規則,但是轉發請求是采用輪詢iptables路由規則的方式進行調用處理的。
這種模式kube-proxy主要做好watching Cluster API即可,路由和請求的轉發都交給了iptables,但是kube-proxy在請求無響應時會換一個pod進行重試,而iptables則是一條條的路由規則,不會進行重試。
在iptables中,默認的輪詢策略是隨機的輪詢策略,但是也可以將其設置為輪詢。
(1)設置為隨機策略
# 隨機:(Random balancing) iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode random --probability 0.33 -j DNAT --to-destination 10.0.0.2:1234 iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode random --probability 0.5 -j DNAT --to-destination 10.0.0.3:1234 iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -j DNAT --to-destination 10.0.0.4:1234
在iptables命令中,命令的執行和順序有關,在上述命令中,用--probability 設置了命中幾率,第一條設置了紀律是0.33,也就是整個請求的0.33,第二條命中率為0.5,實際是剩余請求的0.5,也就是總請求的0.335,第三條是剩余的流量全部打到該ip上,也就是0.335,基本上就是隨機分配了。
(2)設置為輪詢策略
#every:每n個包匹配一次規則 #packet:從第p個包開始 iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 10.0.0.2:1234 iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode nth --every 2 --packet 0 -j DNAT --to-destination 10.0.0.3:1234 iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -j DNAT --to-destination 10.0.0.4:1234
3、IPVS
ipvs (IP Virtual Server) 實現傳輸層負載均衡,通常稱為第四層LAN交換,是Linux內核的一部分。
ipvs與iptables的區別:
ipvs為大型集群提供了更好的可擴展性和性能
ipvs支持比iptables更復雜的負載均衡算法
ipvs支持服務器的健康檢查和連接重試等。
對於上述差異做個說明:
在linux中iptables設計是用於防火牆的,對於比較少的規則,沒有太多的性能影響,如果對於一個龐大的K8S集群,會有上千Service服務,Service服務會對應多個pod,每條都是一個iptables規則,那么對於集群來說,每個node上都有大量的規則,簡直是噩夢。而IPVS則是使用hash tables來存儲網絡轉發規則的,比iptables更有優勢,而且ipvs主要工作在kerbespace,減少了上下文的切換。
IPVS有輪詢(rr)、最小連接數(lc)、目的地址hash(dh)、源地址hash(sh)、最短期望延遲(sed)、無須隊列等待(nq)等負載均衡算法,在node上通過 “–ipvs-scheduler”參數,指定kube-proxy的啟動算法。
kube-proxy和IPVS合作的流程:
(1)kube-proxy仍然是watching Cluster API,獲取新建、刪除Service或Endpoint pod指令,如果有新的Service建立,kube-proxy回調網絡接口,構建IPVS規則。
(2)kube-proxy會定期同步Service和Pod的轉發規則,確保失效的轉發可以被及時修復
(3)有請求轉發到后端的集群時,IPVS的負載均衡直接將其轉發到對應的Pod上
三、Ingres-nginx(七層負載均衡)
(一)為什么要使用Ingres
1、先介紹以下部署K8S服務時使用到的配置文件:
創建配置文件(ingres.yaml),用來部署一個nginx的deployment和service
# 創建一個service資源對象
# kubectl expose deployment xxName –port=80 –target-port=80 k8s指令模式創建一個service # k8s部署yaml方式 apiVersion: v1 kind: Service metadata: name: nginx namespace: default spec: selector: app: nginx ports: - port: 80 targetPort: 80 --- # 部署deployment對象,k8s創建3個資源對象:Deployment,ReplicaSet,POD apiVersion: apps/v1 kind: Deployment metadata: name: nginx namespace: default spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.16 ports: - containerPort: 80
對上面的配置文件做個描述:
首先看Deployment:
apiVersion標記版本,Kind標記類型,說明要創建一個Deployment,該deployment的名字叫nginx,空間為默認空間。
spec下是rs和pod的配置,首先replicas是副本的數量,然后就是選擇器的名稱為nginx。
template下是副本的配置,首先副本的標簽是nginx,容器的名字是nginx,鏡像為nginx1.16,這里可以配置為鏡像倉庫地址和對應的鏡像,最后ports是容器開放的端口。
這里說明幾個比較重要的配置,就是選擇器的名稱和容器的標簽一定要一致(綠色連接線),否則容器不會被副本控制器RS所控制。
然后說一下Service:
apiVersion和Kind都和Deployment一樣,Kind的值表明了這是一個Service配置。
然后就是這個service的名字,service標簽選擇器的名字,以及目標容器的端口。
這里說幾個比較重要的配置,和Deployment一樣,選擇器的名字要和容器的名字一致(綠色連接線),目標容器端口要和容器的端口保持一致(紫色連接線)
執行配置文件,生成deployment和service
kubectl apply -f ingres.yaml
執行后查看deployment、service、rs、pod的情況(注意:這一步一般時間比較久,需要等一會)
從上面圖片的輸出中可以看到,nginx的type為ClusterIp,這種類型的service只能在集群內部訪問,而不能在外部直接訪問,那么就需要修改類型為NodePort,修改命令:
kubectl edit svc nginx
更改完畢之后,重新查看service,發現type已經變為NodePort,且port也變更了,前面的80是容器內的端口,后面的31758是對外開放的端口,也就是宿主機的端口。
此時,使用宿主機的ip + 31758訪問,即可訪問nginx
但是有個問題,就是如果每個服務都要對外開啟一個端口,那么就需要開啟很多的端口,這樣即麻煩又有點浪費,因此就需要Ingres來解決這個問題,Ingres只需要一個NodePort就可以解決上述的問題。因為ingress相當於一個7層的負載均衡器,是k8s對反向代理的一個抽象。大概的工作原理也確實類似於Nginx,可以理解成在 Ingress 里建立一個個映射規則 , ingress Controller 通過監聽 Ingress這個api對象里的配置規則並轉化成 Nginx 的配置(kubernetes聲明式API和控制循環) , 然后對外部提供服務。
(二)Ingres-Nginx介紹
Ingres是K8S對於nginx進行雲原生模式的封裝,使得nginx更適合雲原生的結構,使用Ingres可以對Service進行負載均衡,因此Ingres工作在七層,屬於七層負載均衡。
Ingres通過http協議的方式實現Service的負載均衡:
對於K8S來說,Ingres就是一個資源控制器,用來控制資源的訪問策略,
(三)部署Ingres及使用同一個域名訪問不同服務
Ingres的配置文件如下所示:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx namespace: default labels: app: nginx spec: rules: - host: ingress.lcl.com http: paths: - backend: serviceName: nginx servicePort: 80
在上面文件中,配置了Ingres的規則,那么對於使用同一個域名訪問不同服務的配置,則是在paths下面增加多個path路徑,讓path指向不同的服務,並且在metadata中新增請求重寫配置annotations,去除path的路徑,保證訪問到指定服務上的路徑不帶有該path,具體配置如下所示:
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx namespace: default labels: app: nginx annotations: nginx.ingress.kubernetes.io/rewrite-target: / # 請求重寫 spec: rules: - host: ingress.lcl.com http: paths: - path: /nginx # 把path追加到域名后面 ingress.lcl.com/nginx 把/nignx當成服務請求的一部分 backend: serviceName: nginx servicePort: 80 - path: /tomcat backend: serviceName: tomcat servicePort: 8080
(四)不同域名訪問不同服務
不同域名訪問不同服務主要是在rules下面配置不同的規則即可。
# 使用多個域名,訪問不同的服務 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx namespace: default labels: app: nginx spec: rules: - host: ingress.lcl.com http: paths: - path: / backend: serviceName: nginx servicePort: 80 - host: ingress.lcl.com http: paths: - path: / backend: serviceName: tomcat servicePort: 8080
(五)Ingres和https
如果想將http請求升級為https,我們就需要制作證書
# 生成私鑰 openssl genrsa -out lcl.key 2048 # 自簽發證書 openssl req -new -x509 -key lcl.key -out lcl.crt -subj /C=CN/ST=Shanghai/L=Shanghai/O=DevOps/CN=ingres.lcl.com # 創建K8S使用的證書 kubectl create secret tls lcl-secret --cert=lcl.crt --key=lcl.key
創建ingres,使用證書的Ingres
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-tomcat-tls namespace: default annotations: kubernetes.io/ingress.class: "nginx" labels: app: tomcat spec: tls: - hosts: - ingress.lcl.com secretName: lcl rules: - host: ingres.lcl.com http: paths: - backend: serviceName: nginx servicePort: 8080
查看secret資源
kubectl describe secret lcl
修改原來的ingres配置文件
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx namespace: default labels: app: nginx spec: tls: - hosts: - ingres.lcl.com secretName: lcl-secret rules: - host: ingress.lcl.com http: paths: - backend: serviceName: nginx servicePort: 80
主要就是新增了spec下面的tls配置,使用了剛才創建的secret文件,此時使用https訪問即可正常訪問。