当使用kubernetes来发布应用时,Pod实例的生命周期是短暂,PodIP也是易变的。所以kubernetes中抽象了一个叫Service的概念,给应用提供一个固定的访问入口。
定义一个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中。
ClusterIP和Service代理
在上面的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代理模式。
Proxy-mode: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服务器是一个可选的插件(强烈推荐使用)。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有四种类型:ClusterIP
,NodePort
,ExternalName
,LoadBalancer
。本文主要介绍一下前两种。
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[*].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: