带着问题学 Kubernetes 抽象对象 Service

带着问题学 Kubernetes 抽象对象 Service

摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.com/jasonGeng88/blogphp

当前环境

  1. Mac OS 10.11.xdocker

  2. kubectl == v1.6.4api

  3. minikube == v0.19.1架构

  4. docker == 1.11.1app

知识点

  • Service 的 Selector 与 Label 匹配机制负载均衡

  • Service 与 Pods 的地址映射关系性能

  • kube-proxy 的 iptables 代理机制学习

  • Service 的服务发现机制

  • Service 的服务暴露方式

前言

上一篇讲述了 Pod 的相关内容,了解了 Pod 的定义、生命周期以及通讯机制等。正如上文说的,Pod 是存在生命周期的,它的崩溃、更新都是以建立新 Pod 替换原有的 Pod 的方式进行的,因此经过固定 Pod 地址的访问变得不太可行。咱们须要经过一种上层调用的方式,来解决底层 Pod 的动态变化的场景。

庆幸,K8S 引入了 Service 这个抽象的概念。Service 会建立一个虚拟的服务,由它来整合集群内的 Pod。Service 会虚拟出一个 VIP,并在它销毁以前保持该 VIP 地址保持不变。经过对它的访问,以代理的方式负载到对应的 Pod 上,同时 Pod 生命周期的变换,也会及时反应在代理上。

下面咱们几种常见的场景,来具体看看 Service 是如何工做的。

环境准备

演示镜像

  • 镜像名:jasonn/php-echoserver

  • 做用:打印当前容器的 IP

K8S Pod 建立

  • 文件名:deploy-echoserver.yml (这里以 Deployment 的方式来建立与管理 Pod

  • 文件内容:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  # Deployment 实例名称
  name: echoserver
spec:
  # 设置 Pod 个数
  replicas: 2
  template:
    metadata:
      # 设置 Pod 标签
      labels:
        app: echoserver
    spec:
      # 运行 docker 镜像
      containers:
      - name: echoserver 
        image: jasonn/php-echoserver
  • 启动命令:

kubectl create -f deploy-echoserver.yml

至此,准备工做所有完成。短暂的等待后,Pod 建立成功,而且也由 deployment 管理着。

查看 deployment 启动状况:

对 Pod 的访问状况以下(经过kubectl describe pods获取 Pod 的 IP 地址):

问题

场景1:如今 K8S 上运行着2个 Pod。咱们但愿经过上述所说的 Service 来整合这两个 Pod 的访问,完成对它们的统一访问,而不用向具体的 Pod 发出请求。

Q1: Service 如何对 Pod 进行整合

这里所说的 Pod 默认都是带有标签(label)的,咱们以前建立的两个 Pod 所赋予的标签是 app: echoserver,因此咱们在建立 Service 时,就要经过选择器(selector)来获取符合条件的 Pod 进行整合。

Service 建立脚本内容以下(service-echoserver.yml):

apiVersion: v1
kind: Service
metadata:
  # Service 实例名称
  name: svc-echoserver
spec:
  ports:
    - protocol: TCP
      # Service 端口地址
      port: 8080
      # Pod 端口地址
      targetPort: 80
  selector:
    # 匹配符合标签条件的 Pod
    app: echoserver

建立 Service 命令:

kubectl create -f service-echoserver.yml

由此,咱们建立好了一个 Service,同时也生成了一个对应的 VIP。

查看 Serivce 建立状况:

下面,咱们来验证下是否如以前所说,对 VIP 的访问能访问到 Pod 的内容。

咱们发现不只能成功访问,并且还提供了负载均衡的功能。后面会讲负载是怎么实现的

PS: 标签筛选查找范围仅在同个命名空间(namespace)内。


场景2:了解了 Service 是经过 label & selecor 来进行整合 Pod 的。那若是 Pod 不存在标签,又或者是在不一样 Namespace 下,也多是 K8S 集群外的一个服务。现实状况每每更加复杂,这样的状况下,Service 又该如何整合。

Q2: Service 与 Pod 的地址映射关系由谁管理?

这里引出了另外一个概念 Endpoints。咱们先来看看它的一个具体状况。

发如今 Service 建立的同时,还生成了一个 Endpoints。 该 Endpoints 与 Service 同名,它所暴露的地址信息正是对应 Pod 的地址。由此猜想是 Endpoints 维护了 Service 与 Pod 的映射关系。

为了验证咱们的猜想,咱们手动删除 Endpoints,发现以前能成功访问到 Pod 的 VIP,如今已经已经访问不到了。

咱们在手动把 Endpoints 建立回来,建立脚本以下(endpoint-echoserver.yml):

apiVersion: v1
kind: Endpoints
metadata:
  # Endpoints 实例的名称
  name: svc-echoserver
subsets:
  - addresses:
    - ip: 172.17.0.5
    - ip: 172.17.0.6
    ports:
    - port: 80

建立命令:

kubectl create -f endpoint-echoserver.yml

注意:Endpoints 与 Service 的绑定关系经过名称来关联的,因此这二者的名称(name)必定要一致。

若是建立失败,出现的错误信息是“...endpoints "svc-echoserver" already exists”,说明 Service 已经更新了 Endpoints。这里就说到了 Service 会按期去检查 Pod 的状态,而且将结果更新到 Endpoints 上。

VIP 再次访问时又能成功访问到,如图:

如今咱们已经能轻松的解决场景2的问题了,在建立 Service 时,只要不设置 Selector 属性,那么将不会自动建立 Endpoints,这是咱们能够根据需求手动的建立指定地址(address)的 Endpoints,来解决标签没法实现的整合。


场景3:知道了 Service、Endpoints、Pod 的三者关系后,咱们来具体看看所说的代理究竟是如何实现的。从以前 K8S 的架构中,咱们知道 Service 的代理是由 kube-proxy 实现的。而它的代理模式(Proxy mode)主要有两种:userspace 与 iptables。自 K8S v1.2 开始,默认的代理模式就是 iptables,而且它的性能也是要高于 userspace 的,因此在这儿只讨论 iptables 的实现。

Q3:kube-proxy 是如何使用 iptables 作到服务代理的(对于 iptables 不了解的同窗能够直接跳过)?

咱们如今要作的呢,是将 VIP 请求给转发到对应的 Pod 上。而实现此的正是 iptables。

了解 iptables 的同窗都知道四表五链的概念,而作端口地址转发的呢,主要是在 nat 表中实现。咱们下面看一下一个 VIP 请求在 nat 表中是如何一步步被转发到 Pod 上的。

    1. 根据 iptables 的机制,请求是先到 nat 表的 PREROUTING 链(chain)上的,它的规则以下:

从图中发现,请求首先通过 KUBE-SERVICE 链,其次再到 DOCKER 链上的。

    1. 咱们看一下 KUBE-SERVICE 的状况:

咱们发现 KUBE-SERVICE 中包含了一系列 Service 的规则。根据咱们请求的 VIP 的目的地址,对应到了下一个名叫 KUBE-SVC-PRQ3AXYQLQGIVVIU 的 Service 链上。

    1. KUBE-SVC-PRQ3AXYQLQGIVVIU 规则以下:

从规则的名字上能够看出,该条 Service 链上记录的是2个 Endpoints 链,具体的选择是经过 50% 的随机性的进行决定(这也是它的一个负载规则)。

    1. 咱们来看第一个名叫 KUBE-SEP-JSFY3ZFM2EVD64VQ 的 Endpoints 链的状况:

从图中,咱们已经很清晰的看到了它转发到 Pod 的具体规则。

    1. 下面以一张简单的流程图,看一下请求的转发状况:

关于 DOCKER 链的跟踪,方法是差很少的,请求 从 nat 表结束后,在到 filter 表中,这里就不加以说明了。

而这些规则的实现正是由 Service、Endpoints 来完成的。咱们在建立、更新、以及其自身的检测机制,都会对这些规则进行更新。


场景4:Service 的建立、内部结构以及映射关系,咱们都了解了。下面咱们就要关心如何优雅的使用它,上面咱们都是经过 Service 的 VIP 进行访问的。这存在的问题是,若是有服务与服务之间的调用,难道我还要知道所调用服务的 VIP 不成,对于 VIP 的访问可否更通用一些。

Q4:Service 的服务发现机制是怎样的?

对于服务与服务间的调用,实际上就是 Pod 对 Servie 的调用。而 Pod 是如何发现 Service 的,这里可选择的方式有2种。

咱们经过启动一个名为 busybox 的 Pod 来观察这两种方式:

kubectl run -i --tty busybox --image=busybox --restart=Never -- sh
  • 环境变量:

在 Pod 中,集群中的 Service 会以环境变量的方式赋值在容器中,咱们能够经过 {SERVICE_NAME}_SERVICE_HOST{SERVICE_NAME}_SERVICE_PORT 进行获取(对于有多个 Port 的,能够经过带指定 PORT 名称的变量得到。)。

busybox 中 环境变量以下:

查看访问状况:

  • dns 解析:

第二种方式是经过 kube-dns 对 Service 进行域名解析,一样能达到服务发现的目的。

查看 DNS 域名解析配置:

经过 nslookup 查询 dns 记录:

查看访问结果:


场景5:集群间的服务调用解决了,可说到底仍是经过的 VIP 进行的访问。VIP 对于集群外的终端用户,是没法访问的。因此咱们得经过服务暴露的方式,让终端用户能与集群内的服务进行通讯。

Q5:Service 是如何对外暴露服务的?

在 Service 的配置文件中,属性spec.type就是用来设置服务暴露的方式,它提供的三种方式以下:

  • ClusterIP: 提供一个集群内部的虚拟IP以供Pod访问(默认类型,咱们上述使用的正是这种方式)。

  • NodePort: 在每一个Node上打开一个端口以供外部访问。

  • LoadBalancer: 经过外部的负载均衡器来访问(通常须要云提供商提供 LB 支持)。

咱们这里简单起见,仍是经过 NodePort 方式进行。

修改 Service 配置文件,并从新启动:

apiVersion: v1
kind: Service
metadata:
  # Service 实例名称
  name: svc-echoserver
spec:
  ports:
    - protocol: TCP
      # Service 端口地址
      port: 8080
      # Pod 端口地址
      targetPort: 80
  selector:
    # 匹配符合标签条件的 Pod
    app: echoserver
  type: NodePort

注意:这里若是要以kubecrl replace -f service-echoserver.yml方式进行平滑更新,配置中需添加spec.clusterIP属性,值为当前 Service 的 VIP,不然更新会失败。这也符合了一开始说的 Service 在它终止以前,VIP 是不会改变的。

查看 Service 更新状况:

外部访问(该 Node 地址是:192.168.64.6):

总结

文本从 Service 的标签与选择器开始,讲了 Service 整合 Pod 的过程,引出了 Service, Endpoints, Pods 三者的关系状况。随后又经过 iptables 详细展开了 kube-proxy 的代理机制。最后,以 Service 的集群内与集群外的访问设置,讲述了 Service 的服务发现与服务暴露机制。

关于 Service 的有遗漏重要的知识点,或者有讲的不对的地方,也欢迎提出和指正!最后,但愿本篇对你学习 K8S 有所帮助~

相关文章
相关标签/搜索