kubernetes service

Service是一组Pod的服务抽象,相当于一组Pod的LB,负责将请求分发给对应的 Pod;Service会为这个LB提供一个IP,一般称为ClusterIP。

service 概述

img_1.png

img.png

Kubernetes 里的 Service 具有一个全局唯一的虚拟 ClusterIP 地址,Service 一旦被创建, Kubernetes 就会自动为它分配一个可用的 clusterIP 地址,而且在 Service 整个生命周期中,它的 clusterIP 地址都不会改变,客户端可以通过这个虚拟 IP 地址+服务的端口直接访问该服务, 再通过部署 Kubernetes 集群的 DNS 服务,就可以实现 Service Name(域名)到 clusterIP 地址的 DNS 映射功能,我们只要使用服务的名称( DNS 名称 )即可完成到目标服务的访问请求 。“ 服务发现“这个传统架构中的棘手问题在这里首次得以完美解决,同时,凭借 lusterIP 地址的独特设计, kubernetes 进一 步实现了 Service 的透明负载均衡和故障自动恢复的高级特性。

Service: Cluster IP

img_2.png

每个 Pod 都会被分配一个单独的 IP 地址,而且每 Pod 都提供了一个独立的 Endpoint (Pod IP + container port) 以被客户端访问,那么现在多个 Pod 副本组成了一个集群来提供服务,客户端如何访问它们呢? 传统的做法是部署一个负载均衡器(软件或硬件),为这组 Pod 开启一个对外的服务端口如 8000 端口,并且将这些 Pod Endpoint 表加入 8000 端口的转发列表中,客户端就可以通过负载均衡器的对外IP地址+8000 端口访问此服务了。kubernetes 也是类似的做法,Kubernetes 内部在每个 Node 上都运行了
一套全局的虚拟负载均衡器 kube-proxy ,自动注入并自动实时更新集群中所有 Service 的路由表,通过iptables 或者 IP 机制, 把对 Service 的请求转发到其后端对应的某个 Pod 实例上,并在内部实现服务的负载均衡与会话保待机制 不仅如此, Kubernetes 还采用了 种很巧妙又影响深远的设计一Cluster IP 地址 .我们知道, pod的Endpoint 地址会随着 Pod 的销毁和重新创建而发生改变, 因为新 Pod IP 地址与之前旧 Pod 的不同 ,Service 一旦被创建,
Kubernetes 就会自动为它 配一个全局唯一的虚拟 IP 地址——Cluster 地址,而且在Service 的整个生命周期内,ClusterIP 地址不会发生改变,这样一来,每个服务就变成了具备唯一 IP 的通信节 ,远程服务之间的通信间题就变成了基础的 TCP 网络通信问题。

查看cluster ip

1
kubectl get svc tomcat-service -o yaml

img_6.png

Service是一组Pod的服务抽象,相当于一组Pod的LB,负责将请求分发给对应的 Pod;Service会为这个LB提供一个IP,一般称为ClusterIP。

service 定义

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376

上述配置创建一个名称为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 “app=MyApp” 的 Pod 上。
ports 定义部分指定了 Service 本身的端口号为 8080, targetPort 则用来指定 后端 Pod 的容器端口号

Kubernetes 为该服务分配一个 ClusterIP 地址(有时称为 “集群IP”),该 IP 地址由服务代理使用。

服务选择算符的控制器不断扫描与其选择器匹配的 Pod,然后将所有更新发布到也称为 “my-service” 的 Endpoint 对象。
img_7.png

service type

ClusterIP

通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是默认的 ServiceType。
当然,用户也可手工指定一个 ClusterIP 地址,不过需要确保该 IP 在 Kubernetes 集群设置 clusterIP 地址范围内(通过 kube-apiserver 服务的启 动参数– service-cluster-ip-range 设置),并且没有被其他 Service 使用

NodePort

将 Service 的端口号映射到每个 Node 一个端口号上,这样集群中的 任意 Node 都可以作为 Service 访问入口地址,即 NodeIP:NodePort.

通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>,你可以从集群的外部访问一个 NodePort 服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: MyApp
ports:
# 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。
- port: 80
targetPort: 80
# 可选字段
# 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号(默认:30000-32767)
nodePort: 30007

通过将Service的类型设置为NodePort,就可以在Cluster中的主机上通过一个指定端口暴露服务。注意通过Cluster中每台主机上的该指定端口都可以访问到该服务,发送到该主机端口的请求会被kubernetes路由到提供服务的Pod上。采用这种服务类型,可以在kubernetes cluster网络外通过主机IP:端口的方式访问到服务。
img_5.png

1
2
3
4
5
6
7
8
9
10
11
kind: Service
apiVersion: v1
metadata:
name: influxdb
spec:
type: NodePort
ports:
- port: 8086
nodePort: 31112
selector:
name: influxdb

LoadBalancer

将 Service 映射到一个已存在的负载均衡器的 IP 地址上,通常在公有云环境中使用.

在使用支持外部负载均衡器的云提供商的服务时,设置 type 的值为 “LoadBalancer”, 将为 Service 提供负载均衡器。 负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service 的 status.loadBalancer 字段发布出去。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.0.2.127

来自外部负载均衡器的流量将直接重定向到后端 Pod 上,不过实际它们是如何工作的,这要依赖于云提供商。

某些云提供商允许设置 loadBalancerIP。 在这些情况下,将根据用户设置的 loadBalancerIP 来创建负载均衡器。 如果没有设置 loadBalancerIP 字段,将会给负载均衡器指派一个临时 IP。 如果设置了 loadBalancerIP,但云提供商并不支持这种特性,那么设置的 loadBalancerIP 值将会被忽略掉。

ExternalName

将Service 映射为 一个外部域名地址 ,通过 externalName 字段进行设置。
通过返回 CNAME 和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理。

headless service

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 “None” 来创建 Headless Service。

你可以使用无头 Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。

对这无头 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。

  • 带选择算符的服务
    对定义了选择算符的无头服务,Endpoint 控制器在 API 中创建了 Endpoints 记录, 并且修改 DNS 配置返回 A 记录(IP 地址),通过这个地址直接到达 Service 的后端 Pod 上。

  • 无选择算符的服务
    对没有定义选择算符的无头服务,Endpoint 控制器不会创建 Endpoints 记录。 然而 DNS 系统会查找和配置,无论是:

对于 ExternalName 类型的服务,查找其 CNAME 记录
对所有其他类型的服务,查找与 Service 名称相同的任何 Endpoints 的记录

在 spec.ports 的定义中, targetPort 属性用来确定提供该服务的容器所悬露 (Expose)
的端口号,即具体的业务进程在容器内的 targetPort 上提供 TCP/IP 接入; port 属性则定义
Service 的端口。前面定义 Tomcat 服务时并没有指定 targetPort, 所以 targetPort 默认与
port 相同。除了正常的 service ,还有一种特殊的 Service – Headless service ,只要在 Service
的定义中设置了 clusterIP: None, 就定义了一个 Headless service, 它与普通 service 的关键
区别在于它没有 ClusterIP 地址,如果解析 Headless service DNS 域名,则返回的是该
Service 对应的全部 Pod Endpoint 列表,这意味着客户端是直接与后端的 pod建立 TCP/IP
连接进行通信的,没有通过虚拟 clusterIP 地址进行转发,因此通信性能最高,等同千”原
生网络通信”。

Service 的外网访问问题

  • Service 暴霓到集群外部

    Kubernetes Service 创建的 ClusterIP 地址是对后端 Pod 列表的一层抽象,对于集群
    外部来说并没有意义,但有许多 Service 是需要对集群外部提供服务的, Kubernetes 提供
    了多种机制将 Service 暴露出去,供集群外部的客户端访问 这可以通过 Service 资源对象
    的类型字段 “type:【clusterIp, NodePort, LoadBalancer, ExternalName】” 进行设置。

img_9.png

前面提到,服务的 lusterIP 地址在 Kubernetes 集群内才能被访问,那么如何让集群
外的应用访问我们的服务呢?这也是一个相对复杂的问题 要弄明白这个问题的解决思路
和解决方法,我们需要先弄明白 Kubernetes 的三种 IP, 这三种 IP 分别如下

  • Node IP: Node IP 地址
  • Pod IP: Pod IP 地址
  • Service IP: Service IP 地址

首先, Node IP 是 Kubernetes 集群中每个节点的物理网卡的 IP 地址,是 个真实存在
的物理网络,所有属于这个网络的服务器都能通过这个网络直接通信,不管其中是否有部
分节点不属于这个 Kubernetes 集群 这也表明 ubernetes 集群之外的节点访问 Kubernetes
集群内的某个节点或者 TCP/I 务时,都必须通过 Node IP.
其次, Pod IP 是每个 Pod IP 地址,在使用 Docker 作为容器支持引擎的情况下,它
Docker Engine 艰据 dockerO 网桥的 IP 地址段进行分配的,通常是一个虚拟二层网络。
前面说过, Kubernetes 要求位于不同 Node的Pod 能够彼此直接通信,所以 Kubernetes
中一个 Pod 里的容器访问另外一个 Pod 里的容器时, 就是通过 Pod IP 所在的虚拟二层网
络进行通信的,而真实的 TCP IP 流童是通过 Node IP 所在的物理网卡流出的.

在Kubernetes 集群内, Service ClusterIP 地址属于集群内的地址,无法在 群外
接使用这个地址 为了解决这个问题, Kubernetes 首先引入了 NodePort 这个概念, NodePort
也是解决集群外的应用访问集群内服务的直接、有效的常见做法

  • NodePort
  • 负载均衡器组件独立于 Kubernetes 集群之外
    img_8.png

参考文章

评论