在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@master ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConnTCP 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
//檢視iptables防火牆規則
[root@master ~]# iptables -t nat -nvL
kube-proxy目前支援三種工作模式:
userspace模式下,kube-proxy會為每一個Service建立一個監聽埠,發向Cluster IP的請求被Iptables規則重定向到kube-proxy監聽的埠上,kube-proxy根據LB演演算法選擇一個提供服務的Pod並和其建立連結,以將請求轉發到Pod上。 該模式下,kube-proxy充當了一個四層負責均衡器的角色。由於kube-proxy執行在userspace中,在進行轉發處理時會增加核心和使用者空間之間的資料拷貝,雖然比較穩定,但是效率比較低。
iptables模式下,kube-proxy為service後端的每個Pod建立對應的iptables規則,直接將發向Cluster IP的請求重定向到一個Pod IP。 該模式下kube-proxy不承擔四層負責均衡器的角色,只負責建立iptables規則。該模式的優點是較userspace模式效率更高,但不能提供靈活的LB策略,當後端Pod不可用時也無法進行重試。
ipvs模式和iptables類似,kube-proxy監控Pod的變化並建立相應的ipvs規則。ipvs相對iptables轉發效率更高。除此以外,ipvs支援更多的LB演演算法。
# 此模式必須安裝ipvs核心模組,否則會降級為iptables# 開啟ipvs
//使用kubectl api-resources可以檢視cm是什麼?
[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 -LnIP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConnTCP 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的資源清單檔案:
kind: Service # 資源型別
apiVersion: v1 # 資源版本
metadata: # 後設資料
name: service # 資源名稱
namespace: dev # 名稱空間
spec: # 描述
selector: # 標籤選擇器,用於確定當前service代理哪些pod
app: nginx
type: # Service型別,指定service的存取方式
clusterIP: # 虛擬服務的ip地址
sessionAffinity: # session親和性,支援ClientIP、None兩個選項
ports: # 埠資訊
- protocol: TCP
port: 3017 # service埠
targetPort: 5003 # pod埠
nodePort: 31122 # 主機埠
在使用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.3910.244.1.39
[root@k8s-master01 ~]# curl 10.244.2.3310.244.2.33
[root@k8s-master01 ~]# curl 10.244.1.4010.244.1.40
建立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: NoneEvents: <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:8010.244.2.33
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.4010.244.1.3910.244.2.3310.244.1.4010.244.1.3910.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.3310.244.2.3310.244.2.33
# 刪除service
[root@k8s-master01 ~]# kubectl delete -f service-clusterip.yaml
service "service-clusterip" deleted
在某些場景中,開發人員可能不想使用Service提供的負載均衡功能,而希望自己來控制負載均衡策略,針對這種情況,kubernetes提供了HeadLiness Service,這類Service不會分配Cluster IP,如果想要存取service,只能通過service的域名進行查詢。
建立service-headliness.yaml
apiVersion: v1
kind: Servicemetadata:
name: service-headliness
namespace: devspec:
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
在之前的樣例中,建立的Service的ip地址只有叢集內部才可以存取,如果希望將Service暴露給叢集外部使用,那麼就要使用到另外一種型別的Service,稱為NodePort型別。NodePort的工作原理其實就是將service的埠對映到Node的一個埠上,然後就可以通過NodeIp:NodePort來存取service了。
建立service-nodeport.yaml
apiVersion: v1
kind: Servicemetadata:
name: service-nodeport
namespace: devspec:
selector:
app: nginx-pod
type: NodePort # service型別
ports:
- port: 80
nodePort: 30002 # 指定繫結的node的埠(預設的取值範圍是:30000-32767), 如果不指定,會預設分配
targetPort: 80
# 建立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
LoadBalancer和NodePort很相似,目的都是向外部暴露一個埠,區別在於LoadBalancer會在叢集的外部再來做一個負載均衡裝置,而這個裝置需要外部環境支援的,外部服務傳送到這個裝置上的請求,會被裝置負載之後轉發到叢集中。
ExternalName型別的Service用於引入叢集外部的服務,它通過externalName屬性指定外部一個服務的地址,然後在叢集內部存取此service就可以存取到外部的服務了。
apiVersion: v1
kind: Servicemetadata:
name: service-externalname
namespace: devspec:
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
在前面課程中已經提到,Service對叢集之外暴露服務的主要方式有兩種:NotePort和LoadBalancer,但是這兩種方式,都有一定的缺點:
實際上,Ingress相當於一個7層的負載均衡器,是kubernetes對反向代理的一個抽象,它的工作原理類似於Nginx,可以理解成在Ingress裡建立諸多對映規則,Ingress Controller通過監聽這些設定規則並轉化成Nginx的反向代理設定 , 然後對外部提供服務。在這裡有兩個核心概念:
搭建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
建立ingress-http.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-http
namespace: dev
spec:
ingressClassName: nginx
rules:
- host: nginx.itwangqing.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port: 80
- host: tomcat.itwangqing.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tomcat-service
port: 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.itwangqing.com,tomcat.itwangqing.com 80 22s
# 檢視詳情
[root@k8s-master01 ~]# kubectl describe ing ingress-http -n dev
...Rules:
Host Path Backends
---- ---- --------
nginx.itwangqing.com / nginx-service:80 (10.244.1.96:80,10.244.1.97:80,10.244.2.112:80)
tomcat.itwangqing.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.itwangqing.com:32240 和 nginx.itwangqing.com:32240 檢視效果了
建立證書
# 生成證書
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=itwangqing.com"
# 建立金鑰
kubectl create secret tls tls-secret --key tls.key --cert tls.crt
建立ingress-https.yaml
apiVersion: extensions/v1beta1
kind: Ingressmetadata:
name: ingress-https
namespace: devspec:
tls:
- hosts:
- nginx.itwangqing.com
- tomcat.itwangqing.com
secretName: tls-secret # 指定祕鑰
rules:
- host: nginx.itwangqing.com
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.itwangqing.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.itwangqing.com,tomcat.itwangqing.com 10.104.184.38 80, 443 2m42s
# 檢視詳情
[root@k8s-master01 ~]# kubectl describe ing ingress-https -n dev
...TLS:
tls-secret terminates nginx.itwangqing.com,tomcat.itwangqing.comRules:
Host Path Backends
---- ---- --------
nginx.itwangqing.com / nginx-service:80 (10.244.1.97:80,10.244.1.98:80,10.244.2.119:80)
tomcat.itwangqing.com / tomcat-service:8080(10.244.1.99:8080,10.244.2.117:8080,10.244.2.120:8080)
...
# 下面可以通過瀏覽器存取https://nginx.itwangqing.com:31335 和 https://tomcat.itwangqing.com:31335來檢視了