服务发现与负载均衡

    • Service:直接用 Service 提供 cluster 内部的负载均衡,并借助 cloud provider 提供的 LB 提供外部访问
    • Ingress Controller:还是用 Service 提供 cluster 内部的负载均衡,但是通过自定义 LB 提供外部访问
    • Service Load Balancer:把 load balancer 直接跑在容器中,实现 Bare Metal 的 Service Load Balancer
    • Custom Load Balancer:自定义负载均衡,并替代 kube-proxy,一般在物理部署 Kubernetes 时使用,方便接入公司已有的外部服务

    Service 是对一组提供相同功能的 Pods 的抽象,并为它们提供一个统一的入口。借助 Service,应用可以方便的实现服务发现与负载均衡,并实现应用的零宕机升级。Service 通过标签来选取服务后端,一般配合 Replication Controller 或者 Deployment 来保证后端容器的正常运行。这些匹配标签的 Pod IP 和端口列表组成 endpoints,由 kube-proxy 负责将服务 IP 负载均衡到这些 endpoints 上。

    Service 有四种类型:

    • ClusterIP:默认类型,自动分配一个仅 cluster 内部可以访问的虚拟 IP
    • NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 来访问该服务
    • LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到 <NodeIP>:NodePort
    • ExternalName:将服务通过 DNS CNAME 记录方式转发到指定的域名(通过 spec.externlName 设定)。需要 kube-dns 版本在 1.7 以上。

    另外,也可以将已有的服务以 Service 的形式加入到 Kubernetes 集群中来,只需要在创建 Service 的时候不指定 Label selector,而是在 Service 创建好后手动为其添加 endpoint。

    Service 的定义也是通过 yaml 或 json,比如下面定义了一个名为 nginx 的服务,将服务的 80 端口转发到 default namespace 中带有标签 run=nginx 的 Pod 的 80 端口

    1. # service 自动分配了 Cluster IP 10.0.0.108
    2. $ kubectl get service nginx
    3. NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    4. nginx 10.0.0.108 <none> 80/TCP 18m
    5. # 自动创建的 endpoint
    6. $ kubectl get endpoints nginx
    7. NAME ENDPOINTS AGE
    8. nginx 172.17.0.5:80 18m
    9. # Service 自动关联 endpoint
    10. $ kubectl describe service nginx
    11. Name: nginx
    12. Labels: run=nginx
    13. Annotations: <none>
    14. Selector: run=nginx
    15. Type: ClusterIP
    16. IP: 10.0.0.108
    17. Port: <unset> 80/TCP
    18. Endpoints: 172.17.0.5:80
    19. Events: <none>

    在创建 Service 的时候,也可以不指定 Selectors,用来将 service 转发到 kubernetes 集群外部的服务(而不是 Pod)。目前支持两种方法

    (1)自定义 endpoint,即创建同名的 service 和 endpoint,在 endpoint 中设置外部服务的 IP 和端口

    1. kind: Service
    2. apiVersion: v1
    3. metadata:
    4. name: my-service
    5. namespace: default
    6. spec:
    7. type: ExternalName
    8. externalName: my.database.example.com

    Headless 服务即不需要 Cluster IP 的服务,即在创建服务的时候指定 spec.clusterIP=None。包括两种类型

    • 不指定 Selectors,但设置 externalName,即上面的(2),通过 CNAME 记录处理
    • 指定 Selectors,通过 DNS A 记录设置后端 endpoint 列表
    1. # 查询创建的 nginx 服务
    2. $ kubectl get service --all-namespaces=true
    3. NAMESPACE NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    4. default nginx None <none> 80/TCP 5m
    5. kube-system kube-dns 172.26.255.70 <none> 53/UDP,53/TCP 1d
    6. $ kubectl get pod
    7. NAME READY STATUS RESTARTS AGE IP NODE
    8. nginx-2204978904-6o5dg 1/1 Running 0 14s 172.26.2.5 10.0.0.2
    9. nginx-2204978904-qyilx 1/1 Running 0 14s 172.26.1.5 10.0.0.8
    10. $ dig @172.26.255.70 nginx.default.svc.cluster.local
    11. nginx.default.svc.cluster.local. 30 IN A 172.26.2.5

    备注: 其中 dig 命令查询的信息中,部分信息省略

    保留源 IP

    各种类型的 Service 对源 IP 的处理方法不同:

    • ClusterIP Service:使用 iptables 模式,集群内部的源 IP 会保留(不做 SNAT)。如果 client 和 server pod 在同一个 Node 上,那源 IP 就是 client pod 的 IP 地址;如果在不同的 Node 上,源 IP 则取决于网络插件是如何处理的,比如使用 flannel 时,源 IP 是 node flannel IP 地址。
    • NodePort Service:默认情况下,源 IP 会做 SNAT,server pod 看到的源 IP 是 Node IP。为了避免这种情况,可以给 service 设置 spec.ExternalTrafficPolicy=Local (1.6-1.7 版本设置 Annotation service.beta.kubernetes.io/external-traffic=OnlyLocal),让 service 只代理本地 endpoint 的请求(如果没有本地 endpoint 则直接丢包),从而保留源 IP。
    • LoadBalancer Service:默认情况下,源 IP 会做 SNAT,server pod 看到的源 IP 是 Node IP。设置 service.spec.ExternalTrafficPolicy=Local 后可以自动从云平台负载均衡器中删除没有本地 endpoint 的 Node,从而保留源 IP。

    kube-proxy 负责将 service 负载均衡到后端 Pod 中,如下图所示

    Service - 图2

    Ingress Controller

    Service 虽然解决了服务发现和负载均衡的问题,但它在使用上还是有一些限制,比如

    - 只支持 4 层负载均衡,没有 7 层功能
    - 对外访问的时候,NodePort 类型需要在外部搭建额外的负载均衡,而 LoadBalancer 要求 kubernetes 必须跑在支持的 cloud provider 上面

    可以这样来定义 Ingress:

    1. apiVersion: extensions/v1beta1
    2. kind: Ingress
    3. metadata:
    4. name: test
    5. spec:
    6. rules:
    7. - host: foo.bar.com
    8. http:
    9. paths:
    10. - backend:
    11. serviceName: s1
    12. servicePort: 80
    13. - host: bar.foo.com
    14. http:
    15. paths:
    16. - backend:
    17. serviceName: s2

    注意 Ingress 本身并不会自动创建负载均衡器,cluster 中需要运行一个 ingress controller 来根据 Ingress 的定义来管理负载均衡器。目前社区提供了 nginx 和 gce 的参考实现。

    Traefik 提供了易用的 Ingress Controller,使用方法见 https://docs.traefik.io/user-guide/kubernetes/

    更多 Ingress 和 Ingress Controller 的介绍参见 。

    在 Ingress 出现以前,Service Load Balancer 是推荐的解决 Service 局限性的方式。Service Load Balancer 将 haproxy 跑在容器中,并监控 service 和 endpoint 的变化,通过容器 IP 对外提供 4 层和 7 层负载均衡服务。

    社区提供的 Service Load Balancer 支持四种负载均衡协议:TCP、HTTP、HTTPS 和 SSL TERMINATION,并支持 ACL 访问控制。

    Custom Load Balancer

    • 接入已有的负载均衡设备
    • 多租户网络情况下,容器网络和主机网络是隔离的,这样 kube-proxy 就不能正常工作

    这个时候就可以自定义组件,并代替 kube-proxy 来做负载均衡。基本的思路是监控 kubernetes 中 service 和 endpoints 的变化,并根据这些变化来配置负载均衡器。比如 weave flux、nginx plus、kube2haproxy 等。