k8s中pod是有生命周期的,如果podIP发生变化,跟他向关联的服务就会找不到pod。
service就是为了解决这个问题,每个service和一个或一组pod绑定,可以理解为 service是pod的代理,service中记录着pod的IP,pod发生变化会通知service,我们访问service它会把请求转发给pod
service IP是逻辑存在的记录在iptables或ipvs中。
网络简述
每一个pod会生成一个根容器,根容器创建网络命名空间,在调用cni配置网络接口,使用cni扩展插件calico或flannel为容器分配IP 完成网络通讯
flannel: vxlan 等技术实现二层通讯。
通信流程:
- pod-a访问pod-b 因为两者IP不在同一个子网,首先数据会先到默认网关也就是cni010.64.0.1
- 节点上面的静态路由可以看出来10.64.1.0/24 是指向flannel.1的
- flannel.1 会使用vxlan协议把原始IP包加上目的mac地址封装成二层数据帧
- 原始vxlan数据帧无法在物理二层网络中通信,flannel.1 (linux 内核支持vxlan,此步骤由内核完成)会把数据帧封装成UDP报文经过物理网络发送到node-02
- node-02收到UDP报文中带有vxlan头信息,会转交给flannel.1解封装得到原始数据
- flannel.1 根据直连路由转发到cni0 上面
- cni0 转发给pod-2
calico: 三层路由网络,支持复杂的网络策略,每一个节点类似一个路由
k8s集群中有三类IP
Node NetWork(节点网络)
Pod NetWork(pod网络) 这两种网络是真是存在的
Cluster NetWork(集群网络) 存在于逻辑上的地址,只出现在Service的规则中。
Service 资源清单:
kubectl explain service
apiVersion: v1
kind: Service
metadata:
# svc 名称
name: my-service
spec:
# 通过选择器,绑定后端pod
selector:
dep: app1-pod
ports:
# svc port
- port: 80
# 绑定的pod端口
targetPort: 80
其他常用字段:
allocateLoadBalancerNodePorts <boolean>
clusterIP <string>
#动态分配的地址,也可以自己在创建的时候指定,创建之后就改不了了
clusterIPs <[]string>
externalIPs <[]string>
externalName <string>
externalTrafficPolicy <string>
healthCheckNodePort <integer>
ipFamilies <[]string>
ipFamilyPolicy <string>
loadBalancerIP <string>
loadBalancerSourceRanges <[]string>
publishNotReadyAddresses <boolean>
#通过标签选择器选择关联的pod有哪些
sessionAffinity <string>
sessionAffinityConfig <Object>
#service在实现负载均衡的时候还支持sessionAffinity,sessionAffinity
什么意思?会话联系,默认是none,随机调度的(基于iptables规则调度的);如果我们定义sessionAffinity的client ip,那就表示把来自同一客户端的IP请求调度到同一个pod上
topologyKeys <[]string>
#定义service的类型
service的四种类型
ExternalName:适用于集群中不同命名空间中的访问。
示例:
apiVersion: v1
kind: Service
metadata:
name: my-exter
namespace: dev
spec:
type: ExternalName
# 其他命名空间中FQDN(完全合格域名)
externalName: my-service.default.svc.cluster.local
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod5
namespace: dev
spec:
containers:
- name: bixbuy
# 最新版镜像与k8s不兼容会出现解析失败情况
image: busybox:1.28.4
command: ["/bin/sh","-c","sleep 36000"]
解析命令 nslookup svc名
ClusterIP:通过k8s集群内部IP暴露服务,选择该值,服务只能够在集群内部访问(默认)
NodePort:通过每个Node节点上的IP和静态端口暴露k8s集群内部的服务
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
dep: app1-pod
ports:
- port: 80
# 外部端口,创建集群时的端口范围
nodePort: 30078
targetPort: 80
# 类型
type: NodePort
LoadBalancer:使用云提供商的负载均衡器,可以向外部暴露服务。
service只要创建完成,我们就可以直接解析它的服务名,每一个服务创建完成后都会在集群dns中动态添加一个资源记录,添加完成后我们就可以解析了,资源记录格式是:
SVC_NAME.NS_NAME.DOMAIN.LTD.
服务名.命名空间.域名后缀
集群默认的域名后缀是svc.cluster.local.
映射外部服务示例:
创建svc时会创建个同名的Endpoints完成绑定操作,通过操作Endpoints实现绑定外部服务
apiVersion: v1
kind: Service
metadata:
name: mysql-svc
spec:
# 定义svc,只指定开放的端口
ports:
- port: 3306
此时查看详情发现没有绑定Endpoints
创建Endpoints
apiVersion: v1
kind: Endpoints
metadata:
name: mysql-svc
subsets:
- addresses:
- ip: 192.168.1.23
ports:
- port: 3306
kube-proxy
service 只是把应用对外提供服务的方式做了抽象,应用是跑在容器中,我们的请求是需要发送到个个node上的。kube-proxy部署在k8s的没一个节点上,是k8s的核心组件,我们创建一个service时会在iptables上追加一些规则,实现路由及负载均衡等功能。k8s 1.8之前默认使用iptables模式,但随着service越多,规则链的数量越多,其性能会明显下降。之后引入了ipvs规则来采用hash表。加快查询速度。小集群还是选择iptables
一组pod通过标签绑定到一个svc(Cluster IP), 这个 IP 是个虚拟IP 由iptables管理通过Endpoints实现的,集群内通过访问这个Cluster IP:Port就能访问到集群内对应的serivce下的Pod.
三种工作方式:
Userspace方式
client 请求发送到 server iptables, iptables转发请求给kube-proxy接口,kube-proxy处理完毕把请求发送给指定的pod,再把请求发送给iptabels 由iptabels在转发给各个节点的pod
iptables方式
client 直接请求本地server Ip 根据iptables 分发到个个节点的pod(nat转发)
ipvs方式
与iptables类似,ipvs为负载均衡算法提供了更多选项,例如:
rr:轮询调度
lc:最小连接数
dh:目标哈希
sh:源哈希
sed:最短期望延迟
nq:不排队调度
以上不论哪种,kube-proxy都通过watch的方式监控着apiserver写入etcd中关于Pod的最新状态信息,它一旦检查到一个Pod资源被删除了或新建了,它将立即将这些变化,反应再iptables 或 ipvs规则中,以便iptables和ipvs在调度Clinet Pod请求到Server Pod时,不会出现Server Pod不存在的情况。自k8s1.11以后,service默认使用ipvs规则,若ipvs没有被激活,则降级使用iptables规则.