Service 是 kubernetes 中一个很重要的,也是很有用的概念,我们可以通过 service 来将 pod 进行分组,并提供外网的访问 endpoint。在这个过程中还有比如kube-proxy
提供了对 service 的访问。
如果我们要让一个用户能够使用应用程序,用户需要能访问到 pod,但是 pod 是一个短暂存在的东西,很可能突然挂了然后重启,这时候 ip 地址就会改变,所以 pod 的 ip 地址并不是静态的。比如说:
用户在这张图里面通过 ip 地址访问到了 4 个 pod,突然其中有一个 pod 挂了,然后 controller 又起了一个 pod:
这时候用户就访问不到了,因为用户不知道新的 ip 地址是多少。
kubernetes 为了解决这个问题,提供了一个高层的抽象,叫做 Service。Service 从逻辑上把 pod 进行分组,并且设置访问的策略。一般我们是通过 label 和 selector 来达到分组的目的的。
比如,我们用 app 作为 key,db 和 frontend 作为 value 来区分 pod:
通过 selector ( app=frontend 和 app=db ),我们就可以把这些 pod 分为两个逻辑组了。
这个时候,我们再给这两个逻辑组加上一个名称,比如frontend-svc
和db-svc
,就是 service 了:
一个 service 对象模型大致如下:
kind: Service
apiVersion: v1
metadata:
name: frontend-svc
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 5000
在这个对象模型中,我们创建了一个叫做frontend-svc
的 Service,这个 service 选择了所有的app=frontend
的 pod。在默认情况下,每个 service 都会有一个 cluster 内部可以访问到的 ip 地址,也被称为ClusterIP
:
用户现在可以通过 service 的 ip 地址来访问到 pod 了,service 会负责做负载均衡。
当转发请求的时候,我们可以选择 pod 上的目标端口,比如在我们的例子里面,frontend-svc 通过 80 端口来接受用户的请求,然后转发到 pod 的 5000 端口。如果目标端口没有被显式声明,那么会默认转发到 service 接受请求的端口(和 service 端口一样)。
一个 pod、ip 地址和目标端口的元组代表了一个 service 的 endpoint,比如在这个例子里面,frontend-svc 有 3 个 endpoints,分别是10.0.1.3:5000
, 10.0.1.4:5000
和10.0.1.5:5000
。
所有的 worker node 都有一个后台任务,叫做kube-proxy
。这个 kube-proxy 会检测 API Server 上对于 service 和 endpoint 的新增或者移除。对于每个新的 service,在每个 node 上,kube-proxy 都会设置相应的 iptables 的规则来记录应该转发的地址。当一个 service 被删除的时候,kube-proxy 会在所有的 pod 上移除这些 iptables 的规则。
我们已经知道,Service 是和 kubernetes 进行沟通的主要方式,那么我们就需要有一个办法来在运行的时候能够对已有的服务进行发现。Kubernetes 提供了两种方法:
每个 pod 在 worker node 上启动的时候,kubelet 都会通过环境变量把所有目前可用的 service 的信息传进去。举个例子,我们有一个叫做redis-master
的 service,这个 service expose 了 6379 的端口,并且 ClusterIP 是 172.17.0.6,那么在一个新创建的 pod 上,我们可以看到以下环境变量:
REDIS_MASTER_SERVICE_HOST=172.17.0.6
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://172.17.0.6:6379
REDIS_MASTER_PORT_6379_TCP=tcp://172.17.0.6:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=172.17.0.6
如果使用这个解决方案,我们必须非常小心启动服务的顺序,因为 pod 不会获得自己启动之后的 service 的 env。
kubernetes 有一些 dns 的 addon,这些 addon 会自动为所有 service 创建一个类似my-svc.my-namespace.svc.cluster.local
的 dns 解析,并且在同一个 namespace 里面的 service 可以直接用 service name 进行访问。这是最为推荐的方法。
当我们定义一个 service 的时候,我们可以选择可访问的范围,比如:
可访问的范围由 service 的类型决定,service 的类型可以在创建 service 的时候声明。
ClusterIP 是默认的 service type,一个 service 通过 ClusterIP 来获取自己的 Virtual IP,这个 IP 是用来和别的 service 通信的,只能在集群内部被访问。
NodePort 的 service type 除了会创建一个 ClusterIP 之外,还会把所有 worker node 上的一个 30000-32767 之间的端口映射到这个 service,比如假设32233
端口映射到了frontend-svc
,那么不管我们连接到哪个 worker node,我们都会被转发到 service 分配的 ClusterIP —— 172.17.0.4。
默认情况下,当 expose 到有一个 nodeport 的时候,kubernetes master 会自动随机选择一个 30000-32767 之间的 port,当然,我们自己也可以手动指定这个 port。
NodePort 的这个 service type 在我们想要让外网访问我们服务的时候非常有用,用户通过访问 node 上指定的 port 就可以访问到这个 service。管理员可以在 kubernetes 集群外再搭一个反向代理就可以更方便地进行访问了。
对于 LoadBalancer 这个 Servicetype:
LoadBalancer 这个 service type 只有在底层的基础架构支持了自动创建 load balancer 的时候 kubernetes 才支持,比如 Google Cloud Platform 和 aws。
如果一个 service 可以路由到一个或者多个 worker node 上,那么它可以被映射到一个 ExternalIP 地址。通过这个 ExternalIP 进入到集群的流量会被路由到其中一个 endpoint 上。
需要注意的是,ExternalIP 并不是由 k8s 自动管理的,是由管理员手动设置路由到其中的一个 node 上的。
ExternalName 是一个特定的 service type,这种 service type 没有任何的 selector 也没有任何声明的 endpoint。当在集群中访问到这个 service 的时候,会返回一个外部服务的 CNAME。
这个 service 一般是用来让一个外部的服务在集群内部可以访问到的,比如我们有一个外部服务叫做my-database.example.com
,那么我们可以通过设置 ExternalName 类型的 Service,让内部的其它 service 通过my-database
之类的名字访问到这个服务。
原文出自:我的博客
1
artandlol 2018-01-24 02:24:08 +08:00 via iPhone
感谢
问下博主,微 deis 的 workflow 怎么样?目前国内没有这方面的资料,自己折腾了解的不多 |