美文网首页Kubernetes
kubernetes Service

kubernetes Service

作者: 码二哥 | 来源:发表于2018-10-16 16:57 被阅读133次

    kubernetes Service

    参考文献:
    https://blog.csdn.net/watermelonbig/article/details/79693962

    A、k8s的Service定义了一个服务的访问入口地址,前端的应用通过这个入口地址访问其背 后的一组由Pod副本组成的集群实例,来自外部的访问请求被负载均衡到后端的各个容器应用上。
    B、Service与其后端Pod副本集群之间则是通过Label Selector来实现对接的;
    C、而RC的作用相当于是保证Service的服务能力和服务质量始终处于预期的标准。Service 定义可以基于 POST 方式,请求 apiserver 创建新的实例。
    D、一个 Service 在 Kubernetes 中是一个 REST 对象。
    本文对Service的使用进行详细说明,包括Service的负载均衡、外网访问、DNS服务的搭建、Ingress7层路由机制等。

    1、Service 定义详解

    1.1 yaml格式的Service定义文件的完整内容

    apiVersion: v1
    kind: Service
    matadata:
      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
    

    1.2 对Service定义文件中各属性的说明表

    属性名称 取值类型 是否必须 取值说明
    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,表示将同一个源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 外部负载均衡器的主机名

    2 Service, RC, Pod 架构层次关系

    image

    3 VIP和Service 代理

    3.1 kube-proxy

    运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,它会负责把对Service的请求转发到后端的某个Pod实例上并在内部实现服务的负载均衡与会话保持机制。Service不是共用一个负载均衡器的IP,而是被分配了一个全局唯一的虚拟IP地址,称为Cluster IP。
    在Service的整个生命周期内,它的Cluster IP不会改变。
    kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。
    在k8s v1.2版本之前默认使用userspace提供vip代理服务,从 Kubernetes v1.2 起,默认是使用 iptables 代理。

    3.2 iptables 代理模式

    这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会创建相关 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。 对于每个 Endpoints 对象,它也会创建 iptables 规则,这个规则会选择一个 backend Pod。
    默认的策略是,随机选择一个 backend。 实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 "ClientIP" (默认值为 "None")。
    和 userspace 代理类似,网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。 这应该比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始选择的 Pod 没有响应,iptables 代理能够自动地重试另一个 Pod,所以它需要依赖 readiness probes。

    image

    4、 发布服务---type类型

    对一些应用希望通过外部(Kubernetes 集群外部)IP 地址暴露 Service。
    Kubernetes ServiceTypes 允许指定一个需要的类型的 Service,默认是 ClusterIP 类型。
    Type 的取值以及行为如下:

    • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
    • NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求<NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务。
    • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。
    • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

    k8s中有3种IP地址:

    • Node IP: Node节点的IP地址,这是集群中每个节点的物理网卡的IP地址;
    • Pod IP: Pod的IP地址,这是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络;
    • Cluster IP:Service 的IP地址,这也是一个虚拟的IP,但它更像是一个“伪造”的IP地址,因为它没有一个实体网络对象,所以无法响应ping命令。它只能结合Service Port组成一个具体的通信服务端口,单独的Cluster IP不具备TCP/IP通信的基础。

    在k8s集群之内,Node IP网、Pod IP网与Cluster IP网之间的通信采用的是k8s自己设计的一种编程实现的特殊的路由规则,不同于常见的IP路由实现

    5、 服务发现

    Kubernetes 支持2种基本的服务发现模式 —— 环境变量和 DNS。
    环境变量
    当 Pod 运行在 Node 上,kubelet 会为每个活跃的 Service 添加一组环境变量。 它同时支持 Docker links兼容 变量、简单的 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 变量,这里 Service 的名称需大写,横线被转换成下划线。
    举个例子,一个名称为 "redis-master" 的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:

    REDIS_MASTER_SERVICE_HOST=10.0.0.11
    REDIS_MASTER_SERVICE_PORT=6379
    REDIS_MASTER_PORT=tcp://10.0.0.11:6379
    REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
    REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
    REDIS_MASTER_PORT_6379_TCP_PORT=6379
    REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
    

    这意味着需要有顺序的要求 —— Pod 想要访问的任何 Service 必须在 Pod 自己之前被创建,否则这些环境变量就不会被赋值。DNS 并没有这个限制。
    DNS
    一个强烈推荐的集群插件 是 DNS 服务器。 DNS 服务器监视着创建新 Service 的 Kubernetes API,从而为每一个 Service 创建一组 DNS 记录。 如果整个集群的 DNS 一直被启用,那么所有的 Pod 应该能够自动对 Service 进行名称解析。
    例如,有一个名称为 "my-service" 的 Service,它在 Kubernetes 集群中名为 "my-ns" 的 Namespace 中,为 "my-service.my-ns" 创建了一条 DNS 记录。 在名称为 "my-ns" 的 Namespace 中的 Pod 应该能够简单地通过名称查询找到 "my-service"。 在另一个 Namespace 中的 Pod 必须限定名称为 "my-service.my-ns"。 这些名称查询的结果是 Cluster IP。
    Kubernetes 也支持对端口名称的 DNS SRV(Service)记录。 如果名称为 "my-service.my-ns" 的 Service 有一个名为 "http" 的 TCP 端口,可以对 "_http._tcp.my-service.my-ns" 执行 DNS SRV 查询,得到 "http" 的端口号。
    Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。 更多信息可以查看DNS Pod 和 Service。
    Kubernetes 从 1.3 版本起, DNS 是内置的服务,通过插件管理器 集群插件 自动被启动。Kubernetes DNS 在集群中调度 DNS Pod 和 Service ,配置 kubelet 以通知个别容器使用 DNS Service 的 IP 解析 DNS 名字。

    6、 Service的基本用法

        一般来说,对外提供服务的应用程序需要通过某种机制来实现,对于容器应用最简便的方式就是通过TCP/IP机制及监听IP和端口号来实现。
    创建一个基本功能的Service
    (1) 例如,我们定义一个提供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 
    

    获取Pod的IP地址:

    [root@master service]# kubectl get pods -l app=webapp -o yaml| grep podIP | grep -v cni
        podIP: 192.168.2.94
        podIP: 192.168.1.73
    [root@master service]# 
    

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

    [root@master service]# curl 192.168.2.94:8080
    
    
    
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <title>Apache Tomcat/8.5.34</title>
            <link href="favicon.ico" rel="icon" type="image/x-icon" />
            <link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
            <link href="tomcat.css" rel="stylesheet" type="text/css" />
        </head>
    ........
    

    说明可以直接通过podIP进行访问
    直接通过Pod的IP地址和端口号可以访问容器内的应用服务,但是Pod的IP地址是不可靠的,例如Pod所在的Node发生故障,Pod将被k8s重新调度到另一台Node。Pod的IP地址将发生变化,更重要的是,如果容器应用本身是分布式的部署方式,通过多个实例共同提供服务,就需要在这些实例的前端设置一个负载均衡器来实现请求的分发。kubernetes中的Service就是设计出来用于解决这些问题的核心组件。

    (2) 为了让客户端应用能够访问到两个Tomcat Pod 实例,需要创建一个Service来提供服务
    k8s提供了一种快速的方法,即通过kubectl expose命令来创建:

    [root@master service]# kubectl expose deployment webapp
    service/webapp exposed
    

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

    [root@master service]# kubectl get svc
    NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP    6d10h
    webapp       ClusterIP   10.103.4.220   <none>        8080/TCP   5s
    [root@master service]# 
    

    接下来,我们就可以通过Service的IP地址和Service的端口号访问该Service了:

    image

    这里,对Service地址10.103.4.220:8080的访问被自动负载分发到了后端两个Pod之一。

    (3) 除了使用kubectl expose命令创建Service,我们也可以通过配置文件定义Service,再通过kubectl create命令进行创建
    例如前面的webapp就用,我们可以设置一个Service:
    webapp-svc.yaml

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

    本例中ports定义部分指定了Service所需的虚拟端口号为8081,由于与Pod容器端口号8080不一样,所以需要在通过targetPort来指定后端Pod的端口
    selector定义部分设置的是后端Pod所拥有的label: app=webapp
    (4) 目前kubernetes提供了两种负载分发策略:RoundRobin和SessionAffinity

    • RoundRobin:轮询模式,即轮询将请求转发到后端的各个Pod上
    • SessionAffinity:基于客户端IP地址进行会话保持的模式,第一次客户端访问后端某个Pod,之后的请求都转发到这个Pod上
      默认是RoundRobin模式。

    7、 多端口Service

    有时候,一个容器应用提供多个端口服务,可以按下面这样定义:

    apiVersion: v1
    kind: Service
    metadata:
      name: webapp
    spec:
      ports:
      - name: web
        port: 8080
        targetPort: 8080
      - name: management
        port: 8005
        targetPort: 8005
      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: 10.10.10.100
      ports:
      - name: dns
        port: 53
        protocol: UDP
      - name: dns-tcp
        port: 53
        protocol: TCP
    

    8、 Headless Service

    有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。
    这个选项允许开发人员自由寻找他们自己的方式,从而降低与 Kubernetes 系统的耦合性。 应用仍然可以使用一种自注册的模式和适配器,对其它需要发现机制的系统能够很容易地基于这个 API 来构建。
    对这类 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。仅依赖于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为客户端返回多个服务地址。
    Lable Secector:
    配置 Selector:对定义了 selector 的 Headless Service,Endpoint 控制器在 API 中创建了 Endpoints 记录,并且修改 DNS 配置返回 A 记录(地址),通过这个地址直接到达 Service 的后端 Pod上。
    不配置 Selector:对没有定义 selector 的 Headless Service,Endpoint 控制器不会创建 Endpoints 记录。

    9、 外部服务Service——没有 selector 的 Service

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

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

    Servcie 抽象了该如何访问 Kubernetes Pod,但也能够抽象其它类型的 backend,例如:
    希望在生产环境中使用外部的数据库集群。
    希望服务指向另一个 Namespace 中或其它集群中的服务。
    正在将工作负载同时转移到 Kubernetes 集群和运行在 Kubernetes 集群之外的 backend。
    由于这个 Service 没有 selector,就不会创建相关的 Endpoints 对象。可以手动将 Service 映射到指定的 Endpoints:

    kind: Endpoints
    apiVersion: v1
    metadata:
      name: my-service
    subsets:
      - addresses:
          - ip: 1.2.3.4
        ports:
          - port: 9376
    

    注意:Endpoint IP 地址不能是 loopback(127.0.0.0/8)、 link-local(169.254.0.0/16)、或者 link-local 多播(224.0.0.0/24)。
    访问没有 selector 的 Service,与有 selector 的 Service 的原理相同。请求将被路由到用户定义的 Endpoint(该示例中为 1.2.3.4:9376)。
    ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。 相反地,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

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

    当查询主机 my-service.prod.svc.CLUSTER时,集群的 DNS 服务将返回一个值为 my.database.example.com 的 CNAME 记录。 访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。 如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type。

    10、集群外部访问Pod或Service的方法

    由于Pod和Service是k8s集群范围内的虚拟概念,所以集群外的客户端系统无法通过Pod的IP地址或者Service的虚拟IP地址和虚拟端口号访问到它们。
    为了让外部客户端可以访问这些服务,可以将Pod或Service的端口号映射到宿主机,以使得客户端应用能够通过物理机访问容器应用。

    10.1、将容器应用的端口号映射到物理机

    (1) 通过设置容器级别的hostPort,将容器应用的端口号映射到物理机上
    文件pod-hostport.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      name: webapp
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: tomcat
        ports:
        - containerPort: 8080
          hostPort: 8081
    
    

    hostPort的值可以跟containerPort的值一样
    通过kubectl create创建这个Pod:

    kubectl create -f pod-hostport.yaml
    

    通过物理机的IP地址和8081端口号访问Pod内的容器服务:

    curl 172.16.91.137:8081
    

    (2) 通过设置Pod级别的hostNetwork=true,该Pod中所有容器的端口号都将被直接映射到物理机上
    设置hostWork=true时需要注意,在容器的ports定义部分如果不指定hostPort,则默认hostPort等于containerPort,如果指定了hostPort,则hostPort必须等于containerPort的值。
    文件pod-hostnetwork.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      name: webapp-hostnetwork
      labels:
        app: webapp-hostnetwork
    spec:
      hostNetwork: true
      containers:
      - name: webapp-hostnetwork
        image: tomcat
        imagePullPolicy: Never
        ports:
        - containerPort: 8080
    
    

    创建这个Pod:

    kubectl create -f pod-hostnetwork.yaml
    
    [root@master pod]# kubectl get pod -owide
    NAME                      READY   STATUS    RESTARTS   AGE   IP              NODE     NOMINATED NODE
    webapp-77df6fff64-dhczl   1/1     Running   1          14h   192.168.2.95    slave2   <none>
    webapp-77df6fff64-kpl6w   1/1     Running   1          14h   192.168.1.74    slave1   <none>
    webapp-hostnetwork        1/1     Running   0          7s    172.16.91.136   slave1   <none>
    
    

    通过物理机的IP地址和8080端口访问Pod的容器服务

    curl slave1:8080  
    

    10.2 将Service的端口号映射到物理机

    (1) 通过设置nodePort映射到物理机,同时设置Service的类型为NodePort
    文件webapp-svc-nodeport.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: webapp-nodeport
    spec:
      type: NodePort
      ports:
      - port: 8090
        targetPort: 8080
        nodePort: 8090
      selector:
        app: webapp
    

    创建这个Service:

    kubectl create -f webapp-svc-nodeport.yaml
    
    [root@master service]# kubectl get svc
    NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
    kubernetes        ClusterIP   10.96.0.1      <none>        443/TCP          7d1h
    webapp            ClusterIP   10.103.4.220   <none>        8080/TCP         14h
    webapp-nodeport   NodePort    10.99.248.88   <none>        8090:31090/TCP   6s
    
    

    通过物理机的IP和端口访问:(4种方式访问)

    #Service的IP,即ClusterIP
    curl 10.99.248.88:8090 
    #master的IP,
    curl master:31090 
    curl slave1:31090 
    curl slave2:31090 
    

    也就是说,只要是nodePort端口,就会在物理机上开始监听端口号

    image
    image
    如果访问不通,查看下物理机的防火墙设置
    同样,对该Service的访问也将被负载分发到后端多个Pod上

    (2) 通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址
    这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。
    status.loadBalancer.ingress.ip设置的146.148.47.155为云服务商提供的负载均衡器的IP地址。对该Service的访问请求将会通过LoadBalancer转发到后端的Pod上,负载分发的实现方式依赖云服务商提供的LoadBalancer的实现机制。

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      selector:
        app: Myapp
      ports:
      - protocol: TCP
        port: 80
        targetPort: 9376
        nodePort: 30061
      clusterIP: 10.0.171.239
      loadBalancerIP: 78.11.24.19
      type: LoadBalancer
    status:
      loadBalancer:
        ingree:
        - ip: 146.148.47.155
    

    相关文章

      网友评论

        本文标题:kubernetes Service

        本文链接:https://www.haomeiwen.com/subject/acwhzftx.html