K8s系列之:深入掌握Service

K8s系列之:深入掌握Service

  • 一、Service定义详解
  • 二、Service基本用法
    • 1.expose命令来创建Service
    • 2.配置文件定义Service
  • 三、service负载分发策略
  • 四、多端口Service
  • 五、外部服务Service
  • 六、Headless Service

Service是K8s最核心的概念,通过创建Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。

一、Service定义详解

yaml格式的Service定义文件的完整内容如下:

apiVersion: v1
kind: Service
metadata:
  name: string
  namespace: string
  labels:
    - name: string
  annotations:
    - name: string
spec:
  selector: []
  type: string
  clusterIP: string
  sessionAffinity: string
  ports:
  - name: string
    protocol: string
    port: int
    targetPort: int
    nodePort: int
  status:
    loadBalancer:
      ingress:
        ip: string
        hostname: string

对各属性的说明如表所示:

属性名称 取值类型 是否必选 取值说明
version string Required v1
kind string Required Service
metadata object Required 元数据
metadata.name string Required Service名称
metadata.namespace string Required 命名空间,不指定系统时将使用名为default的命名空间
metadata.labels[] list 自定义标签属性列表
metadata.annotation[] list 自定义注解属性列表
spec object Required 详细描述
spec.selector[] list Required Label Selector配置,将选择具有指定Label标签的Pod作为管理范围
spec.type string Required Service的类型,指定Service的访问方式,默认值为ClusterIP。ClusterIP:虚拟的服务IP地址,该地址用于K8s集群内部的Pod访问,在Node上kube-proxy通过设置的Iptables规则进行转发。NodePort:使用宿主机的端口,使能够访问各Node的外部客户端通过Node的IP地址和端口号就能访问服务。LoadBalancer:使用外接负载均衡器完成到服务的负载分发,需要在spec.status.loadBalancer字段指定外部负载均衡器的IP地址,并同时定义nodePort和clusterIP,用于公有云环境
spec.clusterIP string 虚拟服务IP地址,当type=ClusterIP时,如果不指定,则系统进行自动分配,也可以手工指定。当type=LoadBalancer时,则需要指定
spec.sessionAffinity string 是否支持Session,可选值为ClientIP,默认值为空。ClientIP:表示将同一个客户端(根据客户端的IP地址决定)的访问请求都转发到同一个后端Pod
spec.ports[] list Service需要暴露的端口列表
spec.ports[].name string 端口名称
spec.ports[].protocol string 端口协议,支持TCP和UDP,默认值为TCP
spec.ports[].port int 需要监听的端口号
spec.ports[].targetPort int 需要转发到后端Pod的端口号
spec.ports[].nodePort int 当spec.type=NodePort时,指定映射到物理机的端口号
Status object 当spec.type=LoadBalancer时,设置外部负载均衡器的地址。用于公有云环境
status.loadBalancer object 外部负载均衡器
status.loadBalancer.ingress object 外部负载均衡器
status.loadBalancer.ingress.ip string 外部负载均衡器的IP地址
status.loadBalancer.ingress.hostname string 外部负载均衡器的主机名

二、Service基本用法

一般来说,对外提供服务的应用程序需要通过某种机制来实现,对于容器应用最简便的方式就是通过TCP/IP机制及监听IP和端口号来实现。
例如,定义一个提供Web服务的RC,由两个tomcat容器副本组成,每个容器通过containerPort设置提供服务的端口号为8080。

webapp-rc.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: webapp
spec:
  replicas: 2
  template:
    metadata:
      name: webapp
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: tomcat
        ports:
        - containerPort: 8080

创建该RC

kubectl create -f webapp-rc.yaml
replicationcontroller "webapp" created

获取Pod的IP地址

kubectl get pods -l app=webapp -o yaml | grep podIP
podIP:172.17.1.4
podIP: 172.17.1.3

可以直接通过这两个Pod的IP地址和端口号访问Tomcat服务

curl 172.17.1.4:8080
curl 172.17.1.3:8080

直接通过Pod的IP地址和端口号可以访问到容器应用内的服务,但是Pod的IP地址是不可靠的,例如当Pod所在的Node发生故障时,Pod将被K8s重新调度到另一台Node,Pod的IP地址将发生变化。

容器应用本身是分布式的部署方式,通过多个实例共同提供服务,就需要在这些实例的前端设置一个负载均衡器来实现请求的转发。K8s中的Service就是设计出来用于解决这些问题的核心组件。

以创建的webapp应用为例,为了让客户端应用能够访问到两个Tomcat Pod实例,需要创建一个Service来提供服务。K8s提供了一种快速的方法,即通过kubectl

1.expose命令来创建Service

kubectl expose rc webapp
service "webapp" exposed

查看新创建的Service,可以看到系统为它分配了一个虚拟的IP地址(ClusterIP),而Service所需的端口号则从Pod中的containerPort复制而来。

kubectl get svc
NAME    CLUSTER-IP        EXTERNAL-IP    PORT(S)      AGE
webapp     169.169.235.79    <none>       8080/TCP     3s

接下来可以通过Service的IP地址和Service的端口号访问该Service。

curl 169.169.235.79:8080

对service地址169.169.235.79:8080的访问被自动负载分发到了后端两个Pod之一。

2.配置文件定义Service

除了使用kubectl expose命令创建Service,也可以通过配置文件定义Service,在通过kubectl create命令进行创建。例如对于webapp应用,可以设置一个Service。

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
 - port: 8081
    targetPort: 8080
  selector:
    app: webapp
  • Service定义中的关键字段是ports和selector。
  • ports定义部分指定了Service所需的虚拟端口号为8081,由于Pod容器端口号8080不一样,所以需要再通过targetPort来指定后端Pod的端口号。
  • selector定义部分设置的是后端Pod所拥有宕label:app=webapp。

创建该Service并查看其ClusterIP地址

kubectl create -f webapp-svc.yaml
service "webapp" created
kubectl get svc
NAME    CLUSTER-IP        EXTERNAL-IP    PORT(S)      AGE
webapp   169.169.28.190    <none>       8081/TCP     3s

通过Service的IP地址和Service的端口号进行访问:

curl 169.169.28.190:8081

同样对Service地址169.169.28.190:8081访问被自动负载分发到了后端两个Pod之一。

三、service负载分发策略

K8s提供了两种负载分发策略:RoundRobin和SessionAffinity。说明如下:

  • RoundRobin:轮训模式,即轮询将请求转发到后端的各个Pod上。
  • SessionAffinity:基于客户端IP地址进行会话保持的模式,即第1次将某个客户端发起的请求转发到后端的某个Pod上,之后从相同的客户端发起的请求都将被转发到后端相同的Pod上。

在默认情况下,K8s采用RoundRobin模式对客户端请求进行负载分发,但也可以通过设置service.spec.sessionAffinity=ClientIP来启用SessionAffinity策略。同一个客户端IP发来的请求就会被转发到后端固定的某个Pod上了。

通过Service的定义,K8s实现了一种分布式应用统一入口的定义和负载均衡机制。Service还可以进行其他类型的设置,例如多个端口号、直接设置为集群外部服务,或实现为无头服务(Headless)模式。

四、多端口Service

有时一个容器应用也可能提供多个端口的服务,在Service的定义中也可以相应地设置为将多个端口对应到多个应用服务。

下面的例子中,Service设置了两个端口号,并且为每个端口号进行了命名:

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
  - port: 8080
    targetPort: 8080
    name: web
  - port: 8005
    targetPort: 8005
    name: management
  selector:
    app: webapp

另一个例子是两个端口号使用了不同的4层协议tcp和udp。

apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 169.169.0.100
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP

五、外部服务Service

在某些环境中,应用系统需要将一个外部数据库作为后端服务进行连接,或将另一个集群或Namespace中的服务作为服务的后端,可以通过创建一个无Label Selector的Service实现:

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

通过该定义创建的是一个不带标签选择器的Service,即无法选择后端的Pod,系统不会自动创建Endpoint,需要手动创建一个和该Service同名的Endpoint,用于指向实际的后端访问地址。

创建Endpoint的配置文件内容如下:

kind: Endpoints
apiVersion: v1
metadata:
  name: my-service
subsets:
- addresses:
  - IP: 1.2.3.4
  ports:
  - port: 80

访问没有标签选择器的Service和带有标签选择器的Service一样,请求将会被路由到由用户手动定义的后端Endpoint上。

六、Headless Service

开发人员希望自己控制负载均衡的策略,不使用Service提供的默认负载均衡的功能,或者应用程序希望知道属于同组服务的其他实例。K8s提供了Headless Service(无头服务)来实现这种功能,即不为Service设置ClusterIP(入口IP地址),仅通过Label Selector将后端的Pod列表返回给调用的客户端。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
  clusterIP: None
  selector:
    app: nginx

Service不再有一个特定的ClusterIP地址,对其进行访问将获得包含Label "app=nginx"的全部Pod列表,然后客户端程序自行决定如何处理这个Pod列表。例如StatefulSet就是使用Headless Service为客户端返回多个服务地址。

对于去中心化类的应用集群,Headless Service将非常有用。下面以搭建Cassandra集群为例,看看如何通过对Headless Service的巧妙使用,自动实现应用集群的创建。

  • Apache Cassandra是一套开源分布式NoSQL数据库系统,主要特点为不是单个数据库,而是由一组数据库节点共同构成的一个分布式的集群数据库。由于Cassandra使用的是"去中心化"模式,所以在集群里的一个节点启动后,需要一个途径获知集群中新节点的加入。Cassandra使用了Seed(种子)来完成在集群中节点之间的相互查找和通信。
  • 通过对Headless Service的使用,实现了Cassandra各节点之间的相互查找和集群的自动搭建。
  • 主要步骤包括:自定义SeedProvicer。通过Headless Service自动查找后端Pod。自动添加新Cassandra节点。

1.自定义seedProvider
使用一个自定义的SeedProvider类来完成新节点的查询和添加,类名为io.k8s.cassandra.KubernetesSeedProvider。

在KubernetesSeedProvider类中,通过查询环境变量CASSANDRA_SERVICE的值来获得服务的名称。就要求Service需要在Pod之前创建出来,如果已经创建好DNS服务,也可以直接使用服务的名称而无须使用环境变量。

2.通过Service动态查找Pod

  • Service通常用作一个负载均衡器,供K8s集群中其他应用(Pod)对属于该Service的一组Pod进行访问。
  • 由于Pod的创建和销毁都会实时更新Service的Endpoints数据,所以可以动态地对Service的后端Pod进行查询。
  • Cassandra的去中心化设计使得Cassandra集群中的一个Cassandra实例(节点)只需要查询到其他节点,即可自动组成一个集群,正好可以使用Service的这个特性查询到新增的节点。

定义cassandra service
cassandra-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    name: cassandra
  name: cassandra
spec:
  ports:
 - port: 9042
  selector:
    name: cassandra
  • 在Service的定义中指定Label Selector为name=cassandra

使用kubectl create命令创建这个Service:

kubectl create -f cassandra-service.yaml
service "cassandra" created

创建Cassandra Pod

apiVersion: v1
kind: ReplicationController
metadata:
  labels:
    name: cassandra
  name: cassandra
spec:
  replicas: 1
  selector:
    name: cassandra
  template:
    metadata:
      labels:
        name: cassandra
    spec:
      containers:
      - command:
          - /run.sh
        resources:
          limits:
            cpu: 0.5
        env:
          - name: MAX_HEAP_SIZE
            value: 512M
          - name: HEAP_NEWSIZE
            value: 100M
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
        image: gcr.io/google_containers/cassandra:v5
        name: cassandra
        ports:
          - containerPort: 9042
            name: cq1
          - containerPort: 9160
            name: thrift
        volumeMounts:
          - mountPath: /cassandra_data
            name: data
     volumes:
       - name: data
         emptyDir: {}
kubectl create -f cassandra-rc.yaml
replicationcontroller "cassandra" created

一个Cassandra Pod运行起来了,但还没有组成Cassandra集群。

Cassandra集群中新节点的自动添加
使用K8s提供的Scale(扩容和缩容)机制对Cassandra集群进行扩容。

kubectl scale rc cassandra --replicas=2
replicationcontroller "cassandra" scaled

查看Pod,RC创建并启动了一个新的Pod

kubectl get pods -l="name=cassandra"

使用Cassandra提供的nodetool工具对任一cassandra实例(Pod)进行访问来验证Cassandra集群的状态。

kubectl exec -ti cassandra -- nodetool status

可以看到Cassandra集群中有两个节点处于正常运行状态(Up and Normal,UN)。该结果中的两个IP地址为两个Cassandra Pod的IP地址。

内部的过程为:

  • 每个Cassandra节点Pod通过API访问K8s Master,查询名为cassandra的Service的Endpoints(即Cassandra节点),若发现有新节点加入,就进行添加操作,最后成功组成了一个Cassandra集群。

再增加两个Cassandra实例:

kubectl scale rc cassandra --replicas=4

用nodetool工具查看Cassandra集群状态:

kubectl exec -ti cassandra -- nodetool status

可以看到4个Cassandra节点都加入Cassandra集群中。

可以通过查看Cassandra Pod的日志来看到新节点加入集群的记录:

kubectl logs cassandra

你可能感兴趣的:(日常分享专栏,kubernetes,K8s系列,深入掌握Service)