kubernetes 中的 Service

1,199 阅读6分钟

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

Connecting Users to Pods

如果我们要让一个用户能够使用应用程序,用户需要能访问到 pod,但是 pod 是一个短暂存在的东西,很可能突然挂了然后重启,这时候 ip 地址就会改变,所以 pod 的 ip 地址并不是静态的。比如说:

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

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

Services

比如,我们用 app 作为 key,db 和 frontend 作为 value 来区分 pod:

image.png

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

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

image.png

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:

image.png

用户现在可以通过 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。

kube-proxy

所有的 worker node 都有一个后台任务,叫做 kube-proxy。这个 kube-proxy 会检测 API Server 上对于 service 和 endpoint 的新增或者移除。对于每个新的 service,在每个 node 上,kube-proxy 都会设置相应的 iptables 的规则来记录应该转发的地址。当一个 service 被删除的时候,kube-proxy 会在所有的 pod 上移除这些 iptables 的规则。

image.png

服务发现

我们已经知道,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。

DNS

kubernetes 有一些 dns 的 addon,这些 addon 会自动为所有 service 创建一个类似 my-svc.my-namespace.svc.cluster.local 的 dns 解析,并且在同一个 namespace 里面的 service 可以直接用 service name 进行访问。这是最为推荐的方法。

Service 类型

当我们定义一个 service 的时候,我们可以选择可访问的范围,比如:

  • 是否只能在 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。

image.png NodePort 的这个 service type 在我们想要让外网访问我们服务的时候非常有用,用户通过访问 node 上指定的 port 就可以访问到这个 service。管理员可以在 kubernetes 集群外再搭一个反向代理就可以更方便地进行访问了。

LoadBalancer

对于 LoadBalancer 这个 Servicetype:

  • NodePort 和 ClusterIP 会被自动创建,外部的 load balancer 会自动路由上去
  • service 会在一个静态的端口上被暴露
  • 通过底层的 cloud provider 提供的 load balancer 来暴露到外网

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

ExternalIP

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

image.png 需要注意的是,ExternalIP 并不是由 k8s 自动管理的,是由管理员手动设置路由到其中的一个 node 上的。

ExternalName

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

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

相关文章

  • Kubernetes 中的 Network
  • kubernetes 中的 ingress
  • 如何使用 Helm 进行本地开发
  • 使用 kubeadm 创建一个 kubernetes 集群
  • Service Mesh Istio 初探