K8s Service(3)

Service

概念介绍

虽然每个Pod都会被分配一个单独的IP地址,但这个IP地址会随着Pod的销毁而消失。引出的一个问题是:如果有一组Pod组成一个应用集群来提供服务,那么该如何访问它们呢?

Service就是用来解决这个问题的,一个Service可以看作一组提供相同服务的Pod的对外接口,Service是通过LabelSelector选择一组Pod作为后端服务提供者。

定义

一个 Service 在 Kubernetes 中是一个 REST 对象,和 Pod 类似。
例如,假定有一组 Pod,它们对外暴露了 3000 端口,同时还被打上 app=jaymz 标签。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: jaymz
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000

上述配置创建一个名称为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 3000,并且具有标签 "app=jaymz"Pod 上。 Kubernetes 为该服务分配一个 IP 地址(有时称为 “集群IP” )。

多端口 Service

对于某些服务,需要公开多个端口。 Kubernetes允许在Service对象上配置多个端口定义。 当使用多个端口时,必须提供所有端口名称,以使它们无歧义。 例如:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 3000
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9000

服务代理

在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。

为什么不使用 DNS 轮询?

时不时会有人问道,就是为什么 Kubernetes 依赖代理将入站流量转发到后端。 那其他方法呢? 例如,是否可以配置具有多个A值(或IPv6为AAAA)的DNS记录,并依靠轮询名称解析?

使用服务代理有以下几个原因:

  • DNS 实现的历史由来已久,它不遵守记录 TTL,并且在名称查找结果到期后对其进行缓存。
  • 有些应用程序仅执行一次 DNS 查找,并无限期地缓存结果。
  • 即使应用和库进行了适当的重新解析,DNS 记录上的 TTL 值低或为零也可能会给 DNS 带来高负载,从而使管理变得困难。

版本兼容性

从Kubernetes v1.0开始,您已经可以使用 用户空间代理模式。 Kubernetes v1.1添加了 iptables 模式代理,在 Kubernetes v1.2 中,kube-proxy 的 iptables 模式成为默认设置。 Kubernetes v1.8添加了 ipvs 代理模式。

虚拟IP

Service一般会提供一个虚拟IP(Virtual IP),它在Service创建之后就保持不变,并且能够被同一集群中的Pod资源所访问。Service端口用于接收客户端请求并将其转发给后端的Pod应用程序,这种代理机制称为“端口代理”或四层代理,工作于TCP/IP协议栈的传输层。

API Server会持续监听(watch)Pod的变化(增加、删除),并实时更新Endpoint,Endpoint是一个由Pod的IP地址和端口组成的列表,Service会从Endpoint列表中选择一条记录进行流量调度。

服务代理 - userspace(用户空间)、iptables、ipvs

在众多的Pod对象中,Service资源是如何选择正确的Pod进行流量调度的呢?

这就是我们要具体介绍的,Kubernetes提供的“端口代理”的几种实现方式。

简单来说,一个Service对象就是Node上的一些iptables或ipvs规则,用于将到达Service对象的流量调度到相应的Pod上。Kubernetes集群中的每个Node上都会有产生一个Kube-proxy程序,它通过API-Server持续监控各Service及其关联的Pod对象,并将其变化实时反映至当前节点的iptables或ipvs规则上。

kube-Proxy将请求代理至相应端点的方式有三种:userspace(用户空间)、iptables、ipvs
userspace代理模型

这里的userspace是指Linux操作系统的用户空间。对于每个Service对象它会打开一个本地端口(运行于用户空间的kube-proxy进程负责监听此端口),任何到达此代理端口的连接请求都会被代理至当前Service资源后端的各Pod对象上,至于会挑选哪个Pod取决于Service资源的调度方式,默认为轮询(roundrobin)

这种代理模式请求流量到达内核空间后经由套接字送往用户空间的kube-proxy,而后由它送回内核空间,并调度至后端pod。这种方式请求会在内核和用户空间之间来回转发导致效率不高。

iptables代理模型

iptables代理模式和前一种代理模式是类似的,都是由kube-proxy来跟踪监听API-server上的service和Endpoints的变更。但是有一点不同的是iptables规则直接捕获到达cluster IP和port的流量,并将其重定向至当前Service的后端,对于每个Endpoints对象,Service资源会为其创建iptables规则并关联至挑选的后端pod资源,默认算法是随机调度(random)。iptables代理模式在Kubernetes1.1版本引入,并在1.2版开始成为默认类型。

在创建service资源时,集群中每个节点上的kube-proxy都会接受到通知并将其定义为当前节点上的iptables规则,用于转发接收到的iptables,进行调度和目标地址转换(DNAT)后再转发至集群内部的pod对象上。

默认的策略是,kube-proxy 在 iptables 模式下随机选择一个 backend。

相对于用户空间来讲,iptables模型无须将流量在用户空间和内核空间来回切换,因更加高效和可靠。

ipvs代理模型

Kubernetes从1.9版本引入ipvs代理模型,且从1.11版本起成为默认设置。它和iptables模型很类似,唯一一点不同的是在其请求流量的调度功能由ipvs实现,余下的功能仍由iptables完成。

ipvs是建立在netfilter的钩子函数上,但它使用hash表作为底层数据结构并工作于内核空间,因此流量转发速度特别快、规则同步性很好,而且它支持众多调度算法,rr(轮询)、lc(最小连接数)、dh(目标哈希)、sh(源哈希)、sed(最短期望延迟)、nq(不排队调度)。

服务发现

Kubernetes 支持2种基本的服务发现模式 —— 环境变量和 DNS。

环境变量

Pod 运行在 Node 上,kubelet 会为每个活跃的 Service 添加一组环境变量, {SVCNAME}_SERVICE_HOST{SVCNAME}_SERVICE_PORT 变量,这里 Service 的名称需大写,横线被转换成下划线。

举例,一个名称为 "redis-master" 的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

注意: 必须在Pod出现 之前 创建Service, 否则,这些Pod将不会设定其环境变量。 如果仅使用DNS查找Service的群集IP,则无需担心环境变量问题。

DNS

您可以使用附加组件为Kubernetes集群设置DNS服务。

支持群集的DNS服务器(例如CoreDNS)监视 Kubernetes API 中的新Service,并为每个Service创建一组 DNS 记录。 如果在整个集群中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析出Service对应的IP地址。

Headless Services

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

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

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

配置 Selector

对定义了 selector 的 Headless Service,Endpoint 控制器在 会创建 Endpoints 记录,并且修改 DNS 配置返回 A 记录(地址),通过这个地址直接到达 Service 的后端 Pod 上。

配置 Selector的Headless Services应用场景
  1. 自主选择权,有时候client想自己来决定使用哪个Endpoints记录,可以通过查询DNS来获取Endpoints的信息。

  2. Headless Services还有一个用处(PS:也就是我们需要的那个特性)。Headless Service的对应的每一个Endpoints,即每一个Pod,都会有对应的DNS域名;这样Pod之间就可以互相访问。

不配置 Selector

对没有定义 selector 的 Headless Service,Endpoint 控制器不会创建 Endpoints 记录。 然而 DNS 系统会查找和配置,无论是:

  • ExternalName 类型 Service 的 CNAME 记录
  • 记录:与 Service 共享一个名称的任何 Endpoints,以及所有其它类型
不配置 Selector的Headless Services应用场景

Service最常见的应用是作为Pod的负载均衡器(反向代理)使用,抽象化对 Kubernetes Pod 的访问,但是Service也可以作为其他应用程序的反向代理来使用。 实例:

  • 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
  • 希望Service指向另一个Namespace中或其它集群中的服务。
  • 您正在将工作负载迁移到 Kubernetes。 在评估该方法时,您仅在 Kubernetes 中运行一部分后端。
    服务没有选择器,因此不会自动创建相应的 Endpoint 对象。 您可以通过手动添加 Endpoint 对象,将服务手动映射到运行该服务的网络地址和端口:
apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 192.0.2.42
    ports:
      - port: 9376

注意: 端点 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.0.2.42:9376 (TCP)。

ExternalName ServiceService 的特例,它没有 selector,也没有使用 DNS 名称代替。

类型ExternalName

类型为 ExternalName 的服务将Service 映射到 DNS 名称,而不是典型的选择器,例如 my-service , 您可以使用 spec.externalName 参数指定这些服务。

例如,以下 Service 定义将 prod 名称空间中的 my-service 服务映射到 my.database.example.com

apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

注意: ExternalName 接受 IPv4 地址字符串,但作为包含数字的 DNS 名称,而不是 IP 地址。 类似于 IPv4 地址的外部名称不能由 CoreDNS 或 ingress-nginx 解析,因为外部名称旨在指定规范的 DNS 名称。 要对 IP 地址进行硬编码,请考虑使用 headless Services

当查找主机 my-service.prod.svc.cluster.local 时,群集DNS服务返回 CNAME 记录,其值为 my.database.example.com。 访问 my-service 的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发。 如果以后您决定将数据库移到群集中,则可以启动其 Pod,添加适当的选择器或端点以及更改Service的类型。

外部 IP

如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 externalIPs。 通过外部 IP(作为目的 IP 地址)进入到集群,打到 Service 的端口上的流量,将会被路由到 Service 的 Endpoint 上。 externalIPs 不会被 Kubernetes 管理,它属于集群管理员的职责范畴。

发布服务 —— 服务类型

有一些Pod可能希望将服务暴露给外部应用调用,可以通过Service,选择合适的类型来实现。

Service的默认值是 ClusterIP 类型。

Type 的可选值以及行为如下:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType
  • NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 <NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务。
  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。
  • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建。

注意: 您需要 CoreDNS 1.7 或更高版本才能使用 ExternalName 类型。

您也可以使用 Ingress 来暴露自己的服务。 Ingress 不是服务类型,但它充当集群的入口点。 它可以将路由规则整合到一个资源中,因为它可以在同一IP地址下公开多个服务。

保留源 IP

各种类型的 Service 对源 IP 的处理方法不同:

  • 使用 userspace 代理,隐藏了访问 Service 的数据包的源 IP 地址。 这使得一些类型的防火墙无法起作用。

  • ClusterIP Service:使用 iptables 模式,集群内部的源 IP 会保留(不做 SNAT)。如果 client 和 server pod 在同一个 Node 上,那源 IP 就是 client pod 的 IP 地址;如果在不同的 Node 上,源 IP 则取决于网络插件是如何处理的,比如使用 flannel 时,源 IP 是 node flannel IP 地址。

  • NodePort Service:默认情况下,源 IP 会做 SNAT,server pod 看到的源 IP 是 Node IP。为了避免这种情况,可以给 service 设置 spec.ExternalTrafficPolicy=Local (1.6-1.7 版本设置 Annotation service.beta.kubernetes.io/external-traffic=OnlyLocal),让 service 只代理本地 endpoint 的请求(如果没有本地 endpoint 则直接丢包),从而保留源 IP。

  • LoadBalancer Service:默认情况下,源 IP 会做 SNAT,server pod 看到的源 IP 是 Node IP。设置 service.spec.ExternalTrafficPolicy=Local 后可以自动从云平台负载均衡器中删除没有本地 endpoint 的 Node,从而保留源 IP。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335