k8s-service
说明
服务内部的访问
解决ip不固定的问题,当Pod宕机后重新生成时,其IP等状态信息可能会变动,Service会根据Pod的Label对这些状态信息进行监控和变更,保证上游服务不受Pod的变动而影响
kube-proxy
Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个Node节点上都运行着一个kube-proxy服务进程。当创建Service的时候会通过api-server向etcd写入创建的service的信息,
而kube-proxy会基于监听的机制发现这种Service的变动,然后它会将最新的Service信息转换成对应的访问规则。
kube-proxy支持的工作模式
user space
userspace模式下,kube-proxy会为每一个Service创建一个监听端口,发向Cluster IP的请求被Iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法选择一个提供服务的Pod并和其建立链接,以将请求转发到Pod上。
该模式下,kube-proxy充当了一个四层负责均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理时会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率比较低。
clusterip重定向到kube-proxy服务的过程存在内核态到用户态的切换,开销很大,因此有了iptables模式,而userspace模式也被废弃了。
工作步骤:
- kube-proxy为每个service在node上打开一个随机端口作为代理端口
- 建立iptables规则,将clusterip的请求重定向到代理端口(用户空间)
- 到达代理端口的请求再由kubeproxy转发到后端
iptables
kubernets从1.2版本开始将iptabels模式作为默认模式,这种模式下kube-proxy不再起到proxy的作用。其核心功能:通过API Server的Watch接口实时跟踪Service和Endpoint的变更信息,并更新对应的iptables规则,Client的请求流量通过iptables的NAT机制“直接路由”到目标Pod。
对于每一个Service,Kube-proxy创建相应的IPtables规则,并将发送到Service cluseter IP的流量转发到Service后端提供服务的Pod的相应端口上。并且流量的转发都是在内核态,所以性能高。
在这种模式下缺点是在大规模集群中,iptables添加规则会很大的延迟,因为使用iptables,每增加一个Service都会增加一个iptables的chain。并且iptables修改了规则后必须全部刷新才可以生效。
ipvs
从kubernetes 1.8版本开始引入第三代的IPVS模式,它也是基于netfilter实现的,但定位不同:iptables是为防火墙设计的,IPVS则专门用于高性能
负载均衡,并使用高效的数据结构Hash表,允许几乎无限的规模扩张。
一句话说明:ipvs使用ipset存储iptables规则,在查找时类似hash表查找,时间复杂度为O(1),而iptables时间复杂度则为O(n)。
。除此以外,ipvs支持更多的LB算法。
Endpoint
Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址,它是根据service配置文件中selector描述产生的。
一个Service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合。换句话说,service和pod之间的联系是通过endpoints实现的。
负载分发策略
对Service的访问被分发到了后端的Pod上去,目前kubernetes提供了两种负载分发策略:
如果不定义,默认使用kube-proxy的策略,比如随机、轮询
基于客户端地址的会话保持模式,即来自同一个客户端发起的所有请求都会转发到固定的一个Pod上
此模式可以使在spec中添加sessionAffinity:ClientIP选项
# 查看ipvs的映射规则【rr 轮询】 [root@master ~]# ipvsadm -Ln TCP 10.97.97.97:80 rr -> 10.244.1.39:80 Masq 1 0 0 -> 10.244.1.40:80 Masq 1 0 0 -> 10.244.2.33:80 Masq 1 0 0 # 循环访问测试 [root@master ~]# while true;do curl 10.97.97.97:80; sleep 5; done; 10.244.1.40 10.244.1.39 10.244.2.33 10.244.1.40 10.244.1.39 10.244.2.33 # 修改分发策略----sessionAffinity:ClientIP # 查看ipvs规则【persistent 代表持久】 [root@master ~]# ipvsadm -Ln TCP 10.97.97.97:80 rr persistent 10800 -> 10.244.1.39:80 Masq 1 0 0 -> 10.244.1.40:80 Masq 1 0 0 -> 10.244.2.33:80 Masq 1 0 0 # 循环访问测试 [root@master ~]# while true;do curl 10.97.97.97; sleep 5; done; 10.244.2.33 10.244.2.33 10.244.2.33 # 删除service [root@master ~]# kubectl delete -f service-clusterip.yaml service "service-clusterip" deleted
几种类型的service
ClusterIP类型的Service
默认类型,Service将会被分配一个Cluster IP,只在集群内部可用。比如redis mq等ip访问
创建service-clusterip.yaml文件
apiVersion: v1 kind: Service metadata: name: service-clusterip namespace: dev spec: selector: app: nginx-pod clusterIP: 10.97.97.97 # service的ip地址,如果不写,默认会生成一个 一般不指定 type: ClusterIP ports: - port: 80 # Service端口 targetPort: 80 # pod端口
测试
# 创建service [root@master ~]# kubectl create -f service-clusterip.yaml service/service-clusterip created # 查看service [root@master ~]# kubectl get svc -n dev -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR service-clusterip ClusterIP 10.97.97.97 <none> 80/TCP 13s app=nginx-pod # 查看service的详细信息 # 在这里有一个Endpoints列表,里面就是当前service可以负载到的服务入口 [root@master ~]# kubectl describe svc service-clusterip -n dev Name: service-clusterip Namespace: dev Labels: <none> Annotations: <none> Selector: app=nginx-pod Type: ClusterIP IP: 10.97.97.97 Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: 10.244.1.39:80,10.244.1.40:80,10.244.2.33:80 Session Affinity: None Events: <none> # 查看ipvs的映射规则 [root@master ~]# ipvsadm -Ln TCP 10.97.97.97:80 rr -> 10.244.1.39:80 Masq 1 0 0 -> 10.244.1.40:80 Masq 1 0 0 -> 10.244.2.33:80 Masq 1 0 0 # 访问10.97.97.97:80观察效果 [root@master ~]# curl 10.97.97.97:80 10.244.2.33
HeadLiness类型的Service
在某些场景中,开发人员可能不想使用Service提供的负载均衡功能,而希望自己来控制负载均衡策略,针对这种情况,kubernetes提供了HeadLiness Service,这类Service不会分配Cluster IP,如果想要访问service,只能通过service的域名进行查询。
service定义
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 targetPort: 80
# 创建service [root@master ~]# kubectl create -f service-headliness.yaml service/service-headliness created # 获取service, 发现CLUSTER-IP未分配 [root@master ~]# kubectl get svc service-headliness -n dev -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR service-headliness ClusterIP None <none> 80/TCP 11s app=nginx-pod # 查看service详情 [root@master ~]# kubectl describe svc service-headliness -n dev Name: service-headliness Namespace: dev Labels: <none> Annotations: <none> Selector: app=nginx-pod Type: ClusterIP IP: None Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: 10.244.1.39:80,10.244.1.40:80,10.244.2.33:80 Session Affinity: None Events: <none> # 查看域名的解析情况 [root@master ~]# kubectl exec -it pc-deployment-66cb59b984-8p84h -n dev /bin/sh / # cat /etc/resolv.conf nameserver 10.96.0.10 search dev.svc.cluster.local svc.cluster.local cluster.local [root@master ~]# dig @10.96.0.10 service-headliness.dev.svc.cluster.local service-headliness.dev.svc.cluster.local. 30 IN A 10.244.1.40 service-headliness.dev.svc.cluster.local. 30 IN A 10.244.1.39 service-headliness.dev.svc.cluster.local. 30 IN A 10.244.2.33
NodePort类型的Service
创建的Service的ip地址只有集群内部才可以访问,如果希望将Service暴露给集群外部使用,那么就要使用到另外一种类型的Service,称为NodePort类型。NodePort的工作原理其实就是将service的端口映射到Node的一个端口上(在每个Node上都会公开一个固定的端口,通过该端口可以访问Service。在Node的IP地址上可以通过该端口直接访问Service),然后就可以通过NodeIp:NodePort来访问service了
定义
比如测试的时候本机想访问测试环境redis
apiVersion: v1 kind: Service metadata: name: service-nodeport namespace: dev spec: selector: app: nginx-pod type: NodePort # service类型 ports: - port: 80 nodePort: 30002 # 指定绑定的node的端口(默认的取值范围是:30000-32767), 如果不指定,会默认分配 targetPort: 80
# 创建service [root@master ~]# kubectl create -f service-nodeport.yaml service/service-nodeport created # 查看service [root@master ~]# kubectl get svc -n dev -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) SELECTOR service-nodeport NodePort 10.105.64.191 <none> 80:30002/TCP app=nginx-pod # 接下来可以通过电脑主机的浏览器去访问集群中任意一个nodeip的30002端口,即可访问到pod
LoadBalancer类型的Service
LoadBalancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境支持的,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。
LoadBalancer 类型的 Service 在 Kubernetes 中主要用于将服务公开到云平台的外部,并通过云提供商的负载均衡器来处理流量。这个类型的 Service 通常在云环境中使用,例如 AWS、GCP、Azure 等,因为这些云提供商可以提供负载均衡器服务。
使用 LoadBalancer 类型的 Service 时,确保你的云平台支持该功能,并且你有相应的权限。不同的云提供商可能有一些细微的差异,因此需要查阅相应的文档以确保正确配置。
apiVersion: v1 kind: Service metadata: name: my-loadbalancer-service spec: selector: app: my-app ports: - protocol: TCP port: 80 targetPort: 8080 type: LoadBalancer
ExternalName类型的Service
ExternalName类型的Service用于引入集群外部的服务,它通过externalName
属性指定外部一个服务的地址,然后在集群内部访问此service就可以访问到外部的服务了。
yml定义
apiVersion: v1 kind: Service metadata: name: service-externalname namespace: dev spec: type: ExternalName # service类型 externalName: www.baidu.com #改成ip地址也可以
# 创建service [root@master ~]# kubectl create -f service-externalname.yaml service/service-externalname created # 域名解析 [root@master ~]# dig @10.96.0.10 service-externalname.dev.svc.cluster.local service-externalname.dev.svc.cluster.local. 30 IN CNAME www.baidu.com. www.baidu.com. 30 IN CNAME www.a.shifen.com. www.a.shifen.com. 30 IN A 39.156.66.18 www.a.shifen.com. 30 IN A 39.156.66.14
service如何访问到pod
1.访问到service(创建service如果指定了selector会自动创建对应的endpoint)
2.找到对应的endpoint
3.通过iptables找到路由到对应节点的kube-proxy
4.通过kube-proxy路由到对应的pod
如何定义一个sevice
apiVersion: v1 kind: Service metadata: name: "order" namespace: "test" spec: selector: app: "order" type: ClusterIP ports: - name: order-http port: 8000 targetPort: 8000 - name: order-grpc port: 8001 targetPort: 8001
apiVersion: v1 kind: Service metadata: name: my-service # 服务的名称 spec: selector: app: my-app # 选择器,指定服务所匹配的Pod ports: - protocol: TCP # 服务使用的协议 port: 80 # 服务暴露的端口 targetPort: 8080 # 服务转发到Pod的端口 type: NodePort # 服务的类型,可以是NodePort、ClusterIP、LoadBalancer等 # 以下是可选的配置 # loadBalancerIP: 192.168.1.100 # 如果服务类型为LoadBalancer,可以指定负载均衡器的IP # externalTrafficPolicy: Local # 控制流量的策略,可以是Local或者Cluster
相关命令
根据yml创建service
kubectl apply -f service.yaml
查看所有service: kubectl get services 查看特定service的详细信息: kubectl describe service <service-name> 删除一个service: kubectl delete service <service-name> 编辑一个service: kubectl edit service <service-name> 替换一个service,通过新的YAML文件: kubectl replace -f <new-service-definition.yaml>
如何跨命名空间访问
wget http://nginx-svc.{namespace}
如果自定义endpint
应用场景,转发请求到外部、比如迁移应用到k8s 一部分应用是k8s 一部分非k8s
apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: my-app ports: - protocol: TCP port: 80 targetPort: 8080
apiVersion: v1 kind: Endpoints metadata: name: my-service 与service一致 subsets: - addresses: - ip: 192.168.1.1 # 转发的地址 ports: - port: 8080 # 替换为你的Pod的端口
kubectl apply -f service.yaml
kubectl apply -f endpoints.yaml
service如何代理到域名
apiVersion: v1 kind: Service metadata: name: my-external-service spec: type: ExternalName externalName: example.com # 将此处替换为你的域名 ports: - port: 80
属性介绍
type
ClusterIp 集群内部使用 转发ip(默认)
ExternalName 域名方式
NodePort
LoadBalancer