当使用kubernetes来发布应用时,Pod实例的生命周期是短暂,PodIP也是易变的。所以kubernetes中抽象了一个叫Service的概念,给应用提供一个固定的访问入口。
假如现在:你有一个tomcat镜像,你用它创建了一个Deployment,发布了三个Pod实例;这组Pod暴露了8080端口,并且都拥有标签app=tomcat
。那么,你可以创建如下的一个Service对象,然后在kubernetes集群的容器内或集群的Node上,都可以通过该 Service的ClusterIP
和Port
来访问这组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中。
在上面的Service定义文件中,ClusterIP是一个虚拟IP。它既不是某个Pod的IP,也不是某个Node的IP。kubernetes集群中的每个节点上都运行一个kube-proxy,它负责监听Service
与Endpoints
的创建与删除,并相应地修改Node上的iptables规则。
kube-proxy有三种代理模式:userspace、iptables、ipvs。从kubernetes v1.2开始,iptables成为默认的代理模式,在kubernetes v1.9-alpha中,加入了ipvs代理模式。本文主要介绍一下iptables代理模式。
在这种模式下,对于每一个Service,它会在Node上添加相应的iptables规则,将访问该Service的ClusterIP与Port的连接重定向到Endpoints中的某一个后端Pod。默认情况下,选择哪一个Pod是随机。
当选择的某个后端Pod没有响应时,iptables模式无法自动重新连接到另一个Pod,所以需要用户自已利用Pod的readness probe
和liveness 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服务器会通过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。
kubernetes的Service有四种类型:ClusterIP
,NodePort
,ExternalName
,LoadBalancer
。本文主要介绍一下前两种。
ClusterIP
是Service的默认类型,如果不设置type
字段,则默认为ClusterIP
。这种类型的Service把服务暴露成一个内部的集群IP,只能在集群内访问(Pod和Node上都可以)。clusterIP
字段的值可以自动生成,也可以手动设置,不过一定要在规则的范围内,可通过kube-apiserver
的启动参数--service-cluster-ip-range
指定ClusterIP
的范围。
如果将type
字段设置为NodePort
,则Kubernetes master将从启动参数--service-node-port-range
配置的范围(默认值:30000-32767)分配端口,并且每个节点将该端口(每个节点上端口号相同)代理到你的Service。该端口可以在Service的spec.ports[*].nodePor
t字段进行查看。
如果你想要一个特定的端口号,你可以在该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-MASQ
与KUBE-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
表,这里不做讨论了。