39.Service
Service
Kubernetes Pod
是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。通过 ReplicationController
能够动态地创建和销毁 Pod
。每个 Pod
都会获取它自己的Pod
(称为Pod
(称为
关于 Service
Service
Pod
的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。这一组 Pod
能够被 Service
访问到,通常是通过 Label Selector
(查看下面了解,为什么可能需要没有Service
)实现的。
举个例子,假设有一个用于图片处理的运行了三个副本的Pod
实际上可能会发生变化,Service
定义的抽象能够解耦这种关联。
对Endpoints
Service
中的一组 Pod
发生变更,应用程序就会被更新。对非Service
,再由 Service
重定向到Pod
。
定义Service
一个 Service
在Pod
类似。
像所有的Service
定义可以基于
例如,假定有一组 Pod
,它们对外暴露了"app=MyApp"
标签。
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
上述配置将创建一个名称为 “my-service” 的 Service
对象,它会将请求代理到"app=MyApp"
的 Pod
上。这个 Service
将被指派一个Service
的Endpoints
对象上。
需要注意的是, Service
能够将一个接收端口映射到任意的 targetPort
。默认情况下,targetPort
将被设置为与 port
字段相同的值。targetPort
可以是一个字符串,引用了Pod
Pod
Service
,这种方式会提供更大的灵活性。例如,可以在
Service
TCP
和 UDP
协议,默认为 TCP
协议。
没有selector 的Service
Pod
,但也能够抽象其它类型的
- 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
- 希望服务指向另一个
Namespace
中或其它集群中的服务。 - 正在将工作负载转移到
Kubernetes 集群,和运行在Kubernetes 集群之外的backend 。
在任何这些场景中,都能够定义没有Service
:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
由于这个 Service
没有Endpoints
对象。可以手动将 Service
映射到指定的 Endpoints
:
kind: Endpoints
apiVersion: v1
metadata:
name: my-service
subsets:
- addresses:
- ip: 1.2.3.4
ports:
- port: 9376
注意:
访问没有Service
,与有Service
的原理相同。请求将被路由到用户定义的1.2.3.4:9376
Service
Service
的特例,它没有
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
当查询主机 my-service.prod.svc.CLUSTER
时,集群的my.database.example.com
的 CNAME
记录。访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在Service
的 type
。
VIP 和Service 代理
在kube-proxy
进程。kube-proxy
负责为 Service
实现了一种ExternalName
的形式。
在Service
是 “
在Ingress
从
在
userspace 代理模式
这种模式,Service
对象和 Endpoints
对象的添加和移除。
对每个 Service
,它会在本地
任何连接到“代理端口”的请求,都会被代理到 Service
的Pods
Endpoints
所报告的一样Pod
,是基于 Service
的 SessionAffinity
来确定的。
最后,它安装Service
的 clusterIP
(是虚拟Port
的请求,并重定向到代理端口,代理端口再代理请求到Pod
。
网络返回的结果是,任何到达 Service
的Service
、或 Pod
的任何信息。
默认的策略是,通过Pod
。
实现基于客户端service.spec.sessionAffinity
的值为 "ClientIP"
(默认值为 "None"
iptables 代理模式
这种模式,Service
对象和 Endpoints
对象的添加和移除。
对每个 Service
,它会安装Service
的 clusterIP
(虚拟Service
的一组Endpoints
对象,它也会安装Pod
。
默认的策略是,随机选择一个service.spec.sessionAffinity
的值设置为 "ClientIP"
(默认值为 "None"
和Service
的Service
、或 Pod
的任何信息。
这应该比Pod
没有响应,Pod
,所以它需要依赖 readiness probes。
ipvs 代理模式
这种模式,Service
对象和Endpoints
,调用netlink
接口以相应地创建Service
对象和Endpoints
对象同步
与
rr
:轮询调度lc
:最小连接数dh
:目标哈希sh
:源哈希sed
:最短期望延迟nq
:不排队调度
注意:
多端口Service
很多 Service
需要暴露多个端口。对于这种情况,Service
对象中定义多个端口。
当使用多个端口时,必须给出所有的端口的名称,这样
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
选择自己的IP 地址
在 Service
创建的请求中,可以通过设置 spec.clusterIP
字段来指定自己的集群service-cluster-ip-range
为何不使用round-robin DNS ?
一个不时出现的问题是,为什么我们都使用
- 长久以来,
DNS 库都没能认真对待DNS TTL 、缓存域名查询结果 - 很多应用只查询一次
DNS 并缓存了结果- 就算应用和库能够正确查询解析,每个客户端反复重解析造成的负载也是非常难以管理的
我们尽力阻止用户做那些对他们没有好处的事情,如果很多人都来问这个问题,我们可能会选择实现它。
服务发现
环境变量
当 Pod
运行在 Node
上,Service
添加一组环境变量。它同时支持 {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
变量,这里 Service
的名称需大写,横线被转换成下划线。
举个例子,一个名称为 "redis-master"
的
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
的Service
创建一组Pod
应该能够自动对 Service
进行名称解析。
例如,有一个名称为 "my-service"
的 Service
,它在"my-ns"
的 Namespace
中,为 "my-service.my-ns"
创建了一条
在名称为 "my-ns"
的 Namespace
中的 Pod
应该能够简单地通过名称查询找到 "my-service"
。在另一个 Namespace
中的 Pod
必须限定名称为 "my-service.my-ns"
。这些名称查询的结果是
"my-service.my-ns"
的 Service
有一个名为 "http"
的 TCP
端口,可以对 "_http._tcp.my-service.my-ns"
执行"http"
的端口号。
ExternalName
类型的
更多信息可以查看
Headless Service
有时不需要或不想要负载均衡,以及单独的spec.clusterIP
)的值为 "None"
来创建Headless
这个选项允许开发人员自由寻找他们自己的方式,从而降低与
对这类 Service
并不会分配Service
是否定义了
配置Selector
对定义了Endpoints
记录,并且修改Service
的后端 Pod
上。
不配置Selector
对没有定义Endpoints
记录。然而
ExternalName
类型Service 的CNAME 记录- 记录:与
Service 共享一个名称的任何Endpoints
,以及所有其它类型
- 记录:与
发布服务 —— 服务类型
对一些应用(如
ServiceTypes
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
) 。 没有任何类型代理被创建,这只有Kubernetes 1.7 或更高版本的kube-dns
才支持。
NodePort 类型
如果设置 type
的值为 "NodePort"
,Service
。该端口将通过 Service
的 spec.ports[*].nodePort
字段被指定。
如果需要指定的端口号,可以配置 nodePort
的值,系统将分配这个端口,否则调用
这可以让开发人员自由地安装他们自己的负载均衡器,并配置
需要注意的是,<NodeIP>:spec.ports[*].nodePort
和 spec.clusterIp:spec.ports[*].port
而对外可见。
LoadBalancer 类型
使用支持外部负载均衡器的云提供商的服务,设置 type
的值为 "LoadBalancer"
,将为 Service
提供负载均衡器。负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service
的 status.loadBalancer
字段被发布出去。
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
nodePort: 30061
clusterIP: 10.0.171.239
loadBalancerIP: 78.11.24.19
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 146.148.47.155
来自外部负载均衡器的流量将直接打到Pod
loadBalancerIP
来创建负载均衡器。
某些云提供商允许设置 loadBalancerIP
。如果没有设置 loadBalancerIP
,将会给负载均衡器指派一个临时
如果设置了 loadBalancerIP
,但云提供商并不支持这种特性,那么设置的 loadBalancerIP
值将会被忽略掉。
ExternalName
my-service
或 cassandra
。使用 spec.externalName
参数指定这些服务。
例如,这个服务定义将my-service
映射到 my.database.example.com
。
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
注意:
警告
对于一些常见的协议,包括
对于使用主机名的协议,这种差异可能导致错误或意外的响应。Host: header
,
外部IP
如果外部的Service
externalIPs
。通过外部Service
的端口上的流量,将会被路由到 Service
的externalIPs
不会被
根据 Service
的规定,externalIPs
可以同任意的 ServiceType
来一起指定。在下面的例子中,my-service
可以在 80.11.12.10:80
(外部
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
不足之处
为Service
的大型集群。查看 最初设计方案 获取更多细节。
使用Service
的数据包的源
Type
字段支持嵌套功能 —— 每一层需要添加到上一层里面。不会严格要求所有云提供商(例如,LoadBalancer
能工作而分配一个 NodePort
,但是
未来工作
未来我们能预见到,代理策略可能会变得比简单的Service
将具有 “真正” 的负载均衡器,这种情况下
在未来的版本中,Service
的支持。为 Service
实现更加灵活的请求进入模式,这些 Service
包含当前 ClusterIP
、NodePort
和 LoadBalancer
模式等。
关于虚拟IP 的细节
对很多想使用 Service
的人来说,前面的信息应该足够了。然而,有很多内部原理性的内容,还是值去理解的。
避免冲突
为了使用户能够为他们的 Service
选择一个端口号,我们必须确保不能有Service
发生冲突。我们可以通过为每个 Service
分配它们自己的
为了保证每个 Service
被分配到一个唯一的Service
。
为了使 Service
能够获取到Service
将会失败,指示一个Service
使用它们。
IP 和VIP
不像 Pod
的Service
的iptables
(Service
的
Userspace
作为一个例子,考虑前面提到的图片处理应用程序。
当创建Service
Service
的端口是Service
会被集群中所有的 kube-proxy
实例观察到。当代理看到一个新的 Service
, 它会打开一个新的端口,建立一个从该
当一个客户端连接到一个Service代理
的端口。Service代理
选择一个
这意味着 Service
的所有者能够选择任何他们想使用的端口,而不存在冲突的风险。客户端可以简单地连接到一个Pod
。
Iptables
再次考虑前面提到的图片处理应用程序。
当创建Service
Service
的端口是Service
会被集群中所有的 kube-proxy
实例观察到。当代理看到一个新的 Service
, 它会安装一系列的Service
Service
Endpoint
Endpoint
当一个客户端连接到一个
不像
API 对象
在