K8S之Service詳解
簡介
在K8S中,Pod是容器運行的載體,我們可以訪問Pod內部的容器來訪問相對應的服務,但是Pod的IP地址不是固定的,因此這也就意味我們不方便直接采用IP地址對Pod進行訪問管理。
在K8S中提供我們 service
服務來解決上述問題, service
對一組pod進行聚合,並且提供一個統一的外部接口地址,我們通過訪問該接口地址就可以訪問其所對應的pod服務。
本質上來說其實 service
只是一個概念,真正起到轉發數據的是 kube-proxy
服務進程,每個節點之上都運行一個 kube-proxy
服務進程,當創建 service
的時候 API Service
會監聽 service
然后向 etcd
寫入 service
的信息, kube-proxy
會監聽 etcd
中 service
的信息並且將 service
信息轉發成對應的訪問規則。
kube-proxy工作模式
userspace
在該模式下 kube-proxy
會為每一個 service
創建一個監聽端口,發送給 Cluseter IP
請求會被 iptable
重定向給 kube-proxy
監聽的端口上,其中 kube-proxy
會根據 LB
算法將請求轉發到相應的pod之上。
該模式下,kube-proxy充當了一個四層負載均衡器的角色。由於kube-proxy運行在userspace中,在進行轉發處理的時候會增加內核和用戶空間之間的數據拷貝,雖然比較穩定,但是效率非常低下。
iptables
iptables模式下 kube-proxy
為每一個pod創建相對應的 iptables
規則,發送給 ClusterIP
的請求會被直接發送給后端pod之上
在該模式下 kube-proxy
不承擔負載均衡器的角色,其只會負責創建相應的轉發策略,該模式的優點在於較userspace模式效率更高,但是不能提供靈活的LB策略,當后端Pod不可用的時候無法進行重試。
ipvs模式
ipvs模式與iptable模式類型, kube-proxy
會根據pod的變化創建相應的 ipvs
轉發規則,ipvs相對iptable來說轉發效率更加高效,同時提供了大量的負責均衡算法。
使用ipvs模式必須安裝ipvs內核模塊,否則會自動降級為iptables
# 編輯配置文件 搜索43行mode將其修改為ipvs
[root@master k8s]# kubectl edit cm kube-proxy -n kube-system
# 刪除原有的代理
[root@master k8s]# kubectl delete pod -l k8s-app=kube-proxy -n kube-system
# 查看
[root@master k8s]# ipvsadm -Ln
service類型
資源清單
apiVersion: v1 # 版本
kind: Service # 類型
metadata: # 元數據
name: # 資源名稱
namespace: # 命名空間
spec:
selector: # 標簽選擇器,用於確定當前Service代理那些Pod
app: nginx
type: # Service的類型,指定Service的訪問方式
clusterIP: # 虛擬服務的IP地址
sessionAffinity: # session親和性,支持ClientIP、None兩個選項,默認值為None
ports: # 端口信息
- port: 8080 # Service端口
protocol: TCP # 協議
targetPort : # Pod端口
nodePort: # 主機端口
- ClusterIP:默認類型,處於該模式下K8S會自動為service分配地址,其只能在集群內部訪問。
- NodePort:將Service通過指定的Node上的端口暴露給外部,通過此方法,就可以在集群外部訪問服務。
- LoadBalancer:使用外接負載均衡器完成到服務的負載分發,注意此模式需要外部雲環境的支持。
- ExternalName:把集群外部的服務引入集群內部,直接使用。
service應用
Clusterip service
cat > service-clusterip.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-clusterip
namespace: dev
spec:
selector:
app: nginx-pod
type: ClusterIP
# service IP地址 如果不寫默認會生成一個
clusterIP: 10.97.97.97
ports:
# service端口
- port: 80
# 目標pod端口
targetPort: 80
EOF
# 創建service
[root@master k8s]# kubectl create -f service-clusterip.yaml
# 查看service
[root@master k8s]# kubectl get -n dev service service-clusterip -o wide
創建pod
cat > deployment.yaml << EOF
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
protocol: TCP
EOF
# 創建pod
[root@master k8s]# kubectl create -f deployment.yaml
# 查看pod
[root@master k8s]# kubectl get pod -n dev -o wide --show-labels
# 任意節點發起請求
[root@master k8s]# curl 10.244.2.64
# 由於pod節點都是nginx默認界面都是一樣的為了方便測試修改默認界面
# 進入容器
[root@master k8s]# kubectl exec -it -n dev pc-deployment-7d7dd5499b-j5hnp /bin/sh
# 三個節點寫入數據
echo "Current Request is 10.244.1.64" > /usr/share/nginx/html/index.html
echo "Current Request is 10.244.1.63" > /usr/share/nginx/html/index.html
echo "Current Request is 10.244.1.62" > /usr/share/nginx/html/index.html
# 發起請求
[root@master k8s]# curl 10.244.2.64
[root@master k8s]# curl 10.244.2.63
[root@master k8s]# curl 10.244.2.62
創建services
cat > service-clusterip.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-clusterip
namespace: dev
spec:
selector:
app: nginx-pod
type: ClusterIP
# service IP地址 如果不寫默認會生成一個
clusterIP: 10.97.97.97
ports:
# service端口
- port: 80
# 目標pod端口
targetPort: 80
EOF
# 創建service
[root@master k8s]# kubectl create -f service-clusterip.yaml
# 查看service
[root@master k8s]# kubectl get -n dev service service-clusterip -o wide
# 查看詳情
[root@master k8s]# kubectl describe svc service-clusterip -n dev
# 多次訪問后端節點
[root@master k8s]# curl 10.97.97.97
Endpoint
Endpoint是kubernetes中的一個資源對象,存儲在etcd中,用來記錄一個service對應的所有Pod的訪問地址,它是根據service配置文件中的selector描述產生的。
一個service由一組Pod組成,這些Pod通過Endpoints暴露出來,Endpoints是實現實際服務的端點集合。換言之,service和Pod之間的聯系是通過Endpoints實現的。
# 查看endpoints
[root@master k8s]# kubectl get endpoints -n dev
負載分發策略
對Service的訪問被分發到了后端的Pod上去,目前kubernetes提供了兩種負載分發策略:
- 如果不定義,默認使用kube-proxy的策略,比如隨機、輪詢等。
- 基於客戶端地址的會話保持模式,即來自同一個客戶端發起的所有請求都會轉發到固定的一個Pod上,這對於傳統基於Session的認證項目來說很友好,此模式可以在spec中添加
sessionAffinity: ClusterIP
選項。
# 修改分發策略
apiVersion: v1
kind: Service
metadata:
name: service-clusterip
namespace: dev
spec:
selector:
app: nginx-pod
clusterIP: 10.97.97.97 # service的IP地址,如果不寫,默認會生成一個
type: ClusterIP
sessionAffinity: ClientIP # 修改分發策略為基於客戶端地址的會話保持模式
ports:
- port: 80 # Service的端口
targetPort: 80 # Pod的端口
[root@master k8s]# kubectl create -f service-clusterip.yaml
# 循環測試
[root@master k8s]# while true;do curl 10.97.97.97:80; sleep 5; done;
HeadLiness services
在某些場景中,開發人員可能不想使用Service提供的負載均衡功能,而希望自己來控制負載均衡策略,針對這種情況,kubernetes提供了HeadLinesss Service,這類Service不會分配Cluster IP,如果想要訪問Service,只能通過Service的域名進行查詢。
創建service
cat > service-headliness.yaml << EOF
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 # Service的端口
targetPort: 80 # Pod的端口
EOF
[root@master k8s]# kubectl create -f service-headliness.yaml
# 查看service
[root@master k8s]# kubectl get svc service-headliness -n dev -o wide
# 查看詳情
[root@master k8s]# kubectl describe svc service-headliness -n dev
# 查看pod
[root@master k8s]# kubectl get pod -n dev
# 進入Pod
[root@master k8s]# kubectl exec -it -n dev pc-deployment-7d7dd5499b-jc84t /bin/bash
# 查看域名
root@pc-deployment-7d7dd5499b-jc84t:/# cat /etc/resolv.conf
# 通過Service的域名進行查詢:
# 默認訪問規則service名稱.名稱空間.svc.cluster.local
# 安裝dig
[root@master k8s]# yum -y install bind-utils
# 訪問
[root@master k8s]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local
NodePort service
在上述案例之中 service 暴露的IP 只能供集群內部訪問,但是我們創建資源即暴露給用戶使用,因此 K8S 為我們提供了 NodePort service
其會將service 的端口與 node 的端口進行映射,當我們訪問 node 的 IP + Port
即為訪問 service 所對應的資源
創建 service
# 創建 service
cat > cat service-nodeport.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-nodeport
namespace: dev
spec:
selector:
app: nginx-pod
type: NodePort # Service類型為NodePort
ports:
- port: 80 # Service的端口
targetPort: 80 # Pod的端口
nodePort: 30002 # 指定綁定的node的端口(默認取值范圍是30000~32767),如果不指定,會默認分配
EOF
# 創建pod
[root@master k8s]# kubectl create -f service-nodeport.yaml
# 查看詳情
[root@master k8s]# kubectl get svc -n dev -o wide
# 訪問測試 由於更換機器此時 master 的地址由 10.1.1.2 更改為 172.16.137.128
curl 172.16.137.128:30002
LoadBalancer service
通過上圖可以看到使用 NodePort
模式中對負載均衡能力不是很友好,external 類型與 nodeport 類似都是對於外部暴露一個訪問端口
區別在於使用該模式會在集群外部添加一個負載均衡設備,外部訪問負載均衡設置,由負載均衡設備在根據一定的算法轉發到后端服務節點
ExternalName service
ExternalName類型的Service用於引入集群外部的服務,它通過externalName屬性指定一個服務的地址,然后在集群內部訪問此Service就可以訪問到外部的服務了。
創建 service
cat > service-externalname.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: service-externalname
namespace: dev
spec:
type: ExternalName # Service類型為ExternalName
externalName: www.baidu.com # 改成IP地址也可以
EOF
[root@master k8s]# kubectl create -f service-externalname.yaml
# 查看 service
[root@master k8s]# kubectl get svc service-externalname -n dev
# 安裝域名解析
[root@master k8s]# yum install bind-utils
# 域名解析
[root@master k8s]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local
Ingress
簡介
在上述中 service 對外提供負載均衡主要有 nodeport 與 loadblancer 兩種方式,但是這兩種方式各自都有一定的缺點,在 nodeport 方式中 service 會與 node 節點進行映射,這樣會占用大量的端口,當 service 過多的時候可能會導致節點端口不夠,在 loadblancer 中每一個service 都需要一個 LB,並且需要外部的負載均衡設備進行支持
基於上述問題,在 K8S 中提出了 ingress
資源對象,該資源對象只需要一個 nodeport 或者一個 LB 就可以滿足暴露多個 service 需求
實際上 Ingress 類似於一個七層的負載均衡器,是由 K8S 對反向代理的抽象,其工作原理類似於 Nginx 可以理解為Ingress里面建立了諸多映射規則,Ingress Controller通過監聽這些配置規則並轉化為Nginx的反向代理配置,然后對外提供服務。
-
Ingress:kubernetes中的一個對象,作用是定義請求如何轉發到Service的規則。
-
Ingress Controller:具體實現反向代理及負載均衡的程序,對Ingress定義的規則進行解析,根據配置的規則來實現請求轉發,實現的方式有很多,比如Nginx,Contour,Haproxy等。
-
其工作原理如下
- 用戶編寫 Ingress 規則說明那個域名對應那個 service
- Ingress Contoller 動態感知 ingress 編寫的規則,然后生成對應的反向代理規則
- ingress 控制器會根據生成代理規則寫入到代理服務中
- 客戶端請求代理服務,由代理服務轉發到后端 pod 節點
Ingress 使用
環境搭建
mkdir ingress-controller && cd ingress-controller
# 下載service 與控制器
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
# 更換網絡源
sed -i 's#quay.io/kubernetes-ingress-controller/nginx-ingress-controller#registry.cn-qingdao.aliyuncs.com/kubernetes_xingej/nginx-ingress-controller#g' mandatory.yaml
# 使用配置文件
kubectl apply -f ./
# 查看pod
[root@master ~]# kubectl get -n ingress-nginx pod
# 查看svc
[root@master ~]# kubectl get -n ingress-nginx svc
准備Service和pod
cat > tomcat-nginx.yaml << EOF
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
EOF
# 創建pod
[root@master ~]# kubectl create -f tomcat-nginx.yaml
# 查看
[root@master ~]# kubectl get svc,pod -n dev
HTTP代理
cat > ingress-http.yaml << EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-http
namespace: dev
spec:
# 代理規則
rules:
# 綁定域名
- host: nginx.sr.com
http:
paths:
# 綁定路徑
- path: /
backend:
serviceName: nginx-service
# service暴露端口
servicePort: 80
- host: tomcat.sr.com
http:
paths:
- path: /
backend:
serviceName: tomcat-service
servicePort: 8080
EOF
# 創建
[root@master ~]# kubectl create -f ingress-http.yaml
# 查看
[root@master ~]# kubectl get pod -n dev -o wide
# 查看詳情
[root@master ~]# kubectl describe ingress ingress-http -n dev
配置本地host文件將master的IP地址與上述域名綁定
# 訪問nginx
curl nginx.sr.com:30771
# 訪問tomcat
curl tomcat.sr.com:30378
HTTPS 代理
# 生成證書
[root@master ~]# 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=sr.com"
# 生成秘鑰
[root@master ~]# kubectl create secret tls tls-secret --key tls.key --cert tls.crt
# 生成配置文件
cat > ingress-https.yaml << EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-https
namespace: dev
spec:
tls:
- hosts:
- nginx.sr.com
- tomcat.sr.com
secretName: tls-secret # 指定秘鑰需要與上述生成的秘鑰名稱相同
rules:
- host: nginx.sr.com
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.sr.com
http:
paths:
- path: /
backend:
serviceName: tomcat-service
servicePort: 8080
EOF
# 加載配置文件
[root@master k8s]# kubectl create -f ingress-https.yaml
# 查看配置文件
[root@master k8s]# kubectl get ingress ingress-https -n dev
# 查看詳情
[root@master k8s]# kubectl describe -n dev ingress ingress-https
# 查看service
[root@master k8s]# kubectl get -n ingress-nginx service