k8s-服务与服务发现及路由

1.什么是service

Kubernetes 提供了一种 API 对象叫做 Service。Service 可以理解为一种访问一组特定Pod 的策略。

举个例子,考虑一个图片处理应用程序,通过 Pod 运行了 3 个副本,并且是无状态的。前端访问该应用程序时, 不需要关心实际是调用了那个 Pod 实例。后端的 Pod 发生重启时,前端不应该也不需要感知到。对于这种解耦关系,我们就可以通过 Service 来做。Service 与后端的多个 Pod 进行关联(通过 selector),前端只需要访问Service 即可。

创建service

apiVersion: v1 
kind: Service 
metadata: 
	name: nginx-service 
spec: 
	selector: 
  	app: nginx 
  ports: 
  	- protocol: TCP 
    port: 80 
    targetPort: 80

创建一个名称为 my-service 的 Service 对象,它会将对 80 端口的 TCP 请求转发到一组 Pod 上,这些 Pod 的特点是被打上标签 app=nginx,并且使用 TCP 端口80。

2.service的三种代理模式

在K8s集群中,每个node节点运行一个kube-proxy进程,他负责为service实现一种VIP(虚拟IP)的形式,在k8s的1.0版本,代理完全使用userspace,1.1版本新增了iptables代理,但并不是默认的运行模式,从kubernetes1.2开始,默认就是iptables代理,在kubernetes1.8开始添加了ipvs代理;在1.14版本开始默认使用ipvs代理。

1.userspace模式

img

这种模式所有请求都需要经过kube-proxy处理,导致其压力很大。

2.iptables

img

这种模式下kube-proxy 压力减小,稳定性提高,iptables采用一条条的规则列表。

但iptables又是为了防火墙设计的,集群数量越多iptables规则就越多,而iptables规则是从上到下匹配,所以效率就越是低下。

3.ipvs

img

这种模式,kube-proxy会监视kubernetes的service对象和Endpoints,调用netlink接口以相应的创建ipvs规则并定期与service和Endpoints同步ipvs规则,以确保ipvs状态与期望一致。

访问服务时,流量将被转发到其中一个后端pod与iptables提供功能类似,但使用哈希表作为底层数据结构,并在内核空间中工作。这意味着ipvs可以更快的重定向流量,并且在同步代理规则时具有更好的性能。

此外,ipvs为负载均衡算法提供了更多的选项,例如:

  • rr:轮询调度

  • lc:最小连接数

  • dh:目标哈希

  • sh:源哈希

  • sed:最短期望延迟

  • nq:不排队调度

需要注意的是,当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了ipvs模块,如果未安装,则kube-proxy将回退到iptables模式

3.service的四种类型

1.ClusterIP

这是service在k8s中的默认类型,自动分配一个仅Cluster内部可以访问的虚拟ip。主要在每个node节点使用对应的代理模式(iptable或ipvs),将发向clusterIP对应端口的数据转发到kube-proxy中,然后kube-proxy自己内部实现有负载均衡的方法,并可以查询到这个service下对应pod的地址和端口,进而把数据转发给对应的pod地址和端口。如图所示:

img

为了实现上图的功能,主要需要以下几个组件的协同工作:

  1. Apiserver:用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中;

  2. kube-proxy进程在每个节点中都存在一个,这个进程负责感知service,pod的变化,并将变化信息写入本地的iptables(根据具体使用的代理)规则中;

  3. iptables(根据实际代理模式)使用NAT等技术将虚拟ip的流量转至endpoint中

Headless Service

有时候不需要负载均衡以及单独的service IP,这种情况可以通过指定Cluster IP值为“None”来创建HeadlessService.这类service不会分配ClusterIp,kube-proxy不会处理他们,平台也不会为他们进行负载和路由。普通service域名解析为clusterIP,HeadlessService域名解析为pod IP,在微服务相关场景 如果需要直通pod的时候 我们就可以使用headless service 绕过 k8s的转发机制,直接访问pod了。

2.NodePort

在ClusterIp的基础上为service在每台机器上绑定一个端口,这样就可以通过ip:port来访问该服务。原理在于在node上开了一个端口,将向该端口的流量导入到kube-proxy,然后由kube-proxy进一步转给对应的pod。

yaml示例:

apiVersion: v1 
kind: Service 
metadata: 
	name: nginx-service-nodeport 
spec: 
	type: NodePort 
  selector: 
  	app: nginx 
  ports: 
  	- protocol: TCP 
    port: 30001 # NodePort 类型的 Service 自动创建的 ClusterIP 的端口
    targetPort: 80 # ClusterIP 转发的目标端口
    nodePort: 30002 # Node节点本地启动的用来监听和转发请求的端口,每个节点上都会启动

NodePort类型的service后端其实还是通过ClusterIP来实现的,也会自动创建ClusterIP,并且为他分配对应的端口,这个端口是在一个固定区间随机生成的,这个区间是kubernetes的ApiServer启动的时候,通过启动参数里面的–service-node-port-range来制定的,默认为30000-32767。这个值其实我们也可以指定,如上例子中的nodePort就是指定的,但是指定的这个值需要在上面那个参数指定的范围内,不然会导致service创建失败。

对于 NodePort 类型的 Service,外部的请求顺序是:NodePort -> Port -> TargetPort

3.LoadBalancer

img

在NodePort的基础上,借助cloud provider创建一个外部负载均衡器,并将请求转发到NodeIP:NodePort。跟nodePort其实是同一种方式,区别在于比上面多了一步,可以调用云服务提供商去创建LB来向节点导流。

4.ExternalName

把集群外部的服务引入到集群内部来,在集群内部直接使用,没有任何类型代理被创建。只有kube1.7或更高版本的kube-dns才支持这种类型的service通过返回CNAME和他的值,可以将服务映射到externalName字段的内容。ExternalName Service是Service的特例,他没有selector,也没有定义任何的端口和endpoint,对于运行在集群外部的服务,通过返回该服务的别名这种方式来提供服务(在dns中对原域名做了一个别名操作),访问该 Service 的方式与其他服务的方式相同,但主要区别在于 重定向发生在 DNS 级别,而不是通过代理和转发。

apiVersion: v1  
kind: Service
metadata:
   name: my-service
   namespace: default
spec:
   type: ExternalName
   ExternalName: my.database.com

4.Ingress

ingress公开了从集群外部到集群内服务的 HTTP 和 HTTPS 路由,流量路由由 Ingress 资源上定义的规则控制。ingress是一种服务暴露的方式,可以理解成接口,有很多开源实现解决方案。

下图是一个将所有流量都发送到同一 Service 的简单 Ingress 示例:

img

ingress资源示例:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: foo.bar.com # 服务暴露的域名
  - http:  # 路由转发协议,可以是 http 或者 https
      paths:
      - path: /testpath #路由地址
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

Ingress 经常使用注解(annotations)来配置一些选项,具体取决于 Ingress 控制器,例如重写目标注解。 不同的 Ingress支持不同的注解。查看对应的文档以供你选择 Ingress 控制器,以了解支持哪些注解。

Ingress规则:

  • 可选的 host。在此示例中,未指定 host,因此该规则适用于通过指定 IP 地址的所有入站 HTTP 通信。 如果提供了 host(例如 foo.bar.com),则 rules 适用于该 host。

  • 路径列表 paths(例如,/testpath),每个路径都有一个由 serviceName 和 servicePort 定义的关联后端。 在负载均衡器将流量定向到引用的服务之前,主机和路径都必须匹配传入请求的内容。

  • backend是 Service文档中所述的服务和端口名称的组合。 与规则的 host 和 path 匹配的对 Ingress 的 HTTP(HTTPS )请求将发送到列出的 backend

DefaultBackend

通常在Ingress控制器中会配置defaultBackend(默认后端),没有rule匹配的所有流量都将被发送到同一个默认后端,defaultBackend 通常是Ingress控制器的配置选项,而非在 Ingress 资源中指定。

如果 hosts 或 paths 都没有与 Ingress 对象中的 HTTP 请求匹配,则流量将路由到默认后端。

ingress由两部分组成:

  1. ingress controller:将新加入的Ingress转化成Nginx的配置文件并使之生效.
  2. ingress服务:将配置抽象成一个Ingress对象,每添加一个新的服务只需写一个新的Ingress的yaml文件即可.

5.ingress-nginx

基于nginx服务的ingress controller根据不同的开发公司,又分为k8s社区的ingres-nginx和nginx公司的nginx-ingress。根据github上的活跃度和关注人数,以k8s社区的ingres-nginx为例。

k8s社区提供的ingress,github地址如下:https://github.com/kubernetes/ingress-nginx
nginx社区提供的ingress,github地址如下:https://github.com/nginxinc/kubernetes-ingress

ingress-nginx请求转发架构图如下:

img

ingress-nginx工作原理

img

  1. ingress controller通过与k8s的api进行交互,动态的去感知k8s集群中ingress服务规则的变化,然后读取它,并按照定义的ingress规则,转发到k8s集群中对应的service。

  2. 这个ingress规则写明了哪个域名对应k8s集群中的哪个service,然后再根据ingress-controller中的nginx配置模板,生成一段对应的nginx配置。

  3. 再把该配置动态的写到ingress-controller的pod里,该ingress-controller的pod里面运行着一个nginx服务,控制器会把生成的nginx配置写入到nginx的配置文件中,然后reload一下,使其配置生效,以此来达到域名分配置及动态更新的效果。

ingress解决的问题
  1. 动态配置服务:如果按照传统方式, 当新增加一个服务时, 我们可能需要在流量入口加一个反向代理指向我们新的k8s服务. 而如果用了Ingress, 只需要配置好这个服务, 当服务启动时, 会自动注册到Ingress的中, 不需要而外的操作。

  2. 减少不必要的端口暴露:以NodePort方式映射出去, 这样就相当于给宿主机打了很多孔, 既不安全也不优雅. 而Ingress可以避免这个问题, 除了Ingress自身服务可能需要映射出去, 其他服务都不要用NodePort方式。

  3. Service能够提供负载均衡的能力,但是在使用上有以下限制:只提供 4 层负载均衡能力,而没有 7 层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的,ingress可以支持到7层负载均衡。

四层与七层负载均衡的区别可以参考:https://www.cnblogs.com/zhaiyf/p/9051694.html

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐