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 {
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