k8s四層負載均衡--Service
一、四層負載均衡Service概述
1.1、為什么要有Service
在kubernetes中,Pod是有生命周期的,如果Pod重啟它的IP很有可能會發生變化。如果我們的服務都是將Pod的IP地址寫死,Pod掛掉或者重啟,和剛才重啟的pod相關聯的其他服務將會找不到它所關聯的Pod,為了解決這個問題,在kubernetes中定義了service資源對象,Service 定義了一個服務訪問的入口,客戶端通過這個入口即可訪問服務背后的應用集群實例,service是一組Pod的邏輯集合,這一組Pod能夠被Service訪問到,通常是通過Label Selector實現的。

1)pod ip經常變化,service是pod的代理,我們客戶端訪問,只需要訪問service,就會把請求代理到Pod
2)pod ip在k8s集群之外無法訪問,所以需要創建service,這個service可以在k8s集群外訪問的。
1.2、Service概述
service是一個固定接入層,客戶端可以通過訪問service的ip和端口訪問到service關聯的后端pod,這個service工作依賴於在kubernetes集群之上部署的一個附件,就是kubernetes的dns服務(不同kubernetes版本的dns默認使用的也是不一樣的,1.11之前的版本使用的是kubeDNs,較新的版本使用的是coredns),service的名稱解析是依賴於dns附件的,因此在部署完k8s之后需要再部署dns附件,kubernetes要想給客戶端提供網絡功能,需要依賴第三方的網絡插件(flannel,calico等)。每個K8s節點上都有一個組件叫做kube-proxy,kube-proxy這個組件將始終監視着apiserver中有關service資源的變動信息,需要跟master之上的apiserver交互,隨時連接到apiserver上獲取任何一個與service資源相關的資源變動狀態,這種是通過kubernetes中固有的一種請求方法watch(監視)來實現的,一旦有service資源的內容發生變動(如創建,刪除),kube-proxy都會將它轉化成當前節點之上的能夠實現service資源調度,把我們請求調度到后端特定的pod資源之上的規則,這個規則可能是iptables,也可能是ipvs,取決於service的實現方式。
1.3、Service工作原理
k8s在創建Service時,會根據標簽選擇器selector(lable selector)來查找Pod,據此創建與Service同名的endpoint對象,當Pod 地址發生變化時,endpoint也會隨之發生變化,service接收前端client請求的時候,就會通過endpoint,找到轉發到哪個Pod進行訪問的地址。(至於轉發到哪個節點的Pod,由負載均衡kube-proxy決定)
[root@k8s-master1 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 37h
[root@k8s-master1 ~]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 192.168.40.180:6443 37h
1.4、kubernets中三類IP地址
1)Node Network(節點網絡):物理節點或者虛擬節點的網絡,如eth0接口上的網路地址
[root@k8s-master1 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:0c:29:52:bf:68 brd ff:ff:ff:ff:ff:ff
inet 192.168.40.180/24 brd 192.168.40.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fe52:bf68/64 scope link
valid_lft forever preferred_lft forever
2)Pod network(pod 網絡),創建的Pod具有的IP地址
[root@k8s-master1 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-v1-75fb478d6c-89w27 1/1 Running 0 10s 10.244.36.105 k8s-node1 <none> <none>
myapp-v1-75fb478d6c-dg4bh 1/1 Running 0 10s 10.244.36.104 k8s-node1 <none> <none>
# Node Network和Pod network這兩種網絡地址是我們實實在在配置的,其中節點網絡地址是配置在節點接口之上,而pod網絡地址是配置在pod資源之上的,因此這些地址都是配置在某些設備之上的,這些設備可能是硬件,也可能是軟件模擬的
3)Cluster Network(集群地址,也稱為service network),這個地址是虛擬的地址(virtual ip),沒有配置在某個接口上,只是出現在service的規則當中
[root@k8s-master1 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP
1.5、Service的四種類型
1.5.1、ExternalName
適用於k8s集群內部容器訪問外部資源,它沒有selector,也沒有定義任何的端口和Endpoint。以下Service 定義的是將prod名稱空間中的my-service服務映射到my.database.example.com
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
# 當查詢主機 my-service.prod.svc.cluster.local 時,DNS將返回值為my.database.example.com的CNAME記錄
# service的FQDN是: <service_name>.<namespace>.svc.cluster.local
1.5.2、ClusterIP
# 通過k8s集群內部IP暴露服務,選擇該值,服務只能夠在集群內部訪問,這也是默認的ServiceType。
[root@k8s-master1 ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 37h
metrics-server ClusterIP 10.100.13.0 <none> 443/TCP 35h
1.5.3、NodePort
通過每個Node節點上的IP和靜態端口暴露k8s集群內部的服務。通過請求<NodeIP>:<NodePort>可以把請求代理到內部的pod。
訪問流量:Client ==> NodeIP:NodePort ==> Service Ip:ServicePort ==> PodIP:ContainerPort
1.5.4、LoadBalancer
使用雲提供商的負載均衡器,可以向外部暴露服務。外部的負載均衡器可以路由到NodePort服務和ClusterIP 服務
二、Service創建
2.1、創建類型ClusterIP的Service
1)創建deployment
[root@k8s-master1 ~]# cat pod_test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80 #pod中的容器需要暴露的端口
# 更新資源清單文件
[root@k8s-master1 ~]# kubectl apply -f pod_test.yaml
# 查看剛才創建的Pod ip地址
[root@k8s-master1 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-69f769d56f-gr5jg 1/1 Running 0 6s 10.244.169.149 k8s-node2 <none> <none>
my-nginx-69f769d56f-h52dw 1/1 Running 0 6s 10.244.36.108 k8s-node1 <none> <none>
[root@k8s-master1 ~]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
my-nginx-69f769d56f-gr5jg 1/1 Running 0 52s pod-template-hash=69f769d56f,run=my-nginx
my-nginx-69f769d56f-h52dw 1/1 Running 0 52s pod-template-hash=69f769d56f,run=my-nginx
2)創建service
[root@k8s-master1 ~]# cat service_test.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: ClusterIP
ports:
- port: 80 #service的端口,暴露給k8s集群內部服務訪問
protocol: TCP
targetPort: 80 #pod容器中定義的端口
selector:
run: my-nginx #選擇擁有run=my-nginx標簽的pod
[root@k8s-master1 ~]# kubectl apply -f service_test.yaml
service/my-nginx created
[root@k8s-master1 ~]# kubectl get svc -l run=my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.107.97.200 <none> 80/TCP 14s
[root@k8s-master1 ~]# curl 10.107.97.200
<!DOCTYPE html>
<html>
<title>Welcome to nginx!</title>
</html>
.....
# 查看service詳細信息
[root@k8s-master1 ~]# kubectl describe svc my-nginx
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP Families: <none>
IP: 10.107.97.200
IPs: 10.107.97.200
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.169.149:80,10.244.36.108:80
Session Affinity: None
Events: <none>
# 查看endpoints詳細信息
[root@k8s-master1 ~]# kubectl get ep my-nginx
NAME ENDPOINTS AGE
my-nginx 10.244.169.149:80,10.244.36.108:80 2m21s
# service可以對外提供統一固定的ip地址,並將請求重定向至集群中的pod。其中“將請求重定向至集群中的pod”就是通過endpoint與selector協同工作實現。selector是用於選擇pod,由selector選擇出來的pod的ip地址和端口號,將會被記錄在endpoint中。endpoint便記錄了所有pod的ip地址和端口號。當一個請求訪問到service的ip地址時,就會從endpoint中選擇出一個ip地址和端口號,然后將請求重定向至pod中。具體把請求代理到哪個pod,需要的就是kube-proxy的輪詢實現的。service不會直接到pod,service是直接到endpoint資源,就是地址加端口,再由endpoint再關聯到pod。
3)service解析FQDN
# service解析
service只要創建完成,我們就可以直接解析它的服務名,每一個服務創建完成后都會在集群dns中動態添加一個資源記錄,添加完成后我們就可以解析了
資源記錄格式是:
SVC_NAME.NS_NAME.DOMAIN.LTD.
服務名.命名空間.域名后綴
集群默認的域名后綴是svc.cluster.local.
就像我們上面創建的my-nginx這個服務,它的完整名稱解析就是:my-nginx.default.svc.cluster.local
[root@k8s-master1 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-nginx-69f769d56f-gr5jg 1/1 Running 0 9m55s
my-nginx-69f769d56f-h52dw 1/1 Running 0 9m55s
[root@k8s-master1 ~]# kubectl exec -it my-nginx-69f769d56f-gr5jg -- /bin/bash
root@my-nginx-69f769d56f-gr5jg:/# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
root@my-nginx-69f769d56f-gr5jg:/# curl my-nginx.default.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
root@my-nginx-69f769d56f-gr5jg:/#
2.2、創建類型NodePort的Service
1)創建deployment
# 創建一個pod資源
[root@k8s-master1 ~]# cat pod_nodeport.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-nodeport
spec:
selector:
matchLabels:
run: my-nginx-nodeport
replicas: 2
template:
metadata:
labels:
run: my-nginx-nodeport
spec:
containers:
- name: my-nginx-nodeport-container
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
# 更新資源清單文件
[root@k8s-master1 ~]# kubectl apply -f pod_nodeport.yaml
deployment.apps/my-nginx-nodeport created
# 查看pod是否創建成功
[root@k8s-master1 ~]# kubectl get pods -l run=my-nginx-nodeport
NAME READY STATUS RESTARTS AGE
my-nginx-nodeport-649c945f85-7nngb 1/1 Running 0 9s
my-nginx-nodeport-649c945f85-cfgjk 1/1 Running 0 10s
2)創建service
[root@k8s-master1 ~]# cat service_nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx-nodeport
labels:
run: my-nginx-nodeport
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
nodePort: 30380
selector:
run: my-nginx-nodeport
# 更新資源清單文件
[root@k8s-master1 ~]# kubectl apply -f service_nodeport.yaml
service/my-nginx-nodeport created
# 查看剛才創建的service
[root@k8s-master1 ~]# kubectl get svc -l run=my-nginx-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-nodeport NodePort 10.109.246.208 <none> 80:30380/TCP 11s
# 查看詳細信息
[root@k8s-master1 ~]# kubectl describe svc my-nginx-nodeport
Name: my-nginx-nodeport
Namespace: default
Labels: run=my-nginx-nodeport
Annotations: <none>
Selector: run=my-nginx-nodeport
Type: NodePort
IP Families: <none>
IP: 10.109.246.208 # 只能在集群內訪問
IPs: 10.109.246.208
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 30380/TCP
Endpoints: 10.244.36.109:80,10.244.36.110:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
# 在集群外訪問:http://nodeIP:30380
# 服務請求走向:Client-node ip:30380 -> service ip:80 -> pod ip:container port

2.3、創建類型ExternalName的Service
應用場景:跨名稱空間訪問
需求:default名稱空間下的client 服務想要訪問nginx-ns名稱空間下的nginx-svc服務
1)創建client的deployment及service
[root@k8s-master1 ~]# cat client.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
spec:
replicas: 1
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: busybox
image: busybox
command: ["/bin/sh","-c","sleep 36000"]
[root@k8s-master1 ~]# kubectl apply -f client.yaml
[root@k8s-master1 ~]# cat client_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: client-svc
spec:
type: ExternalName
externalName: nginx-svc.nginx-ns.svc.cluster.local
ports:
- name: http
port: 80
targetPort: 80
# 該文件中指定了到 nginx-svc 的軟鏈,讓使用者感覺就好像調用自己命名空間的服務一樣。
# 查看pod是否正常運行
[root@k8s-master1 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
client-76b6556d97-zx77m 1/1 Running 0 90s
[root@k8s-master1 ~]# kubectl apply -f client_svc.yaml
[root@k8s-master1 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
client-svc ExternalName <none> nginx-svc.nginx-ns.svc.cluster.local 80/TCP 4s
[root@k8s-master1 ~]# kubectl describe svc client-svc
Name: client-svc
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ExternalName
IP Families: <none>
IP:
IPs: <none>
External Name: nginx-svc.nginx-ns.svc.cluster.local
Port: http 80/TCP
TargetPort: 80/TCP
Endpoints: <none>
Session Affinity: None
Events: <none>
2)創建server的deployment及service
[root@k8s-master1 ~]# kubectl create ns nginx-ns
[root@k8s-master1 ~]# cat server_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: nginx-ns
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
[root@k8s-master1 ~]# kubectl apply -f server_nginx.yaml
#查看pod是否創建成功
[root@k8s-master1 ~]# kubectl get pods -n nginx-ns
NAME READY STATUS RESTARTS AGE
nginx-7cf7d6dbc8-slmv4 1/1 Running 0 8s
[root@k8s-master1 ~]# cat nginx_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: nginx-ns
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
[root@k8s-master1 exter]# kubectl apply -f nginx_svc.yaml
[root@k8s-master1 ~]# kubectl get svc -n nginx-ns
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc ClusterIP 10.103.192.106 <none> 80/TCP 46s
3)訪問測試
# 登錄到client pod,訪問的結果一樣
[root@k8s-master1 ~]# kubectl exec -it client-76b6556d97-zx77m -- /bin/sh
/ # wget -q -O - client-svc.default.svc.cluster.local
/ # wget -q -O - nginx-svc.nginx-ns.svc.cluster.local
2.4、自定義endpoint實現映射外部服務
需求:k8s集群引用外部的mysql數據庫
# 1.在k8s-node2上安裝mysql數據庫:
[root@k8s-node2 ~]# yum install mariadb-server.x86_64 -y
[root@k8s-node2 ~]# systemctl start mariadb
[root@k8s-node2 ~]# netstat -lntp|grep 3306
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 36719/mysqld
# 2.創建mysql_service
[root@k8s-master1 mysql]# cat mysql_service.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
type: ClusterIP
ports:
- port: 3306
[root@k8s-master1 mysql]# kubectl apply -f mysql_service.yaml
service/mysql created
[root@k8s-master1 mysql]# kubectl get svc | grep mysql
mysql ClusterIP 10.103.151.40 <none> 3306/TCP 7s
root@k8s-master1 mysql]# kubectl describe svc mysql
[root@k8s-master1 mysql]# kubectl describe svc mysql
Name: mysql
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP Families: <none>
IP: 10.103.151.40
IPs: 10.103.151.40
Port: <unset> 3306/TCP
TargetPort: 3306/TCP
Endpoints: <none> # 還沒有endpoint
Session Affinity: None
Events: <none>
# 3.創建自定義endpoint
[root@k8s-master1 mysql]# cat mysql_endpoint.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: mysql
subsets:
- addresses:
- ip: 192.168.40.182
ports:
- port: 3306
[root@k8s-master1 mysql]# kubectl apply -f mysql_endpoint.yaml
endpoints/mysql created
[root@k8s-master1 mysql]# kubectl describe svc mysql
Name: mysql
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP Families: <none>
IP: 10.103.151.40
IPs: 10.103.151.40
Port: <unset> 3306/TCP
TargetPort: 3306/TCP
Endpoints: 192.168.40.182:3306 # 自定義的endpoint
Session Affinity: None
Events: <none>
# 上面配置就是將外部IP地址和服務引入到k8s集群內部,由service作為一個代理來達到能夠訪問外部服務的目的。
三、Service代理:kube-proxy組件
3.1、kube-proxy組件介紹
Kubernetes service只是把應用對外提供服務的方式做了抽象,真正的應用跑在Pod中的container里,我們的請求轉到kubernetes nodes對應的nodePort上,那么nodePort上的請求是如何進一步轉到提供后台服務的Pod的呢? 就是通過kube-proxy實現的:
kube-proxy部署在k8s的每一個Node節點上,是Kubernetes的核心組件,我們創建一個 service 的時候,kube-proxy 會在iptables中追加一些規則,為我們實現路由與負載均衡的功能。在k8s1.8之前,kube-proxy默認使用的是iptables模式,通過各個node節點上的iptables規則來實現service的負載均衡,但是隨着service數量的增大,iptables模式由於線性查找匹配、全量更新等特點,其性能會顯著下降。從k8s的1.8版本開始,kube-proxy引入了IPVS模式,IPVS模式與iptables同樣基於Netfilter,但是采用的hash表,因此當service數量達到一定規模時,hash查表的速度優勢就會顯現出來,從而提高service的服務性能。
service是一組pod的服務抽象,相當於一組pod的LB,負責將請求分發給對應的pod。service會為這個LB提供一個IP,一般稱為cluster IP。kube-proxy的作用主要是負責service的實現,具體來說,就是實現了內部從pod到service和外部的從node port向service的訪問。
1、kube-proxy其實就是管理service的訪問入口,包括集群內Pod到Service的訪問和集群外訪問service。
2、kube-proxy管理sevice的Endpoints,該service對外暴露一個Virtual IP,也可以稱為是Cluster IP, 集群內通過訪問這個Cluster IP:Port就能訪問到集群內對應的serivce下的Pod。
3.2、kube-proxy三種工作模式
3.2.1、Userspace方式

Client Pod要訪問Server Pod時,它先將請求發給內核空間中的service iptables規則,由它再將請求轉給監聽在指定套接字上的kube-proxy的端口,kube-proxy處理完請求,並分發請求到指定Server Pod后,再將請求轉發給內核空間中的service ip,由service iptables將請求轉給各個節點中的Server Pod。
這個模式有很大的問題,客戶端請求先進入內核空間的,又進去用戶空間訪問kube-proxy,由kube-proxy封裝完成后再進去內核空間的iptables,再根據iptables的規則分發給各節點的用戶空間的pod。由於其需要來回在用戶空間和內核空間交互通信,因此效率很差。在Kubernetes 1.1版本之前,userspace是默認的代理模型。
3.2.2、iptables方式

客戶端IP請求時,直接請求本地內核service ip,根據iptables的規則直接將請求轉發到到各pod上,因為使用iptable NAT來完成轉發,也存在不可忽視的性能損耗。另外,如果集群中存上萬的Service/Endpoint,那么Node上的iptables rules將會非常龐大,性能還會再打折
iptables代理模式由Kubernetes 1.1版本引入,自1.2版本開始成為默認類型
3.2.3、ipvs方式

Kubernetes自1.9-alpha版本引入了ipvs代理模式,自1.11版本開始成為默認設置。客戶端請求時到達內核空間時,根據ipvs的規則直接分發到各pod上。kube-proxy會監視Kubernetes Service對象和Endpoints,調用netlink接口以相應地創建ipvs規則並定期與Kubernetes Service對象和Endpoints對象同步ipvs規則,以確保ipvs狀態與期望一致。訪問服務時,流量將被重定向到其中一個后端Pod。與iptables類似,ipvs基於netfilter 的 hook 功能,但使用哈希表作為底層數據結構並在內核空間中工作。這意味着ipvs可以更快地重定向流量,並且在同步代理規則時具有更好的性能。此外,ipvs為負載均衡算法提供了更多選項,例如:
rr:輪詢調度
lc:最小連接數
dh:目標哈希
sh:源哈希
sed:最短期望延遲
nq:不排隊調度
3.3、kube-proxy的watch機制
如果某個服務后端pod發生變化,標簽選擇器適應的pod又多一個,適應的信息會立即反映到apiserver上,而kube-proxy一定可以watch到etcd中的信息變化,而將它立即轉為ipvs或者iptables中的規則,這一切都是動態和實時的,刪除一個pod也是同樣的原理

以上不論哪種,kube-proxy都通過watch的方式監控着apiserver寫入etcd中關於Pod的最新狀態信息,它一旦檢查到一個Pod資源被刪除了或新建了,它將立即將這些變化,反應再iptables 或 ipvs規則中,以便iptables和ipvs在調度Clinet Pod請求到Server Pod時,不會出現Server Pod不存在的情況。
自k8s1.11以后,service默認使用ipvs規則,若ipvs沒有被激活,則降級使用iptables規則。
3.4、kube-proxy生成的iptables規則分析
3.4.1、ClusterIp類型Service的iptables規則分析
在k8s創建的service,雖然有ip地址,但是service的ip是虛擬的,不存在物理機上的,是在iptables或者ipvs規則里的
# 1.創建pod及clusterIp類型的service
[root@k8s-master1 ~]# cat pod_test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80 #pod中的容器需要暴露的端口
[root@k8s-master1 ~]# cat service_test.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: ClusterIP
ports:
- port: 80 #service的端口,暴露給k8s集群內部服務訪問
protocol: TCP
targetPort: 80 #pod容器中定義的端口
selector:
run: my-nginx #選擇擁有run=my-nginx標簽的pod
[root@k8s-master1 ~]# kubectl apply -f pod_test.yaml
deployment.apps/my-nginx created
[root@k8s-master1 ~]# kubectl apply -f service_test.yaml
service/my-nginx created
# 2.查看svc和pod的ip
[root@k8s-master1 ~]# kubectl get svc -l run=my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.103.178.2 <none> 80/TCP 107s
[root@k8s-master1 ~]# kubectl get pods -l run=my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-69f769d56f-drwh7 1/1 Running 0 2m10s 10.244.36.112 k8s-node1 <none> <none>
my-nginx-69f769d56f-phqq8 1/1 Running 0 2m10s 10.244.36.113 k8s-node1 <none> <none>
# 3.查看svc ip的iptables規則
[root@k8s-master1 ~]# iptables -t nat -L | grep 10.103.178.2
KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.103.178.2 /* default/my-nginx cluster IP */ tcp dpt:http
KUBE-SVC-L65ENXXZWWSAPRCR tcp -- anywhere 10.103.178.2 /* default/my-nginx cluster IP */ tcp dpt:http
[root@k8s-master1 ~]# iptables -t nat -L | grep KUBE-SVC-L65ENXXZWWSAPRCR
KUBE-SVC-L65ENXXZWWSAPRCR tcp -- anywhere 10.103.178.2 /* default/my-nginx cluster IP */ tcp dpt:http
Chain KUBE-SVC-L65ENXXZWWSAPRCR (1 references)
# 4、查看pod ip的iptables規則
[root@k8s-master1 ~]# iptables -t nat -L | grep 10.244.36.112
KUBE-MARK-MASQ all -- 10.244.36.112 anywhere /* default/my-nginx */
DNAT tcp -- anywhere anywhere /* default/my-nginx */ tcp to:10.244.36.112:80
[root@k8s-master1 ~]# iptables -t nat -L | grep 10.244.36.113
KUBE-MARK-MASQ all -- 10.244.36.113 anywhere /* default/my-nginx */
DNAT tcp -- anywhere anywhere /* default/my-nginx */ tcp to:10.244.36.113:80
總結:通過上面可以看到之前創建的service,會通過kube-proxy在iptables中生成一個規則,來實現流量路由,有一系列目標為 KUBE-SVC-xxx 鏈的規則,每條規則都會匹配某個目標 ip 與端口。也就是說訪問某個 ip:port 的請求會由 KUBE-SVC-xxx 鏈來處理。這個目標 IP 其實就是service ip。
3.4.2、nodePort類型Service的iptables規則分析
# 1.創建pod及nodePort類型的service
[root@k8s-master1 ~]# cat pod_nodeport.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-nodeport
spec:
selector:
matchLabels:
run: my-nginx-nodeport
replicas: 2
template:
metadata:
labels:
run: my-nginx-nodeport
spec:
containers:
- name: my-nginx-nodeport-container
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
[root@k8s-master1 ~]# cat service_nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx-nodeport
labels:
run: my-nginx-nodeport
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
nodePort: 30380
selector:
run: my-nginx-nodeport
[root@k8s-master1 ~]# kubectl apply -f pod_nodeport.yaml
deployment.apps/my-nginx-nodeport created
[root@k8s-master1 ~]# kubectl apply -f service_nodeport.yaml
service/my-nginx-nodeport created
# 2.查看svc和pod的ip
[root@k8s-master1 ~]# kubectl get svc -l run=my-nginx-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-nodeport NodePort 10.109.183.22 <none> 80:30380/TCP 41s
[root@k8s-master1 ~]# kubectl get pods -l run=my-nginx-nodeport -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-nodeport-649c945f85-4zk46 1/1 Running 0 9m27s 10.244.36.115 k8s-node1 <none> <none>
my-nginx-nodeport-649c945f85-xnwks 1/1 Running 0 9m27s 10.244.36.114 k8s-node1 <none> <none>
# 3.查看關於nodeport=30380的iptables規則
[root@k8s-master1 ~]# iptables -t nat -S | grep 30380
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
# 4.查看KUBE-SVC-J5QV2XWG4FEBPH3Q鏈
[root@k8s-master1 ~]# iptables -t nat -S | grep KUBE-SVC-J5QV2XWG4FEBPH3Q
-N KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp --dport 30380 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-SERVICES -d 10.109.183.22/32 -p tcp -m comment --comment "default/my-nginx-nodeport cluster IP" -m tcp --dport 80 -j KUBE-SVC-J5QV2XWG4FEBPH3Q
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-EBCJC5WP2H42KJXA
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -j KUBE-SEP-BGGUKAMS6QUZEUGX
# 5、查看KUBE-SEP-EBCJC5WP2H42KJXA鏈及KUBE-SEP-BGGUKAMS6QUZEUGX
[root@k8s-master1 ~]# iptables -t nat -S | grep KUBE-SEP-EBCJC5WP2H42KJXA
-N KUBE-SEP-EBCJC5WP2H42KJXA
-A KUBE-SEP-EBCJC5WP2H42KJXA -s 10.244.36.114/32 -m comment --comment "default/my-nginx-nodeport" -j KUBE-MARK-MASQ
-A KUBE-SEP-EBCJC5WP2H42KJXA -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp -j DNAT --to-destination 10.244.36.114:80
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-EBCJC5WP2H42KJXA
[root@k8s-master1 ~]# iptables -t nat -S | grep KUBE-SEP-BGGUKAMS6QUZEUGX
-N KUBE-SEP-BGGUKAMS6QUZEUGX
-A KUBE-SEP-BGGUKAMS6QUZEUGX -s 10.244.36.115/32 -m comment --comment "default/my-nginx-nodeport" -j KUBE-MARK-MASQ
-A KUBE-SEP-BGGUKAMS6QUZEUGX -p tcp -m comment --comment "default/my-nginx-nodeport" -m tcp -j DNAT --to-destination 10.244.36.115:80
-A KUBE-SVC-J5QV2XWG4FEBPH3Q -m comment --comment "default/my-nginx-nodeport" -j KUBE-SEP-BGGUKAMS6QUZEUGX
四、Service服務發現:coredns組件
CoreDNS 其實就是一個 DNS 服務,而 DNS 作為一種常見的服務發現手段,所以很多開源項目以及工程師都會使用 CoreDNS 為集群提供服務發現的功能,Kubernetes 就在集群中使用 CoreDNS 解決服務發現的問題。 作為一個加入 CNCF(Cloud Native Computing Foundation)的服務, CoreDNS 的實現非常簡單。
[root@k8s-master1 ~]# cat dig.yaml
apiVersion: v1
kind: Pod
metadata:
name: dig
namespace: default
spec:
containers:
- name: dig
image: xianchao/dig:latest
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always
# 更新資源清單文件
[root@k8s-master1 ~]# kubectl apply -f dig.yaml
pod/dig configured
# 查看默認名稱空間的kubernetes服務
[root@k8s-master1 ~]# kubectl get svc | grep kubernetes
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 43h
[root@k8s-master1 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
dig 1/1 Running 0 2m5s 10.244.36.116 k8s-node1 <none> <none>
# 解析dns,如有以下返回說明dns安裝成功
[root@k8s-master1 ~]# kubectl exec -it dig -- nslookup kubernetes
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: kubernetes.default.svc.cluster.local
Address: 10.96.0.1
[root@k8s-master1 ~]# kubectl exec -it dig -- cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
