什么是Service (服务)
Service是Kubernetes里最核心的资源对象之一,Service定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实力。 Service与其后端Pod副本集群之间则是通过Label Selector
来实现"无缝对接"。而RC的作用实际上是保证Service 的服务能力和服务质量处于预期的标准。
通过分析、识别并建模系统中所有服务为微服务---Kubernetes Service,最终我们的系统由多个提供不同业务能力而又彼此独立的微服务单元组成,服务之间通过TCP/IP
进行通信,从而拥有了强大的分布式能力、弹性扩展能力、容错能力
既然每个Pod都会被分配一个单独的IP地址,而且每个Pod都提供了一个独立的Endpoint
(Pod IP+ContainerPort)以被客户端访问,现在多个Pod副本组成了一个集群来提供访问。
Kubernetes 需要在每个Node上安装kube-proxy
,kube-proxy
进程其实就是一个智能的软件负载均衡器,它负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。
Kubernetes发明了一个很巧明的设计,Service不是共用一个负载均衡器的IP地址,而是每个Service分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP
。这样每个服务就变成了具备唯一IP地址的"通信节点",服务调用就变成了最基础的TCP网络通信问题
Pod的Endpoint
地址会随着Pod的销毁和重新创建而发生改变,因为新的Pod地址与之前的旧的Pod不同。而Service一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个声明周期内,它的Cluster IP不会发生改变。所以只要将Service的name与Service的Cluster IP地址做一个DNS域名映射即可解决问题。
Service的三种工作模式
- userspace: k8s 1.1之前;
这种模式,当客户端Pod请求内核空间的service iptables后,把请求转到给用户空间监听的kube-proxy 的端口,由kube-proxy来处理后,再由kube-proxy将请求转给内核空间的 service ip,再由service iptalbes根据请求转给各节点中的的service pod。
由此可见这个模式有很大的问题,由客户端请求先进入内核空间的,又进去用户空间访问kube-proxy,由kube-proxy封装完成后再进去内核空间的iptables,再根据iptables的规则分发给各节点的用户空间的pod。这样流量从用户空间进出内核带来的性能损耗是不可接受的。
- iptables : k8s 1.10之前;
客户端IP请求时,直接请求本地内核service ip,根据iptables的规则直接将请求转发到到各pod上,因为使用iptable NAT来完成转发,也存在不可忽视的性能损耗。另外,如果集群中存在上万的Service/Endpoint,那么Node上的iptables rules将会非常庞大,性能还会再打折扣。
- ipvs : k8s 1.11+
客户端IP请求时到达内核空间时,根据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
:不排队调度
注意: ipvs模式假定在运行kube-proxy之前在节点上都已经安装了IPVS内核模块。当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装,则kube-proxy将回退到iptables代理模式。
如果某个服务后端pod发生变化,标签选择器适应的pod有多一个,适应的信息会立即反映到apiserver上,而kube-proxy一定可以watch到etc中的信息变化,而将它立即转为ipvs或者iptables中的规则,这一切都是动态和实时的,删除一个pod也是同样的原理。如图:
Service的定义
(1)清单创建Service
apiVersion:
kind:
metadata:
spec:
clusterIP: 可以自定义,也可以动态分配
ports:(与后端容器端口关联)
selector:(关联到哪些pod资源上)
type:服务类型
(2)service的类型
对一些应用(如 Frontend)的某些部分,可能希望通过外部(Kubernetes 集群外部)IP 地址暴露 Service。
Kubernetes ServiceTypes
允许指定一个需要的类型的 Service,默认是 ClusterIP
类型。
Type
的取值以及行为如下:
ClusterIP
:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的ServiceType
。NodePort
:通过每个 Node 上的 IP 和静态端口(NodePort
)暴露服务。NodePort
服务会路由到ClusterIP
服务,这个ClusterIP
服务会自动创建。通过请求<NodeIP>:<NodePort>
,可以从集群的外部访问一个NodePort
服务。LoadBalancer
:使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到NodePort
服务和ClusterIP
服务。ExternalName
:通过返回CNAME
和它的值,可以将服务映射到externalName
字段的内容(例如,foo.bar.example.com
)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的kube-dns
才支持。
Service的会话粘性:
- Service的spec下还有一个
sessionAffinity
字段,就是用来设定会话粘性的。此字段有ClientIP
和None
两个值可选。默认是None
,表示不具有会话粘性。ClientIP
表示同一个源IP的访问请求都调度到同一个后端Pod上去(类似于Nginx的ip_hash负载调度算法)。
Service资源清单示例:
[root@s1 ~]# cat redis-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: default
spec:
clusterIP: 10.106.106.106 # clusterIP就是给service分配的IP,如果不定这个字段,K8S会自动给service分配一个IP。
selector: #标签选择器,必须指定pod资源本身的标签
app: redis
role: logstor
type: ClusterIP #指定服务类型为ClusterIP
ports:
- name: redis # 给端口取个名称
protocol: TCP # 协议,默认就是TCP
port: 6379 # service的端口
targetPort: 6379 # Pod的端口
参数 | 解释 |
---|---|
Port | port表示:service暴露在cluster ip(Seriver ip )上的端口,<cluster ip="" style="box-sizing: border-box;">:port 是提供给集群内部客户访问service的入口。</cluster> |
NodePort | nodePort是kubernetes提供给集群外部客户访问service入口的一种方式(另一种方式是LoadBalancer ,<nodeip style="box-sizing: border-box;">:nodePort 是提供给集群外部客户访问service的入口。</nodeip> |
targetPort | targetPort很好理解,targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。 |
无头Service (Headless Service)
无头Service没有ClusterIP。直接将Service名称解析到后端Pod的IP。
在定义无头Service时,只需要将clusterIP
设为None
即可。
清单文件内容如下:
[root@s1 ~]# cat myapp-headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-headless-svc
namespace: default
spec:
clusterIP: None # 关键点,将clusterIP设为None
selector:
app: myapp
release: canary
ports:
- name: http
port: 80
targetPort: 80
查看创建成功的无头Service
[root@s1 ~]# kubectl apply -f myapp-headless-svc.yaml
service/myapp-headless-svc created
[root@s1 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d
myapp-headless-svc ClusterIP None <none> 80/TCP 23s
利用dig命令去解析myapp-headless-svc,最终解析到的是五个后端Pod的IP
命令:dig -t A myapp-headless-svc.default.svc.cluster.local. @10.96.0.10
外部系统访问Service问题
我们需要掌握kubernetes中的三种IP
1.Node IP:Node节点IP地址
2.Pod IP:Pod的IP地址
3.Cluster IP:Service的IP地址
参数解释:
1.Node IP是Kubernetes集群中每个节点的物理网卡的IP地址,这是一个真实存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通讯,不管他们中间是否含有不属于Kubernetes集群中的节点。想Kubernetes之外的节点访问Kubernetes集群内的节点或者TCP/IP服务时,必须通过Node IP
2.Pod IP是每个Pod的IP地址,它是Docker Engine
根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,Kubernetes要求位于不同Node上的Pod能够彼此直接通讯,所以Kubernetes里一个Pod里的容器访问另外一个Pod里的容器,就是通过Pod IP所在的虚拟二层网络进行通信,而真实的TCP/IP流量则是通过Node IP
所在的物理网卡流出
3.Cluster IP
,它是一个虚拟IP,但更像是一个伪造的IP网络
(1)Cluster IP仅仅作用于Kubernetes Service对象,并由Kubernetes管理和分配IP地址(来源于Cluster IP地址池)
(2)Cluster IP无法被Ping,因为没有一个"实体网络对象"来影响
(3)在Kubernetes集群内,Node IP、Pod IP、Cluster IP之间的通信,采用的是Kubernetes自己设计的特殊路由规则