3. 独乐乐不如众乐乐。搭桥修路,迎接用户
上篇文章有提到,通过POD ID只能够在k8s集群内部进行访问,作为一个博客!只给自己看...好像也行啊...
但是每次都要先登录到集群节点中才能看...这就...(脑海里闪过成吨shell... [email protected]... kubectl get po...,噗~!
这是在玩自己吗?
言归正传
集群都有了,服务也部了,咋就不能让他与万物互联了呢?
接下来我们就一同探秘,k8s中服务是如何暴露出去的
Kubernetes 的Pod的寿命是有限的。它们出生然后死亡,它们不会复活。ReplicationController是特别用来动态的创建和销毁Pods(如:动态伸缩或者执行rolling updates中动态的创建和销毁pod)。尽管每个Pod有自己的IP地址,随着时间变化随着时间推移即使这些IP地址也不能被认为是可靠的。这带来一个问题:如果一些Pod的集合(让我们称之为backends)为集群中的其他的Pod提供了一些功能(让我们称它们为frontends),这些frontends应该如何找到并一直知道哪些backends在这样的集合中呢?
Kubernetes的Service是一种抽象,它定义了一组Pods的逻辑集合和一个用于访问它们的策略 - 有的时候被称之为微服务。
举个例子,想象一下我们的博客后端运行了三个副本。这些副本都是可以替代的 - 前端不关心它们使用的是哪一个后端。尽管实际组成后端集合的Pod可能会变化,前端的客户端却不需要知道这个变化,也不需要自己有一个列表来记录这些后端服务。Service抽象能让你达到这种解耦。
定义一个Service
Kubernetes中的Service是一个REST对象,这点与Pod类似。正如所有的REST对象一样,向apiserver POST一个Service的定义就能创建一个新的实例。以上篇文章中的echoserver为例,每一个Pod都开放了80端口,并且都有一个"app=my-blog"的标签。
{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "my-blog" }, "spec": { "selector": { "app": "my-blog" }, "ports": [ { "protocol": "TCP", "port": 8080, "targetPort": 80 } ] } }
这个定义会创建一个新的Service对象,名字为”my-blog”,它指向所有带有”app=my-blog”标签的Pod上面的80端口。这个Service同时也会被分配一个IP地址(有时被称作”cluster ip”),它会被服务的代理所使用(见下面)。这个Service的选择器,会不断的对Pod进行筛选,并将结果POST到名字同样为“my-blog”的Endpoints对象。
注意一个Service能将一个来源的端口映射到任意的targetPort。默认情况下,targetPort会被设置成与port字段一样的值。可能更有意思的地方在于,targetPort可以是一个字符串,能引用一个后端Pod中定义的端口名。实际指派给该名称的端口号在每一个Pod中可能会不同。这为部署和更新你的Service提供了很大的灵活性。例如,你可以在你的后端的下一个版本中更改开放的端口,而无需导致客户出现故障。
Kubernetes的Service支持TCP和UDP协议。默认是TCP(4层)。
Endpoint
说到service不得不提 endpoint。在 Service 创建的同时,还生成了一个 Endpoints。 该 Endpoints 与 Service 同名,它所暴露的地址信息正是对应 Pod 的地址。由此猜测是 Endpoints 维护了 Service 与 Pod 的映射关系。
为了验证我们的猜测,我们手动删除 Endpoints,发现之前能成功访问到 Pod 的 VIP,现在已经已经访问不到了。
ServiceController 主要处理的还是与 LoadBalancer 相关的逻辑,但是 EndpointController 的作用就没有这么简单了,我们在使用 Kubernetes 时虽然很少会直接与 Endpoint 资源打交道,但是它却是 Kubernetes 中非常重要的组成部分。
EndpointController 本身并没有通过 Informer 监听 Endpoint 资源的变动,但是它却同时订阅了 Service 和 Pod 资源的增删事件,它会根据 Service 对象规格中的选择器 Selector 获取集群中存在的所有 Pod,并将 Service 和 Pod 上的端口进行映射生成一个 EndpointPort 结构体,对于每一个 Pod 都会生成一个新的 EndpointSubset,其中包含了 Pod 的 IP 地址和端口和 Service 的规格中指定的输入端口和目标端口,在最后 EndpointSubset 的数据会被重新打包并通过客户端创建一个新的 Endpoint 资源。所以,除了 Service 的变动会触发 Endpoint 的改变之外,Pod 对象的增删也会触发。
在我的理解,service就是将不断变动的pod做了一个固定的端口映射(别管pod怎么变化,怎么漂移,这是我的事),你只需要处理service暴露出来的固定端口即可。到底是谁来处理service暴露出来的端口呢?那当然是代理了。
kube-proxy
我们知道 Service 的代理是由 kube-proxy 实现的。而它的代理模式(Proxy mode)主要有两种:userspace 与 iptables。自 K8S v1.2 开始,默认的代理模式就是 iptables(/ipvs),并且它的性能也是要高于 userspace 的
userspace是在用户空间,通过kuber-proxy实现LB的代理服务。这个是kube-proxy的最初的版本,较为稳定,但是效率也自然不太高。
iptables的方式。是纯采用iptables来实现LB。是目前一般kube默认的方式。
我们现在要做的呢,是将 VIP 请求给转发到对应的 Pod 上。而实现此的正是 iptables(/ipvs)。
举个例子,现在有podA,podB,podC和serviceAB。serviceAB是podA,podB的服务抽象(service)。 那么kube-proxy的作用就是可以将pod(不管是podA,podB或者podC)向serviceAB的请求,进行转发到service所代表的一个具体pod(podA或者podB)上。 请求的分配方法一般分配是采用轮询方法进行分配。
那有了kube-proxy,service是如何暴露的呢?
service的ServiceTypes能让你指定你想要哪一种服务。默认的和基础的是ClusterIP,这会开放一个服务可以在集群内部进行连接。NodePort 和LoadBalancer是两种会将服务开放给外部网络的类型。
apiVersion: v1 kind: Service metadata: # Service 实例名称 name: svc-echoserver spec: ports: - protocol: TCP # Service 端口地址 port: 8080 # Pod 端口地址 targetPort: 80 selector: # 匹配符合标签条件的 Pod app: my-blog type: NodePort <- Service Type
ServiceType字段的合法值是:
ClusterIP: 仅仅使用一个集群内部的IP地址 - 这是默认值,在上面已经讨论过。选择这个值意味着你只想这个服务在集群内部才可以被访问到。
NodePort: 在集群内部IP的基础上,在集群的每一个节点的端口上开放这个服务。你可以在任意<NodeIP>:NodePort地址上访问到这个服务。
如果定义为NodePort,那么我可以可以使用任意节点IP:8080来访问到my-blog容器的80端口
LoadBalancer: 在使用一个集群内部IP地址和在NodePort上开放一个服务之外,向云提供商申请一个负载均衡器,会让流量转发到这个在每个节点上以<NodeIP>:NodePort的形式开放的服务上。
在使用一个集群内部IP地址和在NodePort上开放一个Service的基础上,还可以向云提供者申请一个负载均衡器,将流量转发到已经以NodePort形式开发的Service上。