service介绍
我们在集群中可以创建pod,并通过rc来保证有特定数量的pod在运行。我们可以直接通过pod ip地址来访问pod提供的服务。但是pod的经常变化的,每次pod的创建销毁都会使其ip地址发生变更。如果直接使用pod的ip地址来访问,那么很容易出现问题。针对这个问题,解决方案就是 service
service定义了一种抽象:逻辑上的一组pod,一种可以访问他们的策略。service通过label来绑定一组pod,当我们需要访问一组pod提供的服务时,可以直接通过访问service,再由service来代理到具体某个pod上,起到代理转发的作用,就像是一个反向代理服务器。这样我们就不用直接访问某个一个pod,自然就不用担心pod ip变更的问题。而且还能实现pod之间的负载均衡。
service在这里其实更像是一个概念,实际上并不实际存在一个称为service的示例,而是由背后的kube-proxy和iptable配合来实现这一功能的。
定义示例
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
通过selector来绑定label为 app:MyApp的一组pod。并且将pod中服务的端口9376代理到service的80端口。这样就可以使用serviceIp+port在集群中来访问pod中提供的服务。
nodePort、port、targetPort
- apiVersion: v1
kind: Service
spec:
type: NodePort
ports:
- name: es-api
port: 9200
targetPort: 9200
nodePort: 30200
- name: es-node
port: 9300
targetPort: 30300
selector:
app: elasticsearch
查看如上的配置文件中,ports中有port targetPort nodePort三个端口设置
- port:本服务监听的端口,在集群中访问服务,即可使用服务的ip和这个端口访问服务。
- targetPort:为这个服务后面对应的pod的端口,即访问服务的port端口应该转发到pod中的哪个端口就是由这个值来设置的。该值也可以是一个字符串,引用pod的一个端口的名称,这样的话即使该名称下的pod中的端口不同也能正常提供服务。
- nodePort:这个是为外部服务设置的,如果service设置为nodeport,则外部可以通过node(主机)的ip加上这个端口来访问服务。
Service 代理和VIP
Service虽然有一个ClusterIp,但是这个IP是虚拟的ip(VIP),由kube-proxy实现。那么当我们通过这个ip+Port去访问一个服务的时候,他是如何访问到其后面的pod的呢?主要是通过kube-proxy和iptable来实现代理转发的。
k8s集群中,每个node都运行着一个kube-proxy进程,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。
userpace代理模式
在这种模式下,kube-proxy会为每个新建的service在本地node上打开一个代理端口(随机选择),然后安装iptables规则。当请求访问服务时,会首先被iptables捕获,然后根据规则重定向到代理端口,再由kube-proxy代理请求到backend pod。具体选择哪一个pod是基于Service的SessionAffinity来确定的。
最终的结果就是任何到达Service的IP:PORT请求都会被代理到一个合适的backend,不需要客户端知道关于kubernetes Service Pod的任何信息。
由于需要在kube-proxy内部应用层对其进行转发,所以效率比较低。
iptables代理模式
对每个service,他会安装iptables规则,从而捕获到达该service的IP:port请求。除此之外,对于每个Endpoints对象,它也会安装iptables规则,这个规则会随机选择一个backend pod。换句话说,请求由iptables捕获后,再由iptables规则来随机选择转发到一个pod上。
# service信息
service/es-svc NodePort 10.100.79.190 <none> 9200:30200/TCP,9300:30070/TCP 26h
#以下都iptables得输出信息
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A KUBE-SERVICES -d 10.100.79.190/32 -p tcp -m comment --comment "default/es-svc:es-api cluster IP" -m tcp --dport 9200 -j KUBE-SVC-N6HHKJLNJXWLBPKB
#可以看到,他是随机从三个节点中去访问的
-A KUBE-SVC-N6HHKJLNJXWLBPKB -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-BVM2FVVPRW75VUNG
-A KUBE-SVC-N6HHKJLNJXWLBPKB -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-6HJGYMMQWJRCIELD
-A KUBE-SVC-N6HHKJLNJXWLBPKB -j KUBE-SEP-WW3IJWYVBS5P5N4M
-A KUBE-SEP-BVM2FVVPRW75VUNG -s 10.244.0.28/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-BVM2FVVPRW75VUNG -p tcp -m tcp -j DNAT --to-destination 10.244.0.28:9200
-A KUBE-SEP-6HJGYMMQWJRCIELD -s 10.244.0.29/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-6HJGYMMQWJRCIELD -p tcp -m tcp -j DNAT --to-destination 10.244.0.29:9200
目前的默认模式,也是推荐的模式,它脱离了用户空间在内核空间中的实现转发的方式能够极大地提高proxy的效率。
service的类型
ClusterIp类型
服务只能再集群内部访问,默认类型
NodePort 类型
设置为该类型后,将会从30000-32767中分配一个端口给service,作为提供给外部的访问的端口。也可以手动配置nodePort的值,也是要在30000-32767的范围之内。有了这个端口就可以使用NodeIp:nodePort来访问该服务了。
Headless Services
Headless Service也是一种service,只是这种service没有ClusterIp,即可指定ClusterIp
的值为None
来创建Headless Service。对于这类service,kube-proxy不会处理他们,而且不会进行负载均衡和路由。
对于一般的service,使用dns查询服务的名字,会返回service的地址。但是查询headless service的名字,返回的是backend pod的所有地址。
所以client可以自己决定使用哪个pod,即自定义负载均衡。
但是除此之外,还有一个很有用的用途。 headless service对应的每一个pod,都会有对应的dns域名。这样便可以直接使用pod 的name来进行访问。 再使用StatefulSet下的pod按顺序生成的特点,pod name的命名为Statefulset的名字后面加上数字,如es-0,es-1,es-2.. ,而不是Rc或者Deploment的随机pod名 就可以使用{podname}.{headless service name}来作为pod的域名,使pod之间可以通过这个域名进行通信,并且不用担心pod重新生成时ip地址会变得问题,而且也可以直接在配置文件中配置。 可用于再一些集群的服务发现的初始化配置上。
- apiVersion: v1
kind: Service
metadata:
name: elasticsearch
labels:
app: elasticsearch
spec:
clusterIP: None
ports:
- name: api-port
port: 9200
- name: node-port
port: 9300
selector:
app: elasticsearch
没有 selector 的 Service
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
由于这个 Service 没有 selector,就不会创建相关的 Endpoints 对象。可以手动将 Service 映射到指定的 Endpoints
kind: Endpoints
apiVersion: v1
metadata:
name: my-service
subsets:
- addresses:
- ip: 1.2.3.4
ports:
- port: 9376
服务发现
kubernetes支持两种基本的服务发现模式。环境变量和dns
环境变量
在每一个pod生成的时候,都会其注入一些环境变量,以便再pod中直接通过环境变量来获得集群中服务的地址。但是这样就要求service必须再pod创建之前就已经存在。
举个例子,一个名称为 “redis-master” 的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量
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
dns
强烈推荐是集群插件来提供dns服务器(例如flannel)。dns服务器监控着创建新Service的api,为每一个service创建一组dns记录。从而能够通过dns服务器来直接使用service name来解析获得service ip。
并且再实际的环境中,我们经常使用namespace来分别部署不同的pod和svc。而在不同的namespace要实现互访就需要使用的dns服务器;而且必须使用{$service-name.$namespace-name}这样的格式来进行访问。