服务发现与负载均衡

Kubernetes在设计之初就充分考虑了针对容器的服务发现与负载均衡机制,提供了Service资源,并通过kube-proxy配合cloud provider来适应不同的应用场景。随着kubernetes用户的激增,用户场景的不断丰富,又产生了一些新的负载均衡机制。目前,kubernetes中的负载均衡大致可以分为以下几种机制,每种机制都有其特定的应用场景:

  • 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

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

Service有四种类型:

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

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

Service定义

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

apiVersion: v1
kind: Service
metadata:
  labels:
    run: nginx
  name: nginx
  namespace: default
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: nginx
  sessionAffinity: None
  type: ClusterIP
# service自动分配了Cluster IP 10.0.0.108
$ kubectl get service nginx
NAME      CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx     10.0.0.108   <none>        80/TCP    18m
# 自动创建的endpoint
$ kubectl get endpoints nginx
NAME      ENDPOINTS       AGE
nginx     172.17.0.5:80   18m
# Service自动关联endpoint
$ kubectl describe service nginx
Name:            nginx
Namespace:        default
Labels:            run=nginx
Annotations:        <none>
Selector:        run=nginx
Type:            ClusterIP
IP:            10.0.0.108
Port:            <unset>    80/TCP
Endpoints:        172.17.0.5:80
Session Affinity:    None
Events:            <none>

不指定Selectors的服务

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

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

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
---
kind: Endpoints
apiVersion: v1
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 1.2.3.4
    ports:
      - port: 9376

(2)通过DNS转发,在service定义中指定externalName。此时DNS服务会给<service-name>.<namespace>.svc.cluster.local创建一个CNAME记录,其值为my.database.example.com。并且,该服务不会自动分配Cluster IP,需要通过service的DNS来访问(这种服务也称为Headless Service)。

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

Headless服务

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

  • 不指定Selectors,但设置externalName,即上面的(2),通过CNAME记录处理
  • 指定Selectors,通过DNS A记录设置后端endpoint列表
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  clusterIP: None
  ports:
  - name: tcp-80-80-3b6tl
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
  namespace: default
spec:
  replicas: 2
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:latest
        imagePullPolicy: Always
        name: nginx
        resources:
          limits:
            memory: 128Mi
          requests:
            cpu: 200m
            memory: 128Mi
      dnsPolicy: ClusterFirst
      restartPolicy: Always
# 查询创建的nginx服务
$ kubectl get service --all-namespaces=true
NAMESPACE     NAME         CLUSTER-IP      EXTERNAL-IP      PORT(S)         AGE
default       nginx        None            <none>           80/TCP          5m
kube-system   kube-dns     172.26.255.70   <none>           53/UDP,53/TCP   1d
$ kubectl get pod 
NAME                       READY     STATUS    RESTARTS   AGE       IP           NODE
nginx-2204978904-6o5dg     1/1       Running   0          14s       172.26.2.5   10.0.0.2
nginx-2204978904-qyilx     1/1       Running   0          14s       172.26.1.5   10.0.0.8
$ dig @172.26.255.70  nginx.default.svc.cluster.local 
;; ANSWER SECTION:
nginx.default.svc.cluster.local. 30 IN    A    172.26.1.5
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加上annotation service.beta.kubernetes.io/external-traffic=OnlyLocal,让service只代理本地endpoint的请求(如果没有本地endpoint则直接丢包),从而保留源IP。
  • LoadBalancer Service:源IP会做SNAT,server pod看到的源IP是Node IP。在GKE/GCE中,添加annotation service.beta.kubernetes.io/external-traffic=OnlyLocal后可以自动从负载均衡器中删除没有本地endpoint的Node。

工作原理

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

Ingress Controller

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

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

Ingress就是为了解决这些限制而引入的新资源,主要用来将服务暴露到cluster外面,并且可以自定义服务的访问策略。比如想要通过负载均衡器实现不同子域名到不同服务的访问:

foo.bar.com --|                 |-> foo.bar.com s1:80
              | 178.91.123.132  |
bar.foo.com --|                 |-> bar.foo.com s2:80

可以这样来定义Ingress:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          serviceName: s1
          servicePort: 80
  - host: bar.foo.com
    http:
      paths:
      - backend:
          serviceName: s2
          servicePort: 80

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

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

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

Service Load Balancer

在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

虽然Kubernetes提供了丰富的负载均衡机制,但在实际使用的时候,还是会碰到一些复杂的场景是它不能支持的,比如

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

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

参考资料

results matching ""

    No results matching ""