Service概述

通过以前的学习,我们已经能够通过Deployment来创建一组Pod来提供具有高可用性的服务。虽然每个Pod都会分配一个单独的Pod IP,然而却存在如下两问题:

  1. Pod IP仅仅是集群内可见的虚拟IP,外部无法访问。

  2. Pod IP会随着Pod的销毁而消失,当Deployment对Pod进行动态伸缩时,Pod IP可能随时随地都会变化,这样对于我们访问这个服务带来了难度。

Service能够提供负载均衡的能力,但是在使用上有以下限制。只提供 4 层负载均衡能力,而没有7 层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的因此,Kubernetes中的Service对象就是解决以上问题的实现服务发现核心关键。

Service 是一个固定接入层,客户端可以通过访问 Service 的 ip 和端口访问到 Service 关联的后端pod,这个 Service 工作依赖于在 kubernetes 集群之上部署的一个附件,就是 kubernetes 的 dns 服务(不同 kubernetes 版本的 dns 默认使用的也是不一样的,1.11 之前的版本使用的是 kubeDNs,较新的版本使用的是 coredns),Service 的名称解析是依赖于 dns 附件的,因此在部署完 k8s 之后需要再部署 dns附件,kubernetes 要想给客户端提供网络功能,需要依赖第三方的网络插件(flannel,calico 等)。每个 K8s 节点上都有一个组件叫做 kube-proxy,kube-proxy 这个组件将始终监视着 apiserver 中有关Service 资源的变动信息,需要跟 master 之上的 apiserver 交互,随时连接到 apiserver 上获取任何一个与 Service 资源相关的资源变动状态,这种是通过 kubernetes 中固有的一种请求方法 watch(监视)来实现的,一旦有 Service 资源的内容发生变动(如创建,删除),kube-proxy 都会将它转化成当前节点之上的能够实现 Service 资源调度,把我们请求调度到后端特定的 pod 资源之上的规则,这个规则可能是 iptables,也可能是 ipvs,取决于 Service 的实现方式。

Service类型

#查看Service的type资源描述
kubectl explain service.spec.type
KIND:     Service
VERSION:  v1

FIELD:    type <string>

DESCRIPTION:
     type determines how the Service is exposed. Defaults to ClusterIP. Valid
     options are ExternalName, ClusterIP, NodePort, and LoadBalancer.
     "ExternalName" maps to the specified externalName. "ClusterIP" allocates a
     cluster-internal IP address for load-balancing to endpoints. Endpoints are
     determined by the selector or if that is not specified, by manual
     construction of an Endpoints object. If clusterIP is "None", no virtual IP
     is allocated and the endpoints are published as a set of endpoints rather
     than a stable IP. "NodePort" builds on ClusterIP and allocates a port on
     every node which routes to the clusterIP. "LoadBalancer" builds on NodePort
     and creates an external load-balancer (if supported in the current cloud)
     which routes to the clusterIP. More info:
     https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types

从上面的资源清单描述中可以看到Service在k8s中有以下四种类型:

**ClusterIp:**默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP

**NodePort:**在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 NodePort 来访问该服务

**LoadBalancer:**在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到NodePort。是付费服务,而且价格不菲。

**ExternalName:**把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持。

ClusterIp

ClusterIp类型的Service将发向ClusterIP对应端口的数据,转发到kube-proxy中。然后kube-proxy自己内部实现有负载均衡的方法,并可以查询到这个Service下关联的pod的地址和端口,进而把数据转发给对应的pod的地址和端口,需要注意的是,ClusterIp类型的Service只能在集群内部访问。

资源文件示例如下:

apiVersion: v1
kind: Service
metadata:
  name: service-clusterip
  labels:
    app: nginx-service
spec:
  selector:
    app: nginx-pod
  ports:
    - port: 8000
      targetPort: 80
      protocol: TCP
  type: ClusterIP

NodePort

我们的场景不全是集群内访问,也需要集群外业务访问。那么ClusterIP就满足不了了。NodePort当然是其中的一种实现方案。NodePort的原理在于在node上开了一个端口,将向该端口的流量导入到kube-proxy,然后由kube-proxy进一步到给对应的pod。

资源文件示例如下:

apiVersion: v1
kind: Service
metadata:
  name: service-nodeport
  labels:
    app: nginx-service
spec:
  selector:
    app: nginx-pod
  ports:
    - port: 8000
      targetPort: 80
      nodePort: 30080
      protocol: TCP
  type: NodePort

LoadBalancer

LoadBalancer类型的Service是可以实现集群外部访问服务的另外一种解决方案。不过并不是所有的k8s集群都会支持,大多是在公有云托管集群中会支持该类型。负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过Service的status.loadBalancer字段被发布出去。

资源文件示例如下:

apiVersion: v1
kind: Service
metadata:
  name: service-loadbalancer
  labels:
    app: nginx-service
spec:
  selector:
    app: nginx-pod
  ports:
    - port: 8000
      targetPort: 80
      nodePort: 30080
      protocol: TCP
  type: LoadBalancer

ExternalName

ExternalName类型的Service将服务映射到DNS名称,而不是典型的选择器,你可以使用spec.externalName参数指定这些服务。这种类型的Service通过返回CNAME和它的值,可以将服务映射到ExternalName字段的内容(例如:k8s.bijava.com)。ExternalName Service 是 Service 的特例,它没有selector,也没有定义任何的端口和Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的cname(别名)这种方式来提供服务。

资源文件示例如下:

apiVersion: v1
kind: Service
metadata:
  name: service-externalname
  labels:
    app: nginx-service
spec:
  selector:
    app: nginx-pod
  ports:
    - port: 8000
      targetPort: 80
      protocol: TCP
  type: ExternalName
  externalName: service-clusterip.default.svc.cluster.local

Service应用实践

实践场景:通过k8s部署Nginx服务,并使用Service将服务暴露给集群外,以便用户在浏览器能访问到Nginx中的应用。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: default
  labels:
    app: nginx-deploy
spec:
  replicas: 2
  template:
    metadata:
      name: nginx-pod
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.5
        imagePullPolicy: IfNotPresent
  selector:
    matchLabels:
      app: nginx-pod
      
---

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx-pod
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080
  type: NodePort

k8s的yaml资源文件可以包含多个资源内容,上面的资源文件包含两部分资源内容,Deployment和Service,它们之间通过---进行分割,编写好资源文件,接下来进行创建;

kubectl apply -f nginx-service.yaml

等待创建成功后,查看Service状态:

kubectl get svc
NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes      ClusterIP   10.1.0.1       <none>        443/TCP        98d
nginx-service   NodePort    10.1.172.251   <none>        80:30080/TCP   14s

可以看到POST映射了两个端口,80和30080,前面的是集群内的访问端口(10.1.172.251:80),后面是集群外的访问端口(192.168.58.110:30080),集群外访问需要使用主机的IP地址,在浏览器访问192.168.58.110:30080,验证如何能访问到nginx首页则说明服务部署成功。