kube-proxy负责为Service提供cluster内部的服务发现和负载均衡,它运行在每个Node计算节点上,负责Pod网络代理, 它会定时从etcd服务获取到service信息来做相应的策略,维护网络规则和四层负载均衡工作。在K8s集群中微服务的负载均衡是由Kube-proxy实现的,它是K8s集群内部的负载均衡器,也是一个分布式代理服务器,在K8s的每个节点上都有一个,这一设计体现了它的伸缩性优势,需要访问服务的节点越多,提供负载均衡能力的Kube-proxy就越多,高可用节点也随之增多。
service是一组pod的服务抽象,相当于一组pod的负载均衡,负责将请求分发给对应的pod。service会为这个负载均衡提供一个IP,一般称为cluster IP。kube-proxy的作用主要是负责service的实现,具体来说,就是实现了内部从pod到service和外部的从node port向service的访问。
Kubernetes Service定义了这样一种抽象: Service是一种可以访问 Pod逻辑分组的策略, Service通常是通过 Label Selector访问 Pod组。
Service能够提供负载均衡的能力,但是在使用上有以下限制:只提供 4 层负载均衡能力,而没有 7 层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的。
2.1.1、ClusterIp(集群内部使用)
默认类型,自动分配一个仅Cluster内部可以访问的虚拟IP(VIP)。
2.1.2、NodePort(对外暴露应用)
在ClusterIP基础上为Service在每台机器上绑定一个端口,这样就可以通过NodeIP:NodePort访问来访问该服务。
端口范围:30000~32767
2.1.3、LoadBalancer(对外暴露应用,适用于公有云)
在NodePort的基础上,借助Cloud Provider创建一个外部负载均衡器,并将请求转发到NodePort。
2.1.4、ExternalName
创建一个dns别名指到service name上,主要是防止service name发生变化,要配合dns插件使用。通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容。这只有 Kubernetes 1.7或更高版本的kube-dns才支持。
客户端访问节点时通过 iptables实现的
iptables规则是通过 kube-proxy写入的
apiserver通过监控 kube-proxy去进行对服务和端点的监控
kube-proxy通过 pod的标签( lables)去判断这个断点信息是否写入到 Endpoints里
endpoint是k8s集群中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址。service配置selector,endpoint controller才会自动创建对应的endpoint对象;否则,不会生成endpoint对象。
一个 Service 由一组 backend Pod 组成。这些 Pod 通过 endpoints 暴露出来。 Service Selector 将持续评估,结果被 POST 到一个名称为 Service-hello 的 Endpoint 对象上。 当 Pod 终止后,它会自动从 Endpoint 中移除,新的能够匹配上 Service Selector 的 Pod 将自动地被添加到 Endpoint 中。 检查该 Endpoint,注意到 IP 地址与创建的 Pod 是相同的。现在,能够从集群中任意节点上使用 curl 命令请求 hello Service
: 。
【例如】k8s集群中创建一个名为hello的service,就会生成一个同名的endpoint对象,ENDPOINTS就是service关联的pod的ip地址和端口。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 3
selector:
matchLabels:
run: hello
template:
metadata:
labels:
run: hello
spec:
containers:
- name: nginx
image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
name: service-hello
labels:
name: service-hello
spec:
type: NodePort # 这里代表是NodePort类型的,另外还有ingress,LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
nodePort: 31111 # 所有的节点都会开放此端口30000--32767,此端口供外部调用。
selector:
run: hello
3.2.1、查看验证
Kube-proxy进程获取每个Service的Endpoints,实现Service的负载均衡功能。
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hello-688d6b8d4c-bzfj9 1/1 Running 0 21m 10.254.1.2 node01
hello-688d6b8d4c-nwgd6 1/1 Running 0 21m 10.254.2.2 node02
hello-688d6b8d4c-s5ff8 1/1 Running 0 21m 10.254.3.2 harbor
[root@master ~]# kubectl get service service-hello -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service-hello NodePort 10.244.143.216 80:31111/TCP 18h run=hello
[root@master ~]# kubectl describe service service-hello
Name: service-hello
Namespace: default
Labels:
Annotations:
Selector: run=hello
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.244.143.216
IPs: 10.244.143.216
Port: 80/TCP
TargetPort: 8080/TCP
NodePort: 31111/TCP
Endpoints: 10.254.1.6:8080,10.254.2.3:8080,10.254.3.3:8080
Session Affinity: None
External Traffic Policy: Cluster
Events:
[root@master ~]# kubectl get endpoints service-hello
NAME ENDPOINTS AGE
service-hello 10.254.1.6:8080,10.254.2.3:8080,10.254.3.3:8080 18h
Service的负载均衡转发规则:访问Service的请求,不论是Cluster IP+TargetPort的方式;还是用Node节点IP+NodePort的方式,都被Node节点的Iptables规则重定向到Kube-proxy监听Service服务代理端口。kube-proxy接收到Service的访问请求后,根据负载策略,转发到后端的Pod。
- nodePort是外部访问k8s集群中service的端口,通过nodeIP: nodePort可以从外部访问到某个service。
- port是k8s集群内部访问service的端口,即通过clusterIP: port可以访问到某个service。
- targetPort是pod的端口,从port和nodePort来的流量经过kube-proxy流入到后端pod的targetPort上,最后进入容器。
- containerPort是pod内部容器的端口,targetPort映射到containerPort。
Kubernetes提供了两种方式进行服务发现, 即环境变量和DNS, 简单说明如下:
环境变量:当你创建一个Pod的时候,kubelet会在该Pod中注入集群内所有Service的相关环境变量。【注意】要想一个Pod中注入某个Service的环境变量,则必须Service要比该Pod先创建。这一点,几乎使得这种方式进行服务发现不可用。
DNS:这是k8s官方强烈推荐的方式!!! 可以通过cluster add-on方式轻松的创建KubeDNS来对集群内的Service进行服务发现。
这种模式,kube-proxy 会监视 Kubernetes 控制平面对 Service 对象和 Endpoints 对象的添加和移除操作。 对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。 任何连接到“代理端口”的请求,都会被代理到 Service 的后端 Pods
中的某个上面(如 Endpoints
所报告的一样)。 使用哪个后端 Pod,是 kube-proxy 基于 SessionAffinity
来确定的。
最后,它配置 iptables 规则,捕获到达该 Service 的 clusterIP
(是虚拟 IP) 和 Port
的请求,并重定向到代理端口,代理端口再代理请求到后端Pod。
默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端。
这种模式,kube-proxy
会监视 Kubernetes 控制节点对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会配置 iptables 规则,从而捕获到达该 Service 的 clusterIP
和端口的请求,进而将请求重定向到 Service 的一组后端中的某个 Pod 上面。 对于每个 Endpoints 对象,它也会配置 iptables 规则,这个规则会选择一个后端组合。
默认的策略是,kube-proxy 在 iptables 模式下随机选择一个后端。
使用 iptables 处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理, 而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。
如果 kube-proxy 在 iptables 模式下运行,并且所选的第一个 Pod 没有响应, 则连接失败。 这与用户空间模式不同:在这种情况下,kube-proxy 将检测到与第一个 Pod 的连接已失败, 并会自动使用其他后端 Pod 重试。
你可以使用 Pod 就绪探测器 验证后端 Pod 可以正常工作,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端。 这样做意味着你避免将流量通过 kube-proxy 发送到已知已失败的 Pod。
[root@master ~]# vim mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
name: mysql
role: service
name: mysql-service
spec:
ports:
- port: 3306
targetPort: 3306
nodePort: 30964
type: NodePort
selector:
mysql-service: "true"
name: mysql
[root@master ~]# kubectl apply -f mysql-service.yaml
service/mysql-service created
[root@master ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.244.0.1 443/TCP 29h
mysql-service NodePort 10.244.140.208 3306:30964/TCP 19s
service-hello NodePort 10.244.143.216 80:31111/TCP 20h
特性状态: Kubernetes v1.11 [stable]
在 ipvs
模式下,kube-proxy 监视 Kubernetes 服务和端点,调用 netlink
接口相应地创建 IPVS 规则, 并定期将 IPVS 规则与 Kubernetes 服务和端点同步。 该控制循环可确保IPVS 状态与所需状态匹配。访问服务时,IPVS 将流量定向到后端Pod之一。
IPVS代理模式基于类似于 iptables 模式的 netfilter 挂钩函数, 但是使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。 与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
IPVS 提供了更多选项来平衡后端 Pod 的流量。 这些是:
rr
:轮替(Round-Robin)lc
:最少链接(Least Connection),即打开链接数量最少者优先dh
:目标地址哈希(Destination Hashing)sh
:源地址哈希(Source Hashing)sed
:最短预期延迟(Shortest Expected Delay)nq
:从不排队(Never Queue)5.3.1、kube-proxy配置 ipvs模式(所有节点)
1)加载ip_vs相关内核模块
[root@master ~]# modprobe -- ip_vs
[root@master ~]# modprobe -- ip_vs_sh
[root@master ~]# modprobe -- ip_vs_rr
[root@master ~]# modprobe -- ip_vs_wrr
[root@master ~]# modprobe -- nf_conntrack_ipv4
[root@master ~]# lsmod | grep ip_vs
ip_vs_sh 12688 0
ip_vs_wrr 12697 0
ip_vs_rr 12600 0
ip_vs 141432 6 ip_vs_rr,ip_vs_sh,ip_vs_wrr
nf_conntrack 133053 10 ip_vs,nf_nat,nf_nat_ipv4,nf_nat_ipv6,xt_conntrack,nf_nat_masquerade_ipv4,nf_nat_masquerade_ipv6,nf_conntrack_netlink,nf_conntrack_ipv4,nf_conntrack_ipv6
libcrc32c 12644 4 xfs,ip_vs,nf_nat,nf_conntrack
2)安装ipvsadm工具
[root@master ~]# yum -y install ipset ipvsadm
3)查看kube-proxy
[root@master ~]# kubectl get pod -n kube-system | grep kube-proxy
kube-proxy-2f9x9 1/1 Running 2 (18h ago) 29h
kube-proxy-8xjdp 1/1 Running 2 (18h ago) 29h
kube-proxy-f425c 1/1 Running 2 (18h ago) 29h
kube-proxy-t4pq8 1/1 Running 2 (18h ago) 29h
3)编辑kube-proxy配置文件,mode修改成ipvs
[root@master ~]# kubectl edit configmaps -n kube-system kube-proxy
mode: "ipvs"
[root@master ~]# kubectl get pod -n kube-system | grep kube-proxy |awk '{system("kubectl delete pod "$1" -n kube-system")}'
pod "kube-proxy-2f9x9" deleted
pod "kube-proxy-8xjdp" deleted
pod "kube-proxy-f425c" deleted
pod "kube-proxy-t4pq8" deleted
[root@master ~]# kubectl get pod -n kube-system | grep kube-proxy # 再次查看
kube-proxy-5p5tb 1/1 Running 0 10s
kube-proxy-c2vxk 1/1 Running 0 13s
kube-proxy-z2hjm 1/1 Running 0 12s
kube-proxy-zgjc7 1/1 Running 0 9s
[root@master ~]# ipvsadm -Ln
说明:要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前使 IPVS 在节点上可用。当 kube-proxy 以 IPVS 代理模式启动时,它将验证 IPVS 内核模块是否可用。 如果未检测到 IPVS 内核模块,则 kube-proxy 将退回到以 iptables 代理模式运行。
从数据层面看状态,数据的状态往往受2个维度有关,一是与时间相关或者顺序相关的,不同的操作顺序可能导致同一个时间点上的数据状态大于1个,二是与数据的副本状态相关的。也就是数据的位置,数据落在多个副本上,可能出现多种数据状态的组合。
从服务层面看,服务层面的状态取决于实例是单独维护数据还是共享数据,或者说是否存在多个数据闭环让数据的流向产生了多条路径。有状态的服务往往比较难进行水平拓展,在现在容器盛行的环境,把服务设计成无状态的更加高效,即便是有状态的服务,也要将状态内敛在系统的某个范围,比如分布式的存储,对于业务服务,我不需要关心数据在多个副本的状态,数据的状态由分布式存储这个服务本身解决。
差异维度 | 有状态服务 | 无状态服务 |
服务本身 | 服务本身依赖或者存在局部的状态数据,这些数据需要自身持久化或者可以通过其他节点恢复。 | 服务不依赖自身的状态,实例的状态数据可以维护在内存中。 |
节 点 | 一个请求只能被某个节点(或者同等状态下的节点)处理。 | 任何一个请求都可以被任意一个实例处理。 |
数据状态 | 存储状态数据,实例的拓展需要整个系统参与状态的迁移。 | 不存储状态数据,实例可以水平拓展,通过负载均衡将请求分发到各个节点。 |
系统中 | 在一个封闭的系统中,存在多个数据闭环,需要考虑这些闭环的数据一致性问题。 | 在一个封闭的系统中,只存在一个数据闭环。 |
架构中 | 通常存在于分布式架构中。 | 通常存在于单体架构的集群中。 |
相关资源 | statefulSet,由于是有状态的服务,所以每个pod都有特定的名称和网络标识。比如pod名是由statefulSet名+有序的数字组成(0、1、2..) |
ReplicaSet、ReplicationController、Deployment等,由于是无状态服务,所以这些控制器创建的pod序号都是随机值。并且在缩容的时候并不会明确缩容某一个pod,而是随机的,因为所有实例得到的返回值都是一样,所以缩容任何一个pod都可以。 |
相关服务 | 有状态服务 可以说是 需要数据存储功能的服务、或者指多线程类型的服务,队列等。(mysql数据库、kafka、zookeeper等) | 多个实例可以共享相同的持久化数据。例如:nginx实例,tomcat实例等 |