kubernetes的流量負載組件:Service和Ingress
一、 Service介紹
在kubernetes中,pod是應用程序的載體,我們可以通過pod的ip來訪問應用程序,但是pod的ip地址不是固定的,這也就意味着不方便直接采用pod的ip對服務進行訪問。
為了解決這個問題,kubernetes提供了Service資源,Service會對提供同一個服務的多個pod進行聚合,並且提供一個統一的入口地址。通過訪問Service的入口地址就能訪問到后面的pod服務。
Service在很多情況下只是一個概念,真正起作用的其實是kube-proxy服務進程,每個Node節點上都運行着一個kube-proxy服務進程。當創建Service的時候會通過api-server向etcd寫入創建的service的信息,而kube-proxy會基於監聽的機制發現這種Service的變動,然后它會將最新的Service信息轉換成對應的訪問規則
# 10.97.97.97:80 是service提供的訪問入口
# 當訪問這個入口的時候,可以發現后面有三個pod的服務在等待調用,
# kube-proxy會基於rr(輪詢)的策略,將請求分發到其中一個pod上去
# 這個規則會同時在集群內的所有節點上都生成,所以在任何一個節點上訪問都可以。
[root@node1 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.97.97.97:80 rr
-> 10.244.1.39:80 Masq 1 0 0
-> 10.244.1.40:80 Masq 1 0 0
-> 10.244.2.33:80 Masq 1 0 0
kube-proxy目前支持三種工作模式:
userspace 模式
userspace模式下,kube-proxy會為每一個Service創建一個監聽端口,發向Cluster IP的請求被Iptables規則重定向到kube-proxy監聽的端口上,kube-proxy根據LB算法選擇一個提供服務的Pod並和其建立鏈接,以將請求轉發到Pod上。 該模式下,kube-proxy充當了一個四層負責均衡器的角色。由於kube-proxy運行在userspace中,在進行轉發處理時會增加內核和用戶空間之間的數據拷貝,雖然比較穩定,但是效率比較低。
iptables 模式
iptables模式下,kube-proxy為service后端的每個Pod創建對應的iptables規則,直接將發向Cluster IP的請求重定向到一個Pod IP。 該模式下kube-proxy不承擔四層負責均衡器的角色,只負責創建iptables規則。該模式的優點是較userspace模式效率更高,但不能提供靈活的LB策略,當后端Pod不可用時也無法進行重試。
ipvs 模式
ipvs模式和iptables類似,kube-proxy監控Pod的變化並創建相應的ipvs規則。ipvs相對iptables轉發效率更高。除此以外,ipvs支持更多的LB算法。
# 此模式必須安裝ipvs內核模塊,否則會降級為iptables
# 開啟ipvs
[root@k8s-master01 ~]# kubectl edit cm kube-proxy -n kube-system
# 修改mode: "ipvs"
[root@k8s-master01 ~]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system
[root@node1 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.97.97.97:80 rr
-> 10.244.1.39:80 Masq 1 0 0
-> 10.244.1.40:80 Masq 1 0 0
-> 10.244.2.33:80 Masq 1 0 0
二、 Service類型
k8s默認的配置文件
[root@k8s-m-01 ~]# cat /etc/kubernetes/manifests/kube-apiserver.yaml
Service的資源清單文件:
kind: Service # 資源類型
apiVersion: v1 # 資源版本
metadata: # 元數據
name: service # 資源名稱
namespace: dev # 命名空間
spec: # 描述
selector: # 標簽選擇器,用於確定當前service代理哪些pod,只支持精准匹配
app: nginx
type: # Service類型,指定service的訪問方式,如果定義NodePort,就要定義他的端口
clusterIP: # 虛擬服務的ip地址
sessionAffinity: # session親和性,支持ClientIP、None兩個選項
ports: # 端口信息
- protocol: TCP
port: 3017 # service端口
targetPort: 5003 # pod端口
nodePort: 31122 # 主機端口,默認是30000-32700
protocal: TCP #反向代理的網絡類型
#如果代理多個IP,要用名字區別開來
kind: Service
apiVersion: v1
metadata:
name: test-svc
namespace: default
spec:
ports:
- port: 80 #service負載均衡器的端口
targetPort: 80 #容器的端口
name: http
- port: 443
targetPort: 443
name: https
- ClusterIP:默認值,它是Kubernetes系統自動分配的虛擬IP,只能在集群內部訪問
- NodePort:將Service通過指定的Node上的端口暴露給外部,通過此方法,就可以在集群外部訪問服務
- LoadBalancer:使用外接負載均衡器完成到服務的負載分發,注意此模式需要外部雲環境支持
- ExternalName: 把集群外部的服務引入集群內部,直接使用
三、 Service使用
1. 實驗環境准備
在使用service之前,首先利用Deployment創建出3個pod,注意要為pod設置app=nginx-pod
的標簽
創建deployment.yaml,內容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pc-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
[root@k8s-master01 ~]# kubectl create -f deployment.yaml
deployment.apps/pc-deployment created
# 查看pod詳情
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide --show-labels
NAME READY STATUS IP NODE LABELS
pc-deployment-66cb59b984-8p84h 1/1 Running 10.244.1.39 node1 app=nginx-pod
pc-deployment-66cb59b984-vx8vx 1/1 Running 10.244.2.33 node2 app=nginx-pod
pc-deployment-66cb59b984-wnncx 1/1 Running 10.244.1.40 node1 app=nginx-pod
# 為了方便后面的測試,修改下三台nginx的index.html頁面(三台修改的IP地址不一致)
# kubectl exec -it pc-deployment-66cb59b984-8p84h -n dev /bin/sh
# echo "10.244.1.39" > /usr/share/nginx/html/index.html
#修改完畢之后,訪問測試
[root@k8s-master01 ~]# curl 10.244.1.39
10.244.1.39
[root@k8s-master01 ~]# curl 10.244.2.33
10.244.2.33
[root@k8s-master01 ~]# curl 10.244.1.40
10.244.1.40
2. ClusterIP類型的Service
k8s默認的類型
創建service-clusterip.yaml文件
apiVersion: v1
kind: Service
metadata:
name: service-clusterip
namespace: dev
spec:
selector:
app: nginx-pod
clusterIP: 10.97.97.97 # service的ip地址,如果不寫,默認會生成一個
type: ClusterIP
ports:
- port: 80 # Service端口
targetPort: 80 # pod端口
# 創建service
[root@k8s-master01 ~]# kubectl create -f service-clusterip.yaml
service/service-clusterip created
# 查看service
[root@k8s-master01 ~]# kubectl get svc -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-clusterip ClusterIP 10.97.97.97 <none> 80/TCP 13s app=nginx-pod
# 查看service的詳細信息
# 在這里有一個Endpoints列表,里面就是當前service可以負載到的服務入口
[root@k8s-master01 ~]# kubectl describe svc service-clusterip -n dev
Name: service-clusterip
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx-pod
Type: ClusterIP
IP: 10.97.97.97
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.39:80,10.244.1.40:80,10.244.2.33:80
Session Affinity: None
Events: <none>
# 查看ipvs的映射規則
[root@k8s-master01 ~]# ipvsadm -Ln
TCP 10.97.97.97:80 rr
-> 10.244.1.39:80 Masq 1 0 0
-> 10.244.1.40:80 Masq 1 0 0
-> 10.244.2.33:80 Masq 1 0 0
# 訪問10.97.97.97:80觀察效果
[root@k8s-master01 ~]# curl 10.97.97.97:80
10.244.2.33
Endpoint
Endpoint是kubernetes中的一個資源對象,存儲在etcd中,用來記錄一個service對應的所有pod的訪問地址,它是根據service配置文件中selector描述產生的。
一個Service由一組Pod組成,這些Pod通過Endpoints暴露出來,Endpoints是實現實際服務的端點集合。換句話說,service和pod之間的聯系是通過endpoints實現的。
負載分發策略
對Service的訪問被分發到了后端的Pod上去,目前kubernetes提供了兩種負載分發策略:
-
如果不定義,默認使用kube-proxy的策略,比如隨機、輪詢
-
基於客戶端地址的會話保持模式,即來自同一個客戶端發起的所有請求都會轉發到固定的一個Pod上
此模式可以使在spec中添加
sessionAffinity:ClientIP
選項
# 查看ipvs的映射規則【rr 輪詢】
[root@k8s-master01 ~]# ipvsadm -Ln
TCP 10.97.97.97:80 rr
-> 10.244.1.39:80 Masq 1 0 0
-> 10.244.1.40:80 Masq 1 0 0
-> 10.244.2.33:80 Masq 1 0 0
# 循環訪問測試
[root@k8s-master01 ~]# while true;do curl 10.97.97.97:80; sleep 5; done;
10.244.1.40
10.244.1.39
10.244.2.33
10.244.1.40
10.244.1.39
10.244.2.33
# 修改分發策略----sessionAffinity: ClientIP
# 查看ipvs規則【persistent 代表持久】
[root@k8s-master01 ~]# ipvsadm -Ln
TCP 10.97.97.97:80 rr persistent 10800
-> 10.244.1.39:80 Masq 1 0 0
-> 10.244.1.40:80 Masq 1 0 0
-> 10.244.2.33:80 Masq 1 0 0
# 循環訪問測試
[root@k8s-master01 ~]# while true;do curl 10.97.97.97; sleep 5; done;
10.244.2.33
10.244.2.33
10.244.2.33
# 刪除service
[root@k8s-master01 ~]# kubectl delete -f service-clusterip.yaml
service "service-clusterip" deleted
3. HeadLiness類型的Service
在某些場景中,開發人員可能不想使用Service提供的負載均衡功能,而希望自己來控制負載均衡策略,針對這種情況,kubernetes提供了HeadLiness Service,這類Service不會分配Cluster IP,如果想要訪問service,只能通過service的域名進行查詢。
創建service-headliness.yaml
apiVersion: v1
kind: Service
metadata:
name: service-headliness
namespace: dev
spec:
selector:
app: nginx-pod
clusterIP: None # 將clusterIP設置為None,即可創建headliness Service
type: ClusterIP
ports:
- port: 80
targetPort: 80
# 創建service
[root@k8s-master01 ~]# kubectl create -f service-headliness.yaml
service/service-headliness created
# 獲取service, 發現CLUSTER-IP未分配
[root@k8s-master01 ~]# kubectl get svc service-headliness -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-headliness ClusterIP None <none> 80/TCP 11s app=nginx-pod
# 查看service詳情
[root@k8s-master01 ~]# kubectl describe svc service-headliness -n dev
Name: service-headliness
Namespace: dev
Labels: <none>
Annotations: <none>
Selector: app=nginx-pod
Type: ClusterIP
IP: None
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.39:80,10.244.1.40:80,10.244.2.33:80
Session Affinity: None
Events: <none>
# 查看域名的解析情況
[root@k8s-master01 ~]# kubectl exec -it pc-deployment-66cb59b984-8p84h -n dev /bin/sh
/ # cat /etc/resolv.conf
nameserver 10.96.0.10
search dev.svc.cluster.local svc.cluster.local cluster.local
[root@k8s-master01 ~]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.1.40
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.1.39
service-headliness.dev.svc.cluster.local. 30 IN A 10.244.2.33
4. NodePort類型的Service
在之前的樣例中,創建的Service的ip地址只有集群內部才可以訪問,如果希望將Service暴露給集群外部使用,那么就要使用到另外一種類型的Service,稱為NodePort類型。NodePort的工作原理其實就是將service的端口映射到Node的一個端口上,然后就可以通過NodeIp:NodePort
來訪問service了。
創建service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: service-nodeport
namespace: dev
spec:
selector:
app: nginx-pod
type: NodePort # service類型
ports:
- port: 80
nodePort: 30002 # 指定綁定的node的端口(默認的取值范圍是:30000-32767), 如果不指定,會默認分配
targetPort: 80
#指定綁定的node端口可以修改默認值
[root@k8s-m-01 ~]# vim /etc/kubernetes/manifests/kube-apiserver.yaml
--service-node-port-range=0-1000
[root@k8s-m-01 ~]# kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
# 創建service
[root@k8s-master01 ~]# kubectl create -f service-nodeport.yaml
service/service-nodeport created
# 查看service
[root@k8s-master01 ~]# kubectl get svc -n dev -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) SELECTOR
service-nodeport NodePort 10.105.64.191 <none> 80:30002/TCP app=nginx-pod
# 接下來可以通過電腦主機的瀏覽器去訪問集群中任意一個nodeip的30002端口,即可訪問到pod
5. LoadBalancer類型的Service
彈性IP
LoadBalancer和NodePort很相似,目的都是向外部暴露一個端口,區別在於LoadBalancer會在集群的外部再來做一個負載均衡設備,而這個設備需要外部環境支持的,外部服務發送到這個設備上的請求,會被設備負載之后轉發到集群中。
必須要在公有雲上創建集群彈性ip
6 .ExternalName類型的Service
ExternalName類型的Service用於引入集群外部的服務,它通過externalName
屬性指定外部一個服務的地址,然后在集群內部訪問此service就可以訪問到外部的服務了。
apiVersion: v1
kind: Service
metadata:
name: service-externalname
namespace: dev
spec:
type: ExternalName # service類型
externalName: www.baidu.com #改成ip地址也可以
# 創建service
[root@k8s-master01 ~]# kubectl create -f service-externalname.yaml
service/service-externalname created
# 域名解析
[root@k8s-master01 ~]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local
service-externalname.dev.svc.cluster.local. 30 IN CNAME www.baidu.com.
www.baidu.com. 30 IN CNAME www.a.shifen.com.
www.a.shifen.com. 30 IN A 39.156.66.18
www.a.shifen.com. 30 IN A 39.156.66.14
四、 Ingress介紹
在前面課程中已經提到,Service對集群之外暴露服務的主要方式有兩種:NotePort和LoadBalancer,但是這兩種方式,都有一定的缺點:
- NodePort方式的缺點是會占用很多集群機器的端口,那么當集群服務變多的時候,這個缺點就愈發明顯
- LB方式的缺點是每個service需要一個LB,浪費、麻煩,並且需要kubernetes之外設備的支持
基於這種現狀,kubernetes提供了Ingress資源對象,Ingress只需要一個NodePort或者一個LB就可以滿足暴露多個Service的需求。工作機制大致如下圖表示:
實際上,Ingress相當於一個7層的負載均衡器,是kubernetes對反向代理的一個抽象,它的工作原理類似於Nginx,可以理解成在Ingress里建立諸多映射規則,Ingress Controller通過監聽這些配置規則並轉化成Nginx的反向代理配置 , 然后對外部提供服務。在這里有兩個核心概念:
- ingress:kubernetes中的一個對象,作用是定義請求如何轉發到service的規則
- ingress controller:具體實現反向代理及負載均衡的程序,對ingress定義的規則進行解析,根據配置的規則來實現請求轉發,實現方式有很多,比如Nginx, Contour, Haproxy等等
Ingress(以Nginx為例)的工作原理如下:
- 用戶編寫Ingress規則,說明哪個域名對應kubernetes集群中的哪個Service
- Ingress控制器動態感知Ingress服務規則的變化,然后生成一段對應的Nginx反向代理配置
- Ingress控制器會將生成的Nginx配置寫入到一個運行着的Nginx服務中,並動態更新
- 到此為止,其實真正在工作的就是一個Nginx了,內部配置了用戶定義的請求轉發規則
五、 Ingress使用
1 .環境准備
搭建ingress環境
# 創建文件夾
[root@k8s-master01 ~]# mkdir ingress-controller
[root@k8s-master01 ~]# cd ingress-controller/
# 獲取ingress-nginx,本次案例使用的是0.30版本
[root@k8s-master01 ingress-controller]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
[root@k8s-master01 ingress-controller]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
# 修改mandatory.yaml文件中的倉庫
# 修改quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0
# 為quay-mirror.qiniu.com/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0
# 創建ingress-nginx
[root@k8s-master01 ingress-controller]# kubectl apply -f ./
# 查看ingress-nginx
[root@k8s-master01 ingress-controller]# kubectl get pod -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/nginx-ingress-controller-fbf967dd5-4qpbp 1/1 Running 0 12h
# 查看service
[root@k8s-master01 ingress-controller]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.98.75.163 <none> 80:32240/TCP,443:31335/TCP 11h
准備service和pod
為了后面的實驗比較方便,創建如下圖所示的模型
創建tomcat-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deployment
namespace: dev
spec:
replicas: 3
selector:
matchLabels:
app: tomcat-pod
template:
metadata:
labels:
app: tomcat-pod
spec:
containers:
- name: tomcat
image: tomcat:8.5-jre10-slim
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: dev
spec:
selector:
app: nginx-pod
clusterIP: None
type: ClusterIP
ports:
- port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
namespace: dev
spec:
selector:
app: tomcat-pod
clusterIP: None
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
# 創建
[root@k8s-master01 ~]# kubectl create -f tomcat-nginx.yaml
# 查看
[root@k8s-master01 ~]# kubectl get svc -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP None <none> 80/TCP 48s
tomcat-service ClusterIP None <none> 8080/TCP 48s
2 .Http代理
創建ingress-http.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-http
namespace: dev
spec:
rules:
- host: nginx.itheima.com
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.itheima.com
http:
paths:
- path: /
backend:
serviceName: tomcat-service
servicePort: 8080
# 創建
[root@k8s-master01 ~]# kubectl create -f ingress-http.yaml
ingress.extensions/ingress-http created
# 查看
[root@k8s-master01 ~]# kubectl get ing ingress-http -n dev
NAME HOSTS ADDRESS PORTS AGE
ingress-http nginx.itheima.com,tomcat.itheima.com 80 22s
[root@k8s-m-01 ~]# kubectl get svc -n ingress-nginx
...
post(80:32240)/TCP,443:31335/TCP
# 查看詳情
[root@k8s-master01 ~]# kubectl describe ing ingress-http -n dev
...
Rules:
Host Path Backends
---- ---- --------
nginx.itheima.com / nginx-service:80 (10.244.1.96:80,10.244.1.97:80,10.244.2.112:80)
tomcat.itheima.com / tomcat-service:8080(10.244.1.94:8080,10.244.1.95:8080,10.244.2.111:8080)
...
# 接下來,在本地電腦上配置host文件,解析上面的兩個域名到192.168.109.100(master)上
# 然后,就可以分別訪問tomcat.itheima.com:32240 和 nginx.itheima.com:32240 查看效果了
3. Https代理
創建證書
# 生成證書
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/C=CN/ST=BJ/L=BJ/O=nginx/CN=itheima.com"
# 創建密鑰
kubectl create secret tls tls-secret --key tls.key --cert tls.crt
創建ingress-https.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-https
namespace: dev
spec:
tls:
- hosts:
- nginx.itheima.com
- tomcat.itheima.com
secretName: tls-secret # 指定秘鑰
rules:
- host: nginx.itheima.com
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.itheima.com
http:
paths:
- path: /
backend:
serviceName: tomcat-service
servicePort: 8080
# 創建
[root@k8s-master01 ~]# kubectl create -f ingress-https.yaml
ingress.extensions/ingress-https created
# 查看
[root@k8s-master01 ~]# kubectl get ing ingress-https -n dev
NAME HOSTS ADDRESS PORTS AGE
ingress-https nginx.itheima.com,tomcat.itheima.com 10.104.184.38 80, 443 2m42s
# 查看詳情
[root@k8s-master01 ~]# kubectl describe ing ingress-https -n dev
...
TLS:
tls-secret terminates nginx.itheima.com,tomcat.itheima.com
Rules:
Host Path Backends
---- ---- --------
nginx.itheima.com / nginx-service:80 (10.244.1.97:80,10.244.1.98:80,10.244.2.119:80)
tomcat.itheima.com / tomcat-service:8080(10.244.1.99:8080,10.244.2.117:8080,10.244.2.120:8080)
...
# 下面可以通過瀏覽器訪問https://nginx.itheima.com:31335 和 https://tomcat.itheima.com:31335來查看了