K8s Service

作者: AlienPaul | 来源:发表于2020-01-15 17:00 被阅读0次

    什么是service

    pod不是永恒存在的,pod会随时被创建,同时pod也会被随时销毁(运行出错,超过资源限制等原因)。pod不可能被复活,因此pod被销毁和重建之后的IP地址是不同的。
    其他依赖该pod的服务不可能去维护这些pod的IP地址信息。这里我们引入的service,service保存了我们如何访问一组pod的方式。这一组pod通过pod selector来确定。无论这些pod怎样创建和销毁,他们的label不变。service可以动态维护这些pod的真实IP(通过endpoint对象)。所以说,service解耦了pod和依赖这些pod的应用。

    定义一个service

    创建service的描述文件:

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

    这里我们定义了一个service的描述文件。service的名字为my-service。service指向的pod需要具有app: MyApp标签。可以使用80端口访问每个pod的9376端口。这个service拥有一个cluster IP。这个IP只能在kubernetes集群内部访问。

    具有selector的service在创建的时候会自动创建一个同名的endpoint。endpoint维护了具有这些标签的一组pod的真正的IP。如果这些pod的IP发生变化,endpoint中的IP也会随着变化。

    没有pod selector的service

    如下情况我们需要使用没有pod selector的service:

    • 使用集群外部的数据库
    • 要创建的service指向位于另一个namespace,或者是另一个kubernetes集群的service

    编写描述文件:

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

    只有这样是不够的,service需要有一个对应的endpoint对象。创建没有selector的service并不会随之创建一个endpoint。这时候我们需要手工创建endpoint。endpoint的描述文件如下:

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

    service proxy

    service使用kube-proxy实现

    不使用轮询DNS方式的原因

    • 不少DNS的实现不支持TTL(Time To Live)。无法在pod IP发生变化的时候及时改变缓存。
    • 一部分应用在DNS查询之后便永久保存,再也不会更新。
    • 即便是支持DNS再次解析,我们需要降低DNS的TTL。越低的TTL会导致DNS解析服务的频繁运行,增加系统的负载。

    user space proxy 模式

    kube-proxy 负责监视service和endpoint的创建和删除。对于每个service,在本地节点上随机开启一个端口(代理端口)。任何连接到该端口的连接会被代理到service后端一组pod中的一个。kube-proxy使用SessionAffinity配置项来决定使用哪一个后端pod。

    user space模式使用iptables规则,把目标为service的cluster IP和端口的请求发送到代理端口。

    iptables proxy 模式

    iptables使用规则配置,将访问service的cluster IP和port的连接重定向到某一个后端pod。默认来说,kube-proxy随机选择后端pod提供服务。

    iptables模式系统开销较小。

    如果连接到的第一个被选定的pod没有响应,此连接会失败。这个和userspace模式不同。userspace模式中,如果连接到第一个pod失败,系统会自动重试连接另一个后端pod。

    可以使用readness probe来确保pod是否正常工作。如果某个podreadness probe 失败,这个pod会从endpoint中移除,网络请求不会再转发到这个pod中,直到readness probe恢复到success状态。

    IPVS proxy 模式

    ipvs 模式kube-proxy使用netlink接口创建IPVS规则。IPVS基于钩子函数,使用内核空间的hash表作为底层数据结构。因此IPVS相比iptables具有更低的延迟,吞吐量更高。同时同步代理规则的时候也更快。

    IPVS重定向网络通信具有如下策略:

    • rr: round-robin 轮询
    • lc: least connection (smallest number of open connections) 优先连接数最少
    • dh: destination hashing 目标hash
    • sh: source hashing 源hash
    • sed: shortest expected delay 延迟最小
    • nq: never queue 不排队

    如果需要相同客户端的请求被重定向到相同的pod,需要配置service.spec.sessionAffinityClientIP(默认为None)。我们还可以设置pod和client的最大粘性时间service.spec.sessionAffinityConfig.clientIP.timeoutSeconds。默认为10800,即3小时。如果3小时内client没有访问pod,那么该client下次访问service的时候将重新指定新的pod,原先的session会失效。

    多端口服务

    service可以暴露多个port。如果使用多个port,必须为每一个port设置name。描述文件如下所示:

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      selector:
        app: MyApp
      ports:
        - name: http
          protocol: TCP
          port: 80
          targetPort: 9376
        - name: https
          protocol: TCP
          port: 443
          targetPort: 9377
    

    显式指定service的IP

    可以设定spec.clusterIP配置项。但是IP必须位于API server的service-cluster-ip-range CIDR范围中。

    service发现

    环境变量方式

    Kubernetes自动为每一个运行的pod中加入各个service环境变量。这些service的环境变量的格式为

    • {SVCNAME}_SERVICE_HOST
    • {SVCNAME}_SERVICE_PORT

    以redis-master服务为例,对应的环境变量为:

    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之前创建service。否则pod中将没有这个service的环境变量。如果使用DNS方式的话不需要担心这个创建顺序问题。

    DNS方式

    集群的DNS服务,例如CoreDNS会监听service,创建service对应的DNS入口。

    如果在命名空间my-ns定义了一个服务my-service,自动创建的DNS入口为my-service.my-ns。集群内任何pod可以通过此DNS访问这个服务。如果是和该服务位于同一个命名空间的pod,可以省略DNS中的命名空间部分,使用my-service这个DNS入口访问my-service服务。

    DNS支持命名端口的方式。如果一个端口命名为http,使用的是TCP协议,可以通过_http._tcp.my-service.my-ns这条DNS解析到它的IP和端口号。

    Headless Service

    特点:

    1. 禁用掉了service的负载均衡功能。
    2. 没有指定service的clusterIP。
    3. service不使用kube-proxy代理。

    适用场景:

    1. 需要不经过代理直接访问pod。
    2. 使用服务发现功能,例如部署zookeeper等。

    配置方式:
    设置spec.clusterIPNone

    DNS自动配置的方式和是否配置了pod selector有关。

    • 配置了pod selector:endpoints会被创建,DNS直接指向后台pod的IP
    • 没有配置pod selector:endpoints不会被创建。DNS服务会查找: 1. CNAME记录(ExternalName-type Services)2. 和service同名的endpoints

    公开service(允许service在集群外访问)

    通过设置服务类型(ServiceType)来实现。
    service type有如下类型:

    • clusterIP:一个只有在集群内部可访问的虚拟的IP。这个是Service Type的默认值。
    • NodePort:使用每个节点的一个静态端口来访问service。集群外可使用<NodeIP>:<NodePort>形式访问。
    • LoadBalancer:负载均衡器,使用云提供商的负载均衡器访问服务。
    • ExternalName:映射为一个自定义DNS名称,适用于访问外部服务。

    除此之外还可以使用Ingress方式,但是Ingress不是ServiceType。

    NodePort

    NodePort范围从--service-node-port-rangeflag中随机选择(默认为30000-32767)。分配的端口号在.spec.ports[*].nodePort字段中。

    可以使用kube-proxy的--nodeport-addresses配置指定nodePort IP的范围。

    可以通过指定nodePort字段的内容,来使得NodePort使用一个固定的IP。

    NodePort的使用例子:

    apiVersion: v1
    kind: Service
    metadata:
        name: kubia-nodeport
    spec:
        type: NodePort
        ports:
            - port: 80
              targetPort: 8080
              nodePort: 30123
        selector:
            app: kubia
    

    LoadBalancer

    自己部署的集群中无LoadBalancer,此部分省略。

    ExternalName

    映射service到一个DNS名称。

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

    External IPs

    使用外部IP访问服务。要求可以通过外部IP访问到集群节点。

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      selector:
        app: MyApp
      ports:
        - name: http
          protocol: TCP
          port: 80
          targetPort: 9376
      externalIPs:
        - 80.11.12.10
    

    Service 使用Session Affinity

    session affinity,同一个client发出的请求会发送给同一个pod处理。配置方法如下:

    apiVersion: v1
    kind: Service
    spec:
        sessionAffinity: ClientIP
        ...
    

    使用命名端口

    可以在Pod中为端口命令,在service中使用。

    步骤如下所示:

    1. 在pod中给端口命名
    kind: Pod
    spec:
        containers:
        - name: kubia
          ports:
          - name: http
            containerPort: 8080
          - name: https
            containerPort: 8443
    
    1. 创建service时使用命名端口(targetPort中使用)
    apiVersion: v1
    kind: Service
    spec:
        ports:
        - name: http
          port: 80
          targetPort: http
        - name: https
          port: 443
          targetPort: https
    

    Service关联k8s集群外部的服务

    把集群外部的某一服务的入口做成k8s的endpoint。创建service的时候如果不指定label selector,endpoints就不会自动创建。

    下面举一个例子。我们需要在k8s集群中访问集群外部11.11.11.11和22.22.22.22的80端口这个服务。

    这里我们需要手工创建service和endpoint:

    apiVersion: v1
    kind: Service
    metadata:
        name: external-service
    spec:
        ports:
        - port: 80
    
    apiVersion: v1
    kind: Endpoints
    metadata:
        name: external-service
    subsets:
        - addresses:
            - ip: 11.11.11.11
            - ip: 22.22.22.22
        ports:
            - port: 80
    

    完成之后,集群内部可以通过访问external-service来实现访问集群外部11.11.11.1122.22.22.22

    减少网络连接的hop数量

    可以通过配置service,只把连接重定向到接收这个连接的node上运行的pod。

    方法如下:

    spec:
        externalTrafficPolicy: Local
        ...
    

    缺点:如果这个node上没有运行该service对应的pod,连接会挂起。

    相关文章

      网友评论

        本文标题:K8s Service

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