Service资源概述

Service是Kubernetes的核心资源类型之一,它通过规则定义出由多个Pod对象组合而成的逻辑集合,以及访问这组Pod的策略。 由Deployment等控制器管理的Pod对象在中断或扩缩容后,Pod组合的IP地址都会发生变化,而引入Service资源后,就可以基于标签选择器将一组Pod定义成一个逻辑组合,并通过自己的IP地址和端口调度代理请求至组内的Pod对象之上,它向客户端隐藏了真实的、处理用户请求的Pod资源,使得客户端的请求看上去就像是由Service直接处理并进行响应的一样。而且Service与Pod对象之间通过标签选择器以松耦合的方式关联,它可以先于Pod对象创建而不会发生错误。

创建Service资源

Service资源基本的配置清单:

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  selector:
    app: myapp
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP

Service资源myapp-svc通过标签选择器关联至标签为“app=myapp”的各Pod对象,它会自动创建名为myapp-svc的Endpoints资源对象,并自动配置一个ClusterIP,暴露的端口由port字段进行指定,后端各Pod对象的端口则由targetPort给出。 分别查看service和endpoint的状态:

kubectl get svc myapp-svc
kubectl get endpoints myapp-svc

向Service对象请求服务

Service资源的默认类型为ClusterIP,它仅能接收来自于集群中的Pod对象中的客户端程序的访问请求。所以为了测试,会创建一个专用的Pod对象,利用其交互式接口来访问service资源。

kubectl run cirros-$RANDOM --rm -it --image=cirros -- sh

启动了一个运行CirrOS容器的Pod,CirrOS是设计用来进行云计算环境测试的Linux微型发行版,它自带HTTP客户端工具curl等。 在容器的交互式接口访问ClusterIP:Port

/ # curl http://10.105.246.145:80
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

Kubernetes集群默认的Service代理模式为iptables,而且使用随机调度算法,因此Service会将客户端请求随机调度至与其关联的某个后端Pod资源上。

Service会话粘性

Service资源还支持Session affinity(会话粘性)机制,它能够将来自同一个客户端的请求始终转发至同一个后端的Pod对象,适用于需要基于客户端身份保存某些私有信息,并根据这些私有信息追踪用户的活动等一类的需求。 Service资源通过spec.sessionAffinity和spec.sessionAffinityConfig两个字段配置粘性会话:

  • sessionAffinity字段用于定义要使用的粘性会话的类型,它仅支持使用“None”和“ClientIP”两种属性值。‰
    • None:不使用sessionAffinity,默认值。‰
    • ClientIP:基于客户端IP地址识别客户端身份,把来自同一个源IP地址的请求始终调度至同一个Pod对象。
  • sessionAffinityConfig用于配置其会话保持的时长,其可用的时长范围为“1~86400”,默认为10800秒

但是Service资源的Session affinity机制仅能基于客户端IP地址识别客户端身份,它会把经由同一个NAT服务器进行源地址转换的所有客户端识别为同一个客户端,调度粒度粗糙且效果不佳,因此实践中并不推荐使用此种方法实现粘性会话。

服务发现

Service为Pod中的服务类应用提供了一个稳定的访问入口,但Pod客户端中的应用如何得知某个特定Service资源的IP和端口呢,这就需要借助服务发现机制来进行。 服务发现机制的基本实现,一般是事先部署好一个网络位置较为稳定的服务注册中心(也称为服务总线),服务提供者(服务端)向注册中心注册自己的位置信息,并在变动后及时予以更新,服务消费者则周期性地从注册中心获取服务提供者的最新位置信息从而“发现”要访问的目标服务资源。 在K8S中可以基于CoreDNS进行服务发现,甚至也可以使用简单的环境变量方式。

服务暴露

Service的IP地址默认仅在集群内可达,集群外部想访问服务,需要首先进行服务的暴露。

Service的类型

Service有四种类似ClusterIP、NodePort、LoadBalancer和ExternalName

  1. ClusterIP:通过集群内部IP地址暴露服务,此地址仅在集群内部可达,而无法被集群外部的客户端访问;
  2. NodePort:建立在ClusterIP类型之上,其在每个Node的IP地址的某静态端口(NodePort)暴露服务,NodePort的路由目标为ClusterIP,简单来说,NodePort类型就是在工作节点的IP地址上选择一个端口用于将集群外部的用户请求转发至目标Service的ClusterIP和Port,这种类型的Service既可如ClusterIP一样受到集群内部客户端Pod的访问,也会受到集群外部客户端通过套接字NodeIP:NodePort进行的请求;
  3. LoadBalancer:建构在NodePort类型之上,其通过cloud provider提供的负载均衡器将服务暴露到集群外部,LoadBalancer类型的Service会指向关联至Kubernetes集群外部的某个负载均衡设备,该设备通过工作节点之上的NodePort向集群内部发送请求流量,这种Service的优势在于,能够把来自于集群外部客户端的请求调度至所有节点(或部分节点)的NodePort之上,而不是依赖于客户端自行决定连接至哪个节点,从而避免了因客户端指定的节点故障而导致的服务不可用;
  4. ExternalName:通过将Service映射至由externalName字段的内容指定的主机名来暴露服务,此主机名需要被DNS服务解析至CNAME类型的记录。这种类型并非定义由Kubernetes集群提供的服务,而是把集群外部的某服务以DNS CNAME记录的方式映射到集群内,从而让集群内的Pod资源能够访问外部的Service的一种实现方式,这种类型的Service没有ClusterIP和NodePort,也没有标签选择器用于选择Pod资源。
NodePort

NodePort型的Service资源,其配置清单与前面默认的ClusterIP类型类似,只是要显式地在spec下指定type: NodePort 此外还可以指定Node端口,但必须在30000-32767之间,而且推荐使用集群自动分配的端口以避免端口的冲突。

LoadBalancer

NodePort类型的Service资源虽然能够于集群外部访问得到,但外部客户端必须得事先得知NodePort和集群中至少一个节点的IP地址,且选定的节点发生故障时,客户端还得自行选择请求访问其他的节点,此外有时节点并不允许外界访问,LoadBalancer类型可将请求流量调度至各节点的NodePort之上。 创建LoadBalancer类型需要指定type: LoadBalancer

ExternalName

ExternalName类型的Service资源用于将集群外部的服务发布到集群中以供Pod中的应用程序访问。需要指定type: ExternalName和externalName,比如externalName: redis.ilinux.io,externalName属性定义了一个CNAME记录用于返回外部真正提供服务的主机的别名,而后通过CNAME记录值获取到相关主机的IP地址。

spec:
  type: ExternalName
  externalName: redis.ilinux.io

服务创建后通过serviceName访问相应的服务,ClusterDNS会将此名称以CNAME格式解析为spec.externalName字段中的名称,而后通过DNS服务将其解析为相应的主机的IP地址。

Ingress和Ingress Controller

在Kubernetes中,Service资源和Pod资源的IP地址仅能用于集群网络内部的通信,所有的网络流量都无法穿透边界路由器以实现集群内外通信。Kubernetes提供了两种内建的云端负载均衡机制(cloud loadbalancing)用于发布公共应用:

  • 一种是工作于传输层的Service资源,它实现的是“TCP负载均衡器”,无论是iptables还是ipvs模型的Service资源都配置于Linux内核中的Netfilter之上进行四层调度,是一种类型更为通用的调度器,支持调度HTTP、MySQL等应用层服务。 但由于工作于传输层而功能有局限,比如不支持基于URL的请求调度机制,而且Kubernetes也不支持为其配置任何类型的健康检查。
  • 另一种便是Ingress资源,它实现的是“HTTP(S)负载均衡器”,属于应用层负载均衡机制的一种,它提供了诸如可自定义URL映射和TLS卸载等功能,并支持多种类型的后端服务器健康状态检查机制。

Ingress是Kubernetes API的标准资源类型之一,它其实就是一组基于DNS名称(host)或URL路径把请求转发至指定的Service资源的规则,用于将集群外部的请求流量转发至集群内部完成服务发布,它仅是一组路由规则的集合,Ingress控制器根据这些规则来匹配并路由请求流量。

Ingress资源

Ingress资源是基于HTTP虚拟主机或URL的转发规则。Ingress资源的定义方式举例:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: www.ilinux.io
    http:
      paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: myapp-svc
              port:
                number: 80


spec字段下主要嵌套如下三个字段

  • rules,用于定义当前Ingress资源的转发规则列表;未由rules定义规则,或者没有匹配到任何规则时,所有流量都会转发到由backend定义的默认后端。‰
  • backend,默认的后端用于服务那些没有匹配到任何规则的请求;定义Ingress资源时,至少应该定义backend或rules两者之一;此字段用于让负载均衡器指定一个全局默认的后端。backend对象的定义由两个必选的内嵌字段组成:serviceName和servicePort,分别用于指定流量转发的后端目标Service资源的名称和端口。‰
  • tls,TLS配置,目前仅支持通过默认端口443提供服务;如果要配置指定的列表成员指向了不同的主机,则必须通过SNI TLS扩展机制来支持此功能。
Ingress资源类型
单Service资源型Ingress

暴露单个Service的方法可以使用NodePort、LoadBalancer,也可以使用Ingress来暴露服务,只需要为Ingress指定“default backend”,这样Ingress控制器会为其分配一个IP地址接入请求流量,并将它们转至指定的后段service,如:

spec:
  backend:
    serviceName: my-svc
    servicePort: 80
基于URL路径进行流量分发

Ingress也支持基于URL路径进行流量分发,如:

spec:
  rules:
  - host: www.ilinux.io
    http:
      paths:
        - path: /web
          backend:
            service:
              name: myapp-svc-web
              port:
                number: 80
        - path: /api
          backend:
            service:
              name: myapp-svc-api
              port:
                number: 80

上面定义的规则会将对www.ilinux.io/web的请求转发到前端service myapp-svc-web,而将www.ilinux.io/api请求转发到后段service myapp-svc-api。

基于主机名称的虚拟主机

如果服务按照域名进行划分,可以将Ingress基于虚拟主机定义如下:

spec:
  rules:
  - host: web.ilinux.io
    ...
  - host: api.ilinux.io
TLS类型的Ingress资源

如果需要以HTTPS发布Service资源,也可以配置TLS协议的Ingress资源,但需要基于一个含有私钥和证书的Secret对象,这在后面的章节会涉及到,在Ingress资源中引用此Secret即可让Ingress控制器加载并配置为HTTPS服务:

spec:
  tls:
  - secretName: ilinuxSecret
  backend:
    ...

Ingress控制器

Ingress控制器自身是运行于Pod中的容器应用,一般是Nginx或Envoy一类的具有代理及负载均衡功能的守护进程,它监视着来自于API Server的Ingress对象状态,并以其规则生成相应的应用程序专有格式的配置文件并通过重载或重启守护进程而使新配置生效。例如,对于Nginx来说,Ingress规则需要转换为Nginx的配置信息。 既然Ingress控制器实际上也是Pod资源,那么也需要接入外部流量,这可以使用NodePort或LoadBalancer类型的Service对象为其接入集群外部的请求流量;或者借助于DaemonSet控制器,将Ingress控制器的Pod资源各自以单一实例的方式运行于集群的所有或部分工作节点之上,并配置这类Pod对象以hostPort或hostNetwork的方式在当前节点接入外部流量。 以部署ingress-nginx并通过专用的Service接入流量为例: 首先apply在线的mandatory.yaml来部署ingress-nginx所需的全部资源,它们的namespace:为ingress-nginx:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.24.1/deploy/mandatory.yaml

然后定义专用的NodePort型Service资源,注意其selector要与nginx-ingress-controller的selector保持一致:

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  type: NodePort
  selector:
    app.kubernetes.io/name: ingress-nginx
  ports:
  - port: 80
    name: http
    nodePort: 30080
  - port: 443
    name: https
    nodePort: 30443

然后就可以通过这个NodePort型Serviced的IP:30002访问了。或者也可以根据ingress指定的host名称配置etc/hosts,比如对于如下的ingress配置:

spec:
  rules:
  - host: tomcat.ilinux.io

sudo vim /etc/hosts,添加一行

127.0.0.1 tomcat.ilinux.io

然后也就可以通过tomcat.ilinux.io:30002访问了。

学习资料

《Kubernetes实战进阶》 马永亮著

06-14 20:41