service-九游平台
直接访问pod的问题
pod创建完成后,如何访问pod呢?直接访问pod会有如下几个问题:
- pod会随时被deployment这样的控制器删除重建,那访问pod的结果就会变得不可预知。
- pod的ip地址是在pod启动后才被分配,在启动前并不知道pod的ip地址。
- 应用往往都是由多个运行相同镜像的一组pod组成,逐个访问pod也变得不现实。
举个例子,假设有这样一个应用程序,使用deployment创建了前台和后台,前台会调用后台做一些计算处理,如图1所示。后台运行了3个pod,这些pod是相互独立且可被替换的,当pod出现状况被重建时,新建的pod的ip地址是新ip,前台的pod无法直接感知。
使用service解决pod的访问问题
kubernetes中的service对象就是用来解决上述pod访问问题的。service有一个固定ip地址(在创建cce集群时有一个服务网段的设置,这个网段专门用于给service分配ip地址),service将访问它的流量转发给pod,具体转发给哪些pod通过label来选择,而且service可以给这些pod做负载均衡。
那么对于上面的例子,为后台添加一个service,通过service来访问pod,这样前台pod就无需感知后台pod的变化,如图2所示。
创建后台pod
首先创建一个3副本的deployment,即3个pod,且pod上带有标签“app: nginx”,具体如下所示。
apiversion: apps/v1
kind: deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchlabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
name: container-0
resources:
limits:
cpu: 100m
memory: 200mi
requests:
cpu: 100m
memory: 200mi
imagepullsecrets:
- name: default-secret
创建service
下面示例创建一个名为“nginx”的service,通过selector选择到标签“app:nginx”的pod,目标pod的端口为80,service对外暴露的端口为8080。
访问服务只需要通过“服务名称:对外暴露的端口”接口,对应本例即“nginx:8080”。这样,在其他pod中,只需要通过“nginx:8080”就可以访问到“nginx”关联的pod。
apiversion: v1 kind: service metadata: name: nginx # service的名称 spec: selector: # label selector,选择包含app=nginx标签的pod app: nginx ports: - name: service0 targetport: 80 # pod的端口 port: 8080 # service对外暴露的端口 protocol: tcp # 转发协议类型,支持tcp和udp type: clusterip # service的类型
将上面service的定义保存到nginx-svc.yaml文件中,使用kubectl创建这个service。
$ kubectl create -f nginx-svc.yaml service/nginx created $ kubectl get svc name type cluster-ip external-ip port(s) age kubernetes clusterip 10.247.0.1443/tcp 7h19m nginx clusterip 10.247.124.252 8080/tcp 5h48m
您可以看到service有个cluster ip,这个ip是固定不变的,除非service被删除,所以您也可以使用clusterip在集群内部访问service。
下面创建一个pod并进入容器,使用clusterip访问pod,可以看到能直接返回内容。
$ kubectl run -i --tty --image nginx:alpine test --rm /bin/sh if you don't see a command prompt, try pressing enter. / # curl 10.247.124.252:8080welcome to nginx! ...
使用servicename访问service
通过dns进行域名解析后,可以使用“servicename:port”访问service,这也是kubernetes中最常用的一种使用方式。在创建cce集群的时候,会默认要求安装coredns插件,在kube-system命名空间下可以查看到coredns的pod。
$ kubectl get po --namespace=kube-system name ready status restarts age coredns-7689f8bdf-295rk 1/1 running 0 9m11s coredns-7689f8bdf-h7n68 1/1 running 0 11m
coredns安装成功后会成为dns服务器,当创建service后,coredns会将service的名称与ip记录起来,这样pod就可以通过向coredns查询service的名称获得service的ip地址。
访问时通过nginx.
例如上面创建的名为nginx的service,直接通过“nginx:8080”就可以访问到service,进而访问后台pod。
使用servicename的方式有个主要的优点就是可以在开发应用程序时可以将servicename写在程序中,这样无需感知具体service的ip地址。
下面创建一个pod并进入容器,查询nginx域名的地址,可以发现是解析出nginx这个service的ip地址10.247.124.252;同时访问pod的域名,可以看到能直接返回内容。
$ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=never --rm /bin/sh if you don't see a command prompt, try pressing enter. / # nslookup nginx server: 10.247.3.10 address: 10.247.3.10#53 name: nginx.default.svc.cluster.local address: 10.247.124.252 / # curl nginx:8080welcome to nginx! ...
service是如何做到服务发现的
前面说到有了service后,无论pod如何变化,service都能够发现到pod。
如果调用kubectl describe命令查看service的信息,您会看到如下信息。
$ kubectl describe svc nginx
name: nginx
......
endpoints: 172.16.2.132:80,172.16.3.6:80,172.16.3.7:80
......
可以看到一个endpoints,endpoints同样也是kubernetes的一种资源对象,可以查询得到。kubernetes正是通过endpoints监控到pod的ip,从而让service能够发现pod。
$ kubectl get endpoints
name endpoints age
nginx 172.16.2.132:80,172.16.3.6:80,172.16.3.7:80 5h48m
这里的172.16.2.132:80是pod的ip:port,通过如下命令可以查看到pod的ip,与上面的ip一致。
$ kubectl get po -o wide name ready status restarts age ip node nginx-869759589d-dnknn 1/1 running 0 5h40m 172.16.3.7 192.168.0.212 nginx-869759589d-fcxhh 1/1 running 0 5h40m 172.16.3.6 192.168.0.212 nginx-869759589d-r69kh 1/1 running 0 5h40m 172.16.2.132 192.168.0.94
如果删除一个pod,deployment会将pod重建,新的pod ip会发生变化。
$ kubectl delete po nginx-869759589d-dnknn
pod "nginx-869759589d-dnknn" deleted
$ kubectl get po -o wide
name ready status restarts age ip node
nginx-869759589d-fcxhh 1/1 running 0 5h41m 172.16.3.6 192.168.0.212
nginx-869759589d-r69kh 1/1 running 0 5h41m 172.16.2.132 192.168.0.94
nginx-869759589d-w98wg 1/1 running 0 7s 172.16.3.10 192.168.0.212
再次查看endpoints,会发现endpoints的内容随着pod发生了变化。
$ kubectl get endpoints
name endpoints age
kubernetes 192.168.0.127:5444 7h20m
nginx 172.16.2.132:80,172.16.3.10:80,172.16.3.6:80 5h49m
下面进一步了解这又是如何实现的。
在kubernetes集群架构中介绍过node节点上的kube-proxy,实际上service相关的事情都由节点上的kube-proxy处理。在service创建时kubernetes会分配ip给service,同时通过api server通知所有kube-proxy有新service创建了,kube-proxy收到通知后通过iptables记录service对应的ip和端口,从而让service在节点上可以被查询到。
下图是一个实际访问service的图示,pod x访问service(10.247.124.252:8080),在往外发数据包时,在节点上根据iptables规则目的ip:port被随机替换为pod1的ip:port,从而通过service访问到实际的pod。
除了记录service对应的ip和端口,kube-proxy还会监控service和endpoint的变化,从而保证pod重建后仍然能通过service访问到pod。
service的类型与使用场景
service的类型除了clusterip还有nodeport、loadbalancer和headless service,这几种类型的service有着不同的用途。
- clusterip:用于在集群内部互相访问的场景,通过clusterip访问service。
- nodeport:用于从集群外部访问的场景,通过节点上的端口访问service,详细介绍请参见nodeport类型的service。
- loadbalancer:用于从集群外部访问的场景,其实是nodeport的扩展,通过一个特定的loadbalancer访问service,这个loadbalancer将请求转发到节点的nodeport,而外部只需要访问loadbalancer,详细介绍请参见loadbalancer类型的service。
- headless service:用于pod间的互相发现,该类型的service并不会分配单独的clusterip, 而且集群也不会为它们进行负载均衡和路由。您可通过指定spec.clusterip字段的值为“none”来创建headless service,详细介绍请参见headless service。
nodeport类型的service
nodeport类型的service可以让kubernetes集群每个节点上保留一个相同的端口, 外部访问连接首先访问节点ip:port,然后将这些连接转发给服务对应的pod。如下图所示。
apiversion: v1
kind: service
metadata:
name: nodeport-service
spec:
type: nodeport
ports:
- port: 8080
targetport: 80
nodeport: 30120
selector:
app: nginx
创建并查看,可以看到port这一列为8080:30120/tcp,说明service的8080端口是映射到节点的30120端口。
$ kubectl create -f nodeport.yaml service/nodeport-service created $ kubectl get svc -o wide name type cluster-ip external-ip port(s) age selector kubernetes clusterip 10.247.0.1443/tcp 107m nginx clusterip 10.247.124.252 8080/tcp 16m app=nginx nodeport-service nodeport 10.247.210.174 <none> 8080:30120/tcp 17s app=nginx
此时,通过节点ip:端口访问service可以访问到pod,如下所示。
$ kubectl run -i --tty --image nginx:alpine test --rm /bin/sh if you don't see a command prompt, try pressing enter. / # curl 192.168.0.212:30120welcome to nginx! ......
loadbalancer类型的service
loadbalancer类型的service其实是nodeport类型service的扩展,通过一个特定的loadbalancer访问service,这个loadbalancer将请求转发到节点的nodeport。
loadbalancer本身不是属于kubernetes的组件,这部分通常是由具体厂商(云服务提供商)提供,不同厂商的kubernetes集群与loadbalancer的对接实现各不相同,例如cce对接了elb。这就导致了创建loadbalancer类型的service有不同的实现。
apiversion: v1
kind: service
metadata:
annotations:
kubernetes.io/elb.id: 3c7caa5a-a641-4bff-801a-feace27424b6
labels:
app: nginx
name: nginx
spec:
loadbalancerip: 10.78.42.242 # elb实例的ip地址
ports:
- name: service0
port: 80
protocol: tcp
targetport: 80
nodeport: 30120
selector:
app: nginx
type: loadbalancer # 类型为loadbalancer
上面metadata.annotations里的参数配置是cce的loadbalancer类型service需要配置的参数,表示这个service绑定哪个elb实例。cce还支持创建loadbalancer类型service时新建elb实例,详细的内容请参见。
headless service
前面讲的service解决了pod的内外部访问问题,允许客户端连接到service关联的某个pod。但还有下面这些问题没解决。
- 同时访问所有pod
- 一个service内部的pod互相访问
为了解决以上问题,kubernetes提供了另一种较为特殊的service类型,称为headless service。对于其他service来说,客户端在访问服务时,dns查询时只会返回service的clusterip地址,具体访问到哪个pod是由集群转发规则(ipvs或iptables)决定的。而headless service并不会分配单独的clusterip,在进行dns查询时会返回所有pod的dns记录,这样就可查询到每个pod的ip地址。中statefulset正是使用headless service解决pod间互相访问的问题。
apiversion: v1 kind: service # 对象类型为service metadata: name: nginx-headless labels: app: nginx spec: ports: - name: nginx # pod间通信的端口名称 port: 80 # pod间通信的端口号 selector: app: nginx # 选择标签为app:nginx的pod clusterip: none # 必须设置为none,表示headless service
执行如下命令创建headless service。
# kubectl create -f headless.yaml service/nginx-headless created
创建完成后可以查询service。
# kubectl get svc name type cluster-ip external-ip port(s) age nginx-headless clusterip none80/tcp 5s
创建一个pod来查询dns,可以看到能返回所有pod的记录,这就解决了访问所有pod的问题了。
$ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=never --rm /bin/sh if you don't see a command prompt, try pressing enter. / # nslookup nginx-headless server: 10.247.3.10 address: 10.247.3.10#53 name: nginx-headless.default.svc.cluster.local address: 172.16.0.31 name: nginx-headless.default.svc.cluster.local address: 172.16.0.18 name: nginx-headless.default.svc.cluster.local address: 172.16.0.19
相关文档
意见反馈
文档内容是否对您有帮助?
如您有其它疑问,您也可以通过华为云社区问答频道来与我们联系探讨