3-独乐乐不如众乐乐搭桥修路迎接用户

20次阅读

共计 3422 个字符,预计需要花费 9 分钟才能阅读完成。

上篇文章有提到,通过 POD ID 只能够在 k8s 集群内部进行访问,作为一个博客!只给自己看 … 好像也行啊 …
但是每次都要先登录到集群节点中才能看 … 这就 …(脑海里闪过成吨 shell… ssh@192.10o… 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 上。

正文完
 0