查看原文
其他

漫画:小黄人学 Kubernetes Service

吴迪 K8S中文社区 2019-12-18

------3小时后------

Service 是kubernetes中一个很重要的,也是很有用的概念,可以通过Service来将pod进行分组,并提供外网的访问endpoint。在这个过程中还有比如kube-proxy提供了对Service的访问。


但pod是一个短暂存在的东西,很可能突然挂了然后重启,这时候ip地址就会改变,所以pod的ip地址并不是静态的。比如说:



用户在这张图里面通过ip地址访问到了4个pod,突然其中有一个pod挂了,然后controller又起了一个pod:



这时候用户就访问不到了,因为用户不知道新的ip地址是多少。


Kubernetes为了解决这个问题,提供了一个高层的抽象,叫做Service。Service从逻辑上把pod进行分组,并且设置访问的策略。一般我们是通过label和selector来达到分组的目的的。

通过selector(app=frontend和app=db),我们就可以把这些pod分为两个逻辑组了。


这个时候,我们再给这两个逻辑组加上一个名称,比如frontend-svc和db-svc,就是Service了(如下图):

  1. kind: Service

  2. apiVersion: v1

  3. metadata:

  4.  name: frontend-svc

  5. spec:

  6.  selector:

  7.    app: frontend

  8.  ports:

  9.    - protocol: TCP

  10.      port: 80

  11.      targetPort: 5000


在这个对象模型中,我们创建了一个叫做frontend-svc的Service,这个Service选择了所有的app=frontend的pod。在默认情况下,每个Service都会有一个cluster内部可以访问到的ip地址,也被称为ClusterIP:


当转发请求的时候,我们可以选择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上,我们可以看到以下环境变量:


  1. REDIS_MASTER_SERVICE_HOST=172.17.0.6

  2. REDIS_MASTER_SERVICE_PORT=6379

  3. REDIS_MASTER_PORT=tcp://172.17.0.6:6379

  4. REDIS_MASTER_PORT_6379_TCP=tcp://172.17.0.6:6379

  5. REDIS_MASTER_PORT_6379_TCP_PROTO=tcp

  6. REDIS_MASTER_PORT_6379_TCP_PORT=6379

  7. 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进行访问。这是最为推荐的方法。

  • 是否只能在cluster内部访问

  • 是否同时可以被cluster内部和外部访问

  • 是否是映射到一个集群外的entity上


可访问的范围由Service 的类型决定,Service 的类型可以在创建Service 的时候声明。


ClusterIP 和 NodePort


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集群外再搭一个反向代理就可以更方便地进行访问了。

  • NodePort和ClusterIP会被自动创建,外部的load balancer会自动路由上去

  • Service 会在一个静态的端口上被暴露

  • 通过底层的cloud provider提供的load balancer来暴露到外网


LoadBalancer这个Service type只有在底层的基础架构支持了自动创建load balancer的时候kubernetes才支持,比如Google Cloud Platform和aws。


ExternalName

ExternalName是一个特定的Service type,这种Service type没有任何的selector也没有任何声明的endpoint。当在集群中访问到这个Service 的时候,会返回一个外部服务的CNAME。


这个service一般是用来让一个外部的服务在集群内部可以访问到的,比如我们有一个外部服务叫做my-database.example.com,那么我们可以通过设置ExternalName类型的Service,让内部的其它Service 通过my-database之类的名字访问到这个服务。


ExternalIP

如果一个Service 可以路由到一个或者多个worker node上,那么它可以被映射到一个ExternalIP地址。通过这个ExternalIP进入到集群的流量会被路由到其中一个endpoint上。

需要注意的是,ExternalIP并不是由k8s自动管理的,是由管理员手动设置路由到其中的一个node上的,ExternalIP可以和任意Service type来一起指定。

----1小时后----


推荐阅读

梁胜博士:写给程序员的话

创建最新版 Kubernetes1.9 集群

40张技术图谱,架构师阶梯 (附高清下载)

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存