• 欢迎来到本博客,希望可以y一起学习与分享

k8s之Service

笔记 benz 5个月前 (05-03) 13次浏览 0个评论 扫描二维码
文章目录[隐藏]

问题

首先,我们思考这样一个问题:

访问k8s集群中的pod, 客户端需要知道pod地址,需要感知pod的状态。那如何获取各个pod的地址?若某一node上的pod故障,客户端如何感知?

什么是service

是发现后端pod服务;

是为一组具有相同功能的容器应用提供一个统一的入口地址;

是将请求进行负载分发到后端的各个容器应用上的控制器。

对service的访问来源

访问service的请求来源有两种:k8s集群内部的程序(Pod)和 k8s集群外部的程序。

创建service

创建一个servcie有两种方式

命令式

声明式
创建yaml文件 svc-goweb.yaml

创建服务

我们来看下配置文件中几个重点字段:

  • selector指定了app: goweb标签。说明该svc代理所有包含有”app: goweb”的pod
  • port字段指定了该svc暴露80端口
  • targetPort指定改svc代理对应pod的8000端口
  • type定义了svc的类型为ClusterIP,这也是svc的默认类型

通过apply创建服务后,来查看一下服务状态

可以看到,Kubernetes自动为服务分配了一个CLUSTER-IP。通过这个访问这个IP的80端口,就可以访问到”app: goweb”这组pod的8000端口,并且可以在这组pod中负载均衡。

service类型

采用微服务架构时,作为服务所有者,除了实现业务逻辑以外,还需要考虑如何把服务发布到k8s集群或者集群外部,使这些服务能够被k8s集群内的应用、其他k8s集群的应用以及外部应用使用。因此k8s提供了灵活的服务发布方式,用户可以通过ServiceType来指定如何来发布服务,类型有以下几种:

ClusterIP(集群内部使用)

默认方式,分配一个稳定的IP地址,即VIP,只能在集群内部访问。


创建ClusterIP的Service yaml如下:

类型为ClusterIP的service,这个service有一个Cluster-IP,其实就一个VIP。具体实现原理依靠kubeproxy组件,通过iptables或是ipvs实现。

这种类型的service 只能在集群内访问。

NodePort(对外暴露应用)

在每个节点启用一个端口来暴露服务,可以在集群外部访问,通过NodeIP:NodePort访问

端口范围:30000~32767


创建NodePort 类型service 如下:


此时我们可以通过http://4.4.4.1:30080http://4.4.4.2:30080 对pod-python访问。该端口有一定的范围,比如默认Kubernetes 控制平面将在--service-node-port-range标志指定的范围内分配端口(默认值:30000-32767)。

LoadBalancer(对外暴露应用,适用于公有云)

与NodePort类似,在每个节点启用一个端口来暴露服务。除此之外,K8s请求底层云平台的负载均衡器,把每个[Node IP]:[NodePort]作为后端添加进去。



可以看到external-ip。我们就可以通过该ip来访问了。

当然各家公有云支持诸多的其他设置。大多是公有云负载均衡器的设置参数,都可以通过svc的注解来设置,例如下面的aws:

 

ExternalName:

类型为 ExternalName 的service将服务映射到 DNS 名称,而不是典型的选择器,例如my-service或者cassandra。 您可以使用spec.externalName参数指定这些服务。

创建 ExternalName 类型的服务的 yaml 如下:

 

说明:您需要 CoreDNS 1.7 或更高版本才能使用ExternalName类型。

当查找主机 service-python.default.svc.cluster.local时,集群DNS服务返回CNAME记录,其值为my.database.example.com。 访问service-python的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发。

将生产工作负载迁移到Kubernetes集群并不容易。大多数我们不可以停止所有服务并在Kubernetes集群上启动它们。有时,尝试迁移轻量且不会破坏你服务的服务是很好的。在此过程中,一个可能不错的解决方案是使用现有的有状态服务(例如DB),并首先从无状态容器开始。

从Pod中访问外部服务的最简单正确的方法是创建ExternalName service。例如,如果您决定保留AWS RDS,但您还希望能够将MySQL容器用于测试环境。让我们看一下这个例子:

你已将Web应用程序配置为使用URL测试服务访问数据库,但是在生产集群上,数据库位于AWS RDS上,并且具有以下URL test.database.example.com。创建ExternalName service 并且你的Web Pod尝试访问test-service上的数据库之后,Kubernetes DNS服务器将返回值为test.database.example.com的CNAME记录。问题解决了。

ExternalName service 也可以用于从其他名称空间访问服务。例如:

在这里,我可以使用名称空间a中定义的test-service-1访问命名空间b中的服务test-service-2

这个意义在哪里?

ExternalName service 也是一种service,那么ingress controller 会支持,那么就可以实现跨namespace的ingress。

几种特殊的service

除了上面这些通常的service配置,还有几种特殊情况:

Multi-Port Services

service可以配置不止一个端口,比如官方文档的例子:

这个service保留了80与443端口,分别对应pod的9376与9377端口。这里需要注意的是,pod的每个端口一定指定name字段(默认是default)。

Headless services

Headless services是指一个服务没有配置了clusterIP=None的服务。这种情况下,kube-proxy不会为这个服务做负载均衡的工作,而是交予DNS完成。具体又分为2种情况:

  • 有配置selector: 这时候,endpoint控制器会为服务生成对应pod的endpoint对象。service对应的DNS返回的是endpoint对应后端的集合。
  • 没有配置selector:这时候,endpoint控制器不会自动为服务生成对应pod的endpoint对象。若服务有配置了externalname,则生成一套cnmae记录,指向externalname。如果没有配置,就需要手动创建一个同名的endpoint对象。dns服务会创建一条A记录指向endpoint对应后端。

External IPs

如果有个非node本地的IP地址,可以通过比如外部负载均衡的vip等方式被路由到任意一台node节点,那就可以通过配置service的externalIPs字段,通过这个IP地址访问到服务。集群以这个IP为目的IP的请求时,会把请求转发到对应服务。参考官方文档的例子:

这里的80.11.12.10就是一个不由kubernetes维护的公网IP地址,通过80.11.12.10:80就可以访问到服务对应的pod。

简单总结下,service对象实际上解决的就是一个分布式系统的服务发现问题,把相同功能的pod做成一个服务集也能很好的对应微服务的架构。在目前的kubernetes版本中,service还只能实现4层的代理转发,并且要搭配好DNS服务才能真正满足生产环境的需求。

 

service selector

service通过selector和pod建立关联。

k8s会根据service关联到pod的podIP信息组合成一个endpoint。

若service定义中没有selector字段,service被创建时,endpoint controller不会自动创建endpoint。

service负载分发策略

service 负载分发策略有两种:
RoundRobin:轮询模式,即轮询将请求转发到后端的各个pod上(默认模式);

SessionAffinity:基于客户端IP地址进行会话保持的模式,第一次客户端访问后端某个pod,之后的请求都转发到这个pod上。

k8s服务发现方式

虽然Service解决了Pod的服务发现问题,但不提前知道Service的IP,怎么发现service服务呢?

k8s提供了两种方式进行服务发现:

环境变量: 当创建一个Pod的时候,kubelet会在该Pod中注入集群内所有Service的相关环境变量。需要注意的是,要想一个Pod中注入某个Service的环境变量,则必须Service要先比该Pod创建。这一点,几乎使得这种方式进行服务发现不可用。

 

DNS:可以通过cluster add-on的方式轻松的创建KubeDNS来对集群内的Service进行服务发现————这也是k8s官方强烈推荐的方式。为了让Pod中的容器可以使用kube-dns来解析域名,k8s会修改容器的/etc/resolv.conf配置。

k8s服务发现原理

endpoint

endpoint是k8s集群中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址。

service配置selector,endpoint controller才会自动创建对应的endpoint对象;否则,不会生成endpoint对象。

例如,k8s集群中创建一个名为k8s-classic-1113-d3的service,就会生成一个同名的endpoint对象,如下图所示。其中ENDPOINTS就是service关联的pod的ip地址和端口。

endpoint controller

endpoint controller是k8s集群控制器的其中一个组件,其功能如下:

  • 负责生成和维护所有endpoint对象的控制器
  • 负责监听service和对应pod的变化
  • 监听到service被删除,则删除和该service同名的endpoint对象
  • 监听到新的service被创建,则根据新建service信息获取相关pod列表,然后创建对应endpoint对象
  • 监听到service被更新,则根据更新后的service信息获取相关pod列表,然后更新对应endpoint对象
  • 监听到pod事件,则更新对应的service的endpoint对象,将podIp记录到endpoint中

Service代理模式

Service是由kube-proxy实现的

kube-proxy支持三种代理模式: 用户空间,iptables和IPVS;它们各自的操作略有不同。

Userspace

作为一个例子,考虑前面提到的图片处理应用程序。 当创建 backend Service 时,Kubernetes master 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。 假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy 实例观察到。 当代理看到一个新的 Service, 它会打开一个新的端口,建立一个从该 VIP 重定向到新端口的 iptables,并开始接收请求连接。

当一个客户端连接到一个 VIP,iptables 规则开始起作用,它会重定向该数据包到 Service代理 的端口。 Service代理 选择一个 backend,并将客户端的流量代理到 backend 上。

这意味着 Service 的所有者能够选择任何他们想使用的端口,而不存在冲突的风险。 客户端可以简单地连接到一个 IP 和端口,而不需要知道实际访问了哪些 Pod

iptables

再次考虑前面提到的图片处理应用程序。 当创建 backend Service 时,Kubernetes 控制面板会给它指派一个虚拟 IP 地址,比如 10.0.0.1。 假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy 实例观察到。 当代理看到一个新的 Service, 它会配置一系列的 iptables 规则,从 VIP 重定向到 per-Service 规则。 该 per-Service 规则连接到 per-Endpoint 规则,该 per-Endpoint 规则会重定向(目标 NAT)到 backend。

当一个客户端连接到一个 VIP,iptables 规则开始起作用。一个 backend 会被选择(或者根据会话亲和性,或者随机),数据包被重定向到这个 backend。 不像 userspace 代理,数据包从来不拷贝到用户空间,kube-proxy 不是必须为该 VIP 工作而运行,并且客户端 IP 是不可更改的。 当流量打到 Node 的端口上,或通过负载均衡器,会执行相同的基本流程,但是在那些案例中客户端 IP 是可以更改的。

IPVS(推荐)

在大规模集群(例如10,000个服务)中,iptables 操作会显着降低速度。 IPVS 专为负载平衡而设计,并基于内核内哈希表。 因此,您可以通过基于 IPVS 的 kube-proxy 在大量服务中实现性能一致性。 同时,基于 IPVS 的 kube-proxy 具有更复杂的负载平衡算法(最小连接,局部性,加权,持久性)。

 

参考

详解k8s 4种类型Service
浅谈 kubernetes service 那些事
Kubernetes Service详解


文章 k8s之Service 转载需要注明出处
喜欢 (0)

您必须 登录 才能发表评论!