[TOC] > 官网:https://kubernetes.io/docs/concepts/services-networking/service/ # 介绍 * RC、RS和Deployment保障支撑服务Pod的数量 * Service解决访问Pod的问题 * 每个Service对应集群内部一个有效虚拟IP * 集群内部通过虚拟IP访问一个服务 * K8s内部负载均衡为Kube-proxy * 每个节点都有一个Kube-proxy * Service是由标签选择器定义的一组pods * `Service`能将一个接收`port`映射到任意的`targetPort` * 默认`Service`的`targetPort`和`port`的值相同 * 支持多个端口、协议可以相同也可以不相同 ![](https://img.kancloud.cn/af/19/af19d85a1bd730016f5254ead0e9719b_652x343.png) # 举例 ## 有selector的Service 一组`Pod`对外暴露6379端口,标签为`app=MyRedis` ```yaml apiVersion: v1 kind: Service metadata: name: redis-master spec: selector: app: MyRedis ports: - protocol: TCP port: 80 targetPort: 6379 ``` > 通过上述配置创建名称为“redis-master”的`Service`对象,将请求代理到使用TCP端口6379,并且标签为`app=MyRedis`的`Pod` ## 无selector的Service Service常规用法是抽象化对·Pod·的访问,也可以抽象化其他种类的后端: * 在生产环和测试环境中使用不同的数据库集群 * 服务指向另一个命名空间中或其他集群中的服务 * 在K8s集群中运行部分后端,用于评估将工作负载迁移到K8S ``` apiVersion: v1 kind: Service metadata: name: redis-master spec: ports: - protocol: TCP port: 80 targetPort: 6379 ``` 由于定义的`Service`没有selector,所以不会自动创建相应的Endpoint对象,需要手动添加Endpoint对象,将服务手动映射到运行该服务的网络地址和端口 ```yaml apiVersion: v1 kind: Endpoints metadata: name: redis-master subsets: - addresses: - ip: 192.168.136.200 ports: - port: 6379 ``` > * 端点 IPs 不可以是环回( IPv4 的 127.0.0.0/8 , IPv6 的 ::1/128 ) > * 不可以是本地链接(IPv4 的 169.254.0.0/16 和 224.0.0.0/24,IPv6 的 fe80::/64) > * 端点 IP 地址不能是其他 Kubernetes Services 的群集 IP,因为kube-proxy不支持将虚拟 IP 作为目标 访问没有 selector 的`Service`,与有 selector 的`Service`的原理相同。 请求将被路由到用户定义的 Endpoint, YAML中为:`192.168.136.200:6379`(TCP) ### ExternalName Service * ExternalName `Service`无Selector,也没有用DNS名称代替,是`Service`的特例 * ExternalName类型的`Service`将服务映射到DNS名称,如`redis-service`,可以通过`spec.externalNmae`参数指定服务 #### 举例 将`prod`名称空间中的`redis-master`服务映射到`my.redis.pmk8s.com` ```yaml apiVersion: v1 kind: Service metadata: name: redis-master namespace: prod spec: type: ExternalName externalName: my.redis.pmk8s.com ``` > ExternalName 接受 IPv4 地址字符串,但作为包含数字的 DNS 名称,而不是 IP 地址。 类似于 IPv4 地址的外部名称不能由 CoreDNS 或 ingress-nginx 解析,因为外部名称旨在指定规范的 DNS 名称。 要对 IP 地址进行硬编码,需要使用`headless Services` 当查找主机`redis-service.prod.svc.cluster.local`时,群集DNS服务返回`CNAME`记录,其值为`my.redis.pmk8s.com`。 访问`redis-service`的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发 # Service代理 kube-proxy负责为`Service`实现了一种VIP(虚拟IP)的形式,而不是ExternalName的形式 ## userspace代理模式 ## iptables代理模式 ## IPVS代理模式 # 多端口Service * 定义多个端口时,必须提供端口名称 * 端口名称只能包含小写字母、数字字符和`-` * 端口号名称必须以字母数字字符开头和结尾 ``` apiVersion: v1 kind: Service metadata: name: redis-master spec: selector: app: MyRedis ports: - name: http protocol: TCP port: 80 targetPort: 6379 - name: https protocol: TCP port: 443 targetPort: 6380 ``` # 选择自己的IP地址 * 在` Service`创建请求中,通过设置`spec.clusterIP`字段指定自己的集群IP地址 * 指定的IP地址必须合法,并且这个IP地址在`service-cluster-ip-range` CIDR范围内 ``` apiVersion: v1 kind: Service metadata: name: redis-master spec: selector: app: MyRedis clusterIP: 10.96.0.13 ports: - name: http protocol: TCP port: 80 targetPort: 6379 - name: https protocol: TCP port: 443 targetPort: 6380 ``` # 服务发现 K8s支持环境变量和DNS两种服务发现模式 * 环境变量 * 当`Pod`运行在`Node`上,kubelet 会为每个活跃的`Service`添加一组环境变量 * 它同时支持 Docker links兼容变量、简单的`{SVCNAME}_SERVICE_HOST`和`{SVCNAME}_SERVICE_PORT`变量 * 这里`Service`的名称需大写,横线被转换成下划线。 例子,一个名称为`"redis-master"`的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.96.0.13,这个 Service 生成了如下环境变量: ```shell REDIS_MASTER_SERVICE_HOST=10.96.0.13 REDIS_MASTER_SERVICE_PORT=6379 REDIS_MASTER_PORT=tcp://10.96.0.13:6379 REDIS_MASTER_PORT_6379_TCP=tcp://10.96.0.13:6379 REDIS_MASTER_PORT_6379_TCP_PROTO=tcp REDIS_MASTER_PORT_6379_TCP_PORT=6379 REDIS_MASTER_PORT_6379_TCP_ADDR=10.96.0.13 ``` > 通过环境变量访问服务的Pod时,服务创建必须在客户端Pod出现之前创建好 * DNS * 使用[附加组件](https://kubernetes.io/docs/concepts/cluster-administration/addons/)为Kubernetes集群设置DNS服务。 * 支持群集的DNS服务器(例如CoreDNS)监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录 * 如果在整个群集中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务 * Kubernetes DNS 服务器是唯一的一种能够访问`ExternalName`类型的 Service 的方式 * Kubernetes 还支持命名端口的 DNS SRV(服务)记录 例如,如果您在 Kubernetes 命名空间`"my-ns"`中有一个名为`"redis-master"`的服务, 则控制平面和DNS服务共同为`"redis-master.my-ns"`创建 DNS 记录。`"my-ns"`命名空间中的Pod应该能够通过简单地对`redis-master`进行名称查找来找到它(`"redis-master.my-ns"`也可以工作)。 其他命名空间中的Pod必须将名称限定为`redis-master.my-ns`。 这些名称将解析为为服务分配的群集IP。 如果`"redis-master.my-ns"`服务具有名为`"http"` 的端口,且协议设置为`TCP`, 则可以对`_http._tcp.redis-master.my-ns`执行DNS SRV查询查询以发现该端口号,`"http"`以及IP地址 # Headless Services 有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(`spec.clusterIP`)的值为`"None"`来创建`Headless`Service。 您可以使用 headless Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。 对这 headless`Service`并不会分配 Cluster IP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于`Service`是否定义了 selecto # 发布服务 * Kubernetes`ServiceTypes`允许指定一个需要的类型的 Service,默认是`ClusterIP`类型 * `Type`的取值以及行为如下: * `ClusterIP`:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的`ServiceType`。 \*[`NodePort`](https://kubernetes.io/zh/docs/concepts/services-networking/service/#nodeport):通过每个 Node 上的 IP 和静态端口(`NodePort`)暴露服务。 * `NodePort`服务会路由到`ClusterIP`服务,这个`ClusterIP`服务会自动创建。通过请求`<NodeIP>:<NodePort>`,可以从集群的外部访问一个`NodePort`服务。 * `LoadBalancer`:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到`NodePort`服务和`ClusterIP`服务。 * `ExternalName`:通过返回`CNAME`和它的值,可以将服务映射到`externalName`字段的内容(例如,`foo.bar.example.com`)。 没有任何类型代理被创建 ## NodePort类型 如果将`type`字段设置为`NodePort`,则 Kubernetes 控制平面将在`--service-node-port-range`标志指定的范围内分配端口(默认值:30000-32767)。 每个节点将那个端口(每个节点上的相同端口号)代理到您的服务中。 您的服务在其`.spec.ports[*].nodePort`字段中要求分配的端口。 如果您想指定特定的IP代理端口,则可以将 kube-proxy 中的`--nodeport-addresses`标志设置为特定的IP块。从Kubernetes v1.10开始支持此功能。 该标志采用逗号分隔的IP块列表(例如10.0.0.0/8、192.0.2.0/25)来指定 kube-proxy 应该认为是此节点本地的IP地址范围。 例如,如果您使用`--nodeport-addresses=127.0.0.0/8`标志启动 kube-proxy,则 kube-proxy 仅选择 NodePort Services 的环回接口。`--nodeport-addresses`的默认值是一个空列表。 这意味着 kube-proxy 应该考虑 NodePort 的所有可用网络接口。 (这也与早期的Kubernetes版本兼容)。 如果需要特定的端口号,则可以在`nodePort`字段中指定一个值。 控制平面将为您分配该端口或向API报告事务失败。 这意味着您需要自己注意可能发生的端口冲突。 您还必须使用有效的端口号,该端口号在配置用于NodePort的范围内。 使用 NodePort 可以让您自由设置自己的负载平衡解决方案,配置 Kubernetes 不完全支持的环境,甚至直接暴露一个或多个节点的IP。 需要注意的是,Service 能够通过`<NodeIP>:spec.ports[*].nodePort`和`spec.clusterIp:spec.ports[*].port`而对外可见 ## LoadBalancer 类型 使用支持外部负载均衡器的云提供商的服务,设置`type`的值为`"LoadBalancer"`,将为`Service`提供负载均衡器。 负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过`Service`的`status.loadBalancer`字段被发布出去 外部IP,如转到外部的DB存储 ```yaml apiVersion: v1 kind: Service metadata: name: redis-master spec: selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 6379 clusterIP: 10.0.171.239 loadBalancerIP: 78.11.24.19 type: LoadBalancer status: loadBalancer: ingress: - ip: 146.148.47.155 ``` 来自外部负载均衡器的流量将直接打到 backend`Pod`上,不过实际它们是如何工作的,这要依赖于云提供商。 在这些情况下,将根据用户设置的`loadBalancerIP`来创建负载均衡器。 某些云提供商允许设置`loadBalancerIP`。如果没有设置`loadBalancerIP`,将会给负载均衡器指派一个临时 IP。 如果设置了`loadBalancerIP`,但云提供商并不支持这种特性,那么设置的`loadBalancerIP`值将会被忽略掉。 ## 外部 IP 如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes`Service`会被暴露给这些`externalIPs`。 通过外部 IP(作为目的 IP 地址)进入到集群,打到`Service`的端口上的流量,将会被路由到`Service`的 Endpoint 上。`externalIPs`不会被 Kubernetes 管理,它属于集群管理员的职责范畴。 根据`Service`的规定,`externalIPs`可以同任意的`ServiceType`来一起指定。 在上面的例子中,`redis-master`可以在 “`192.168.136.200:80`”(`externalIP:port`) 上被客户端访问 ```yaml apiVersion: v1 kind: Service metadata: name: redis-master spec: selector: app: MyApp ports: - name: http protocol: TCP port: 80 targetPort: 6379 externalIPs: - 192.168.136.200 ```