kubernetes服务访问与负载均衡(一)

当使用kubernetes来发布应用时,Pod实例的生命周期是短暂,PodIP也是易变的。所以kubernetes中抽象了一个叫Service的概念,给应用提供一个固定的访问入口。

定义一个Service

假如现在:你有一个tomcat镜像,你用它创建了一个Deployment,发布了三个Pod实例;这组Pod暴露了8080端口,并且都拥有标签app=tomcat。那么,你可以创建如下的一个Service对象,然后在kubernetes集群的容器内或集群的Node上,都可以通过该 Service的ClusterIPPort 来访问这组Pod的8080端口:(具体原理见文章后面的实验部分)

kind: Service
apiVersion: v1
metadata:
  name: tomcat
spec:
  type: ClusterIP
  clusterIP: 10.254.11.12  # 可以指定,也可以随机生成
  selector:
    app: tomcat
  ports:
  - protocol: TCP
    port: 8080         # Service的Port
    targetPort: 8080         # Pod的Port

创建如上的一个Service对象后,Endpoints Controller也会自动创建一个与该Service同名的Endpoints。然后根据Service中的selector,把集群中label中含有app=tomcat 的Pod加入到该Endpoints中。

ClusterIP和Service代理

在上面的Service定义文件中,ClusterIP是一个虚拟IP。它既不是某个Pod的IP,也不是某个Node的IP。kubernetes集群中的每个节点上都运行一个kube-proxy,它负责监听ServiceEndpoints的创建与删除,并相应地修改Node上的iptables规则。

kube-proxy有三种代理模式:userspace、iptables、ipvs。从kubernetes v1.2开始,iptables成为默认的代理模式,在kubernetes v1.9-alpha中,加入了ipvs代理模式。本文主要介绍一下iptables代理模式。

Proxy-mode:iptables

在这种模式下,对于每一个Service,它会在Node上添加相应的iptables规则,将访问该Service的ClusterIP与Port的连接重定向到Endpoints中的某一个后端Pod。默认情况下,选择哪一个Pod是随机。

当选择的某个后端Pod没有响应时,iptables模式无法自动重新连接到另一个Pod,所以需要用户自已利用Pod的readness probeliveness probe 来规避这种情况。下图(截自官网)给出了通过ServiceIP:ServicePort 访问应用的原理:

这里写图片描述

服务发现

在上面的内容中,我们讲述了可以通过Service的IP和端口来访问应用。不过,在Pod中(注意在Node上不行),我们也可以通过Service的名字加端口来访问应用,它依赖于kubernetes的服务发现机制。

kubernetes的服务发现机制有两种:环境变量与DNS。

环境变量

当kubelet创建一个Pod时,对于每一个活动的Service,它会往该Pod中加入一组环境变量,形如:{SVCNAME}_SERVICE_HOST{SVCNAME}_SERVICE_PORT 等。其中服务名是大写的,破折号会被转化为下划线。

例如,有一个Service,名叫redis-master,暴露了6379的TCP端口,ClusterIP为 10.0.0.11,那么会产生下面的环境变量:

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中。DNS不会有这个限制。

DNS

DNS服务器是一个可选的插件(强烈推荐使用)。DNS服务器会通过Kubernetes的API监听Service的创建,然后为Service创建一组DNS记录。如果在整个集群中开启了DNS,则所有的Pod都能够自动地做Service的域名解析。
例如,如果在Kubernetes的命名空间my-ns中有一个叫my-service的Service,那么就会创建一条my-service.my-ns的DNS记录。命名空间my-ns中的Pod可以直接使用my-service做域名解析。其他命名空间中的Pod则使用my-service.my-ns做域名解析.解析出来的结果是Service的ClusterIP。

Service的类型

kubernetes的Service有四种类型:ClusterIPNodePortExternalNameLoadBalancer。本文主要介绍一下前两种。

ClusterIP

ClusterIP 是Service的默认类型,如果不设置type 字段,则默认为ClusterIP。这种类型的Service把服务暴露成一个内部的集群IP,只能在集群内访问(Pod和Node上都可以)。clusterIP 字段的值可以自动生成,也可以手动设置,不过一定要在规则的范围内,可通过kube-apiserver 的启动参数--service-cluster-ip-range 指定ClusterIP 的范围。

NodePort

如果将type字段设置为NodePort,则Kubernetes master将从启动参数--service-node-port-range配置的范围(默认值:30000-32767)分配端口,并且每个节点将该端口(每个节点上端口号相同)代理到你的Service。该端口可以在Service的spec.ports[*].nodePort字段进行查看。

如果你想要一个特定的端口号,你可以在该nodePort字段中指定一个值,系统将为你分配该端口,或者API事务将失败(也就是说,你需要注意自己可能发生的端口冲突)。你指定的值必须位于节点端口的配置范围内。

这种类型的Service也会有一个ClusterIP。这意味着,它有两种访问方式:通过ServiceIP:SerivcePort 访问,或者通过NodeIP:NodePort 访问。

实验一

发布一个ClusterIP类型的Service,查看在Node上通过ServiceIP:ServicePort 访问服务是如何被重定向到后端Pod的。

1、环境准备

搭建好集群,本文中的实验集群信息如下:

$ kubectl get node
NAME              STATUS    ROLES     AGE       VERSION
132.122.232.101   Ready     <none>    31d       v1.8.6
132.122.232.78    Ready     <none>    13d       v1.8.6

2、创建deployment

创建一个deployment,发布三个实例,yaml文件如下:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: tomcat
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tomcat
  template:
    metadata:
      labels:
        app: tomcat
    spec:
      containers:
      - name: container1
        image: 10.142.232.115:8021/public/tomcat:8
        ports:
        - containerPort: 8080

创建完deployment后,查看pod的情况如下:

$ kubectl get pod -o wide | grep tomcat
tomcat-b85775b9f-dzqj6   1/1       Running   0          25s       172.27.48.24   132.122.232.101
tomcat-b85775b9f-hxhh5   1/1       Running   0          26s       172.27.135.8   132.122.232.78
tomcat-b85775b9f-xgk7c   1/1       Running   0          26s       172.27.135.7   132.122.232.78

3、创建service,yaml文件如下

apiVersion: v1
kind: Service
metadata: 
  name: myservice
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: tomcat
  ports:
  - name: port1
    port: 8080
    targetPort: 8080

创建service,然后查看service的信息如下:

$ kubectl get service myservice
NAME        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
myservice   ClusterIP   10.254.197.221   <none>        8080/TCP   5m

查看自动生成的endpoints的信息如下:

$ kubectl get endpoints myservice
NAME        ENDPOINTS                                               AGE
myservice   172.27.135.7:8080,172.27.135.8:8080,172.27.48.24:8080   7m

4、查看node上iptables规则

注意:下面的内容需要对linux的iptables有一定的基础,可阅读相关文章

接下来我们来研究,当我们从Node上通过Service的IP与端口来访问服务时,是如何被转发到后端的某个Pod上去的。

出站数据流经过的链依次为:OUTPUT -> 主机内核路由 -> POSTROUTING 。如下图:

这里写图片描述

查看nat表(其他表的内容已省略,转发规则在这个表中)的OUTPUT链如下(其他行已省略,用...代替),发现引用了KUBE-SERVICES

$ iptables -t nat -L OUTPUT -n
Chain OUTPUT (policy ACCEPT)
target         prot opt source               destination         
...
KUBE-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
...

查看KUBE-SERVICES链的内容,如下:

$ iptables -t nat -L KUBE-SERVICES -n
Chain KUBE-SERVICES (2 references)
target                     prot opt source               destination         
...
KUBE-SVC-FPR422BQLZ77Y5BA  tcp  --  0.0.0.0/0            10.254.197.221       /* default/myservice:port1 cluster IP */ tcp dpt:8080
...

这条规则的意思是:协议为tcp、目的地址为10.254.197.221、目的端口为8080的包,都转到KUBE-SVC-FPR422BQLZ77Y5BA链。10.254.197.221:8080 就是Service的IP与端口。

我们接着查看KUBE-SVC-FPR422BQLZ77Y5BA链的内容,如下:

$ iptables -t nat -L KUBE-SVC-FPR422BQLZ77Y5BA -n 
Chain KUBE-SVC-FPR422BQLZ77Y5BA (1 references)
target                     prot opt source               destination         
KUBE-SEP-4NXNX7EV3NGCZ67M  all  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice:port1 */ statistic mode random probability 0.33332999982
KUBE-SEP-CG5TE3ILWI4BIYIZ  all  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice:port1 */ statistic mode random probability 0.50000000000
KUBE-SEP-6SZNOYYRDNRYRRHV  all  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice:port1 */

上面三条规则的意思是:随机选择其中的一条链。

我们查看上面三条链的内容如下:

$ iptables -t nat -L KUBE-SEP-4NXNX7EV3NGCZ67M -n 
Chain KUBE-SEP-4NXNX7EV3NGCZ67M (1 references)
target          prot opt source               destination         
KUBE-MARK-MASQ  all  --  172.27.135.7         0.0.0.0/0            /* default/myservice:port1 */
DNAT            tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice:port1 */ tcp to:172.27.135.7:8080

$ iptables -t nat -L KUBE-SEP-CG5TE3ILWI4BIYIZ -n 
Chain KUBE-SEP-CG5TE3ILWI4BIYIZ (1 references)
target          prot opt source               destination         
KUBE-MARK-MASQ  all  --  172.27.135.8         0.0.0.0/0            /* default/myservice:port1 */
DNAT            tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice:port1 */ tcp to:172.27.135.8:8080

$ iptables -t nat -L KUBE-SEP-6SZNOYYRDNRYRRHV -n 
Chain KUBE-SEP-6SZNOYYRDNRYRRHV (1 references)
target          prot opt source               destination         
KUBE-MARK-MASQ  all  --  172.27.48.24         0.0.0.0/0            /* default/myservice:port1 */
DNAT            tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice:port1 */ tcp to:172.27.48.24:8080

可以看出,三条链刚好代表了三个Pod,随机选择一条链即随机选择后端的某个Pod。

在经过这三条链中的某一条链时,数据包就会做目的地址转换(DNAT),目的地址变成了后端Pod的IP与端口。然后再经过主机的路由判断,转发到后端的Pod去。Pod之间的网络连通性,是由网络插件实现的,与这里的Service没有关系。

实验二

发布一个NodePort类型的Service,查看在集群外通过NodeIP:NodePort 访问服务是如何被重定向到后端Pod的。

1
、前提工作

搭建好集群,集群信息如下

$ kubectl get node
NAME              STATUS    ROLES     AGE       VERSION
132.122.232.101   Ready     <none>    31d       v1.8.6
132.122.232.78    Ready     <none>    13d       v1.8.6

2、创建deployment,yaml文件如下:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: tomcat2
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tomcat2
  template:
    metadata:
      labels:
        app: tomcat2
    spec:
      containers:
      - name: container1
        image: 10.142.232.115:8021/public/tomcat:8
        ports:
        - containerPort: 8080

创建完后,查看pod的信息如下:

$ kubectl get pod -o wide | grep tomcat2
tomcat2-f85bd4487-gx45x   1/1       Running   0          14s       172.27.135.10   132.122.232.78
tomcat2-f85bd4487-lqvh2   1/1       Running   0          14s       172.27.135.9    132.122.232.78
tomcat2-f85bd4487-mtc9z   1/1       Running   0          14s       172.27.48.25    132.122.232.101

3、创建service

创建service,yaml文件如下:

apiVersion: v1
kind: Service
metadata: 
  name: myservice2
  namespace: default
spec:
  type: NodePort
  selector:
    app: tomcat2
  ports:
  - name: port1
    port: 8080
    targetPort: 8080

创建完成后,查看service的信息如下,它仍然有ClusterIP和ServicePort(下面的8080端口),同时还增加了一个NodePort(下面的31406端口)

$ kubectl get service myservice2
NAME         TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
myservice2   NodePort   10.254.136.47   <none>        8080:31406/TCP   17s

查看endpoints的信息如下:

$ kubectl get endpoints myservice2
NAME         ENDPOINTS                                                AGE
myservice2   172.27.135.10:8080,172.27.135.9:8080,172.27.48.25:8080   3m

4、查看node上的iptables规则

当我们从集群外通过NodeIP:NodePort 访问服务时,数据包进入Node后会依次通过如下的线路:

PREROUTING -> 主机内核路由 -> FOWARD -> POSTROUTING

查看PREROUTING链的规则如下(其他行已省略,用...代替),引用了KUBE-SERVICES

$ iptables -t nat -L PREROUTING -n
Chain PREROUTING (policy ACCEPT)
target         prot opt source               destination         
...
KUBE-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
...

继续查看KUBE-SERVICES链的内容,如下

$ iptables -t nat -L KUBE-SERVICES -n
Chain KUBE-SERVICES (2 references)
target          prot opt source               destination         
...
KUBE-NODEPORTS  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

上面KUBE-NODEPORTS 一行的意思是,如果目的地址是LOCAL 类型(比如Node有两个网卡,则两个IP都是),则进入KUBE-NODEPORTS 链。

继续查看KUBE-NODEPORTS链的内容,如下

$ iptables -t nat -L KUBE-NODEPORTS -n
Chain KUBE-NODEPORTS (1 references)
target                      prot opt source               destination         
KUBE-MARK-MASQ              tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice2:port1 */ tcp dpt:31406
KUBE-SVC-ZLEWOEFITNP2NY7D   tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice2:port1 */ tcp dpt:31406

查看KUBE-MAKR-MASQKUBE-SVC-ZLEWOEFITNP2NY7D链的内容如下:

$ iptables -t nat -L KUBE-MARK-MASQ -n
Chain KUBE-MARK-MASQ (11 references)
target     prot opt source               destination         
MARK       all  --  0.0.0.0/0            0.0.0.0/0            MARK or 0x4000

$ iptables -t nat -L KUBE-SVC-ZLEWOEFITNP2NY7D -n
Chain KUBE-SVC-ZLEWOEFITNP2NY7D (2 references)
target                     prot opt source               destination         
KUBE-SEP-7354SXOGHIVJMV7G  all  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice2:port1 */ statistic mode random probability 0.33332999982
KUBE-SEP-7CDANDZR4NYGM5QP  all  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice2:port1 */ statistic mode random probability 0.50000000000
KUBE-SEP-6JKWW5STFDSOOTVT  all  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice2:port1 */

KUBE-MARK-MASQ 这条链会给数据包打一个mark标记,在POSTROUTING 链中会给打了mark标记的数据包做源地址转换(SNAT),这个在这里不做详细讨论。

KUBE-SVC-ZLEWOEFITNP2NY7D链可以看出,数据包会随机地进入到其中的某一条链。

查看上面三条链的内容,如下:

$ iptables -t nat -L KUBE-SEP-7354SXOGHIVJMV7G -n
Chain KUBE-SEP-7354SXOGHIVJMV7G (1 references)
target          prot opt source               destination         
KUBE-MARK-MASQ  all  --  172.27.135.10        0.0.0.0/0            /* default/myservice2:port1 */
DNAT            tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice2:port1 */ tcp to:172.27.135.10:8080

$ iptables -t nat -L KUBE-SEP-7CDANDZR4NYGM5QP -n
Chain KUBE-SEP-7CDANDZR4NYGM5QP (1 references)
target          prot opt source               destination         
KUBE-MARK-MASQ  all  --  172.27.135.9         0.0.0.0/0            /* default/myservice2:port1 */
DNAT            tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice2:port1 */ tcp to:172.27.135.9:8080

$ iptables -t nat -L KUBE-SEP-6JKWW5STFDSOOTVT -n
Chain KUBE-SEP-6JKWW5STFDSOOTVT (1 references)
target          prot opt source               destination         
KUBE-MARK-MASQ  all  --  172.27.48.25         0.0.0.0/0            /* default/myservice2:port1 */
DNAT            tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/myservice2:port1 */ tcp to:172.27.48.25:8080

上面三条链的内容表示,数据包的目的IP与端口会从NodeIP:NodePort 转换为PodIP:PodPort 。数据包后面如何通过主机内核路由及POSTROUTING 表,这里不做讨论了。

你可能感兴趣的:(kubernetes)