服务概述
直接访问Pod的问题
Pod创建完成后,如何访问Pod呢?直接访问Pod会有如下几个问题:
- Pod会随时被Deployment这样的控制器删除重建,那访问Pod的结果就会变得不可预知。
- Pod的IP地址是在Pod启动后才被分配,在启动前并不知道Pod的IP地址。
- 应用往往都是由多个运行相同镜像的一组Pod组成,逐个访问Pod也变得不现实。
举个例子,假设有这样一个应用程序,使用Deployment创建了前台和后台,前台会调用后台做一些计算处理,如图1所示。后台运行了3个Pod,这些Pod是相互独立且可被替换的,当Pod出现状况被重建时,新建的Pod的IP地址是新IP,前台的Pod无法直接感知。
使用Service解决Pod的访问问题
Kubernetes中的Service对象就是用来解决上述Pod访问问题的。Service有一个固定IP地址(在创建CCE集群时有一个服务网段的设置,这个网段专门用于给Service分配IP地址),Service将访问它的流量转发给Pod,具体转发给哪些Pod通过Label来选择,而且Service可以给这些Pod做负载均衡。
那么对于上面的例子,为后台添加一个Service,通过Service来访问Pod,这样前台Pod就无需感知后台Pod的变化,如图2所示。
Service的类型
Kubernetes允许指定一个需要的类型的Service,类型的取值以及行为如下:
- 集群内访问(ClusterIP)
集群内访问表示工作负载暴露给同一集群内其他工作负载访问的方式,可以通过“集群内部域名”访问。
- 节点访问(NodePort)
节点访问 ( NodePort )是指在每个节点的IP上开放一个静态端口,通过静态端口对外暴露服务。节点访问 ( NodePort )会路由到ClusterIP服务,这个ClusterIP服务会自动创建。通过请求<NodeIP>:<NodePort>,可以从集群的外部访问一个NodePort服务。
- 负载均衡(LoadBalancer)
负载均衡( LoadBalancer )可以通过弹性负载均衡从公网访问到工作负载,与弹性IP方式相比提供了高可靠的保障。集群外访问推荐使用负载均衡类型。
- DNAT网关(DNAT)
可以为集群节点提供网络地址转换服务,使多个节点可以共享使用弹性IP。与弹性IP方式相比增强了可靠性,弹性IP无需与单个节点绑定,任何节点状态的异常不影响其访问。
服务亲和(externalTrafficPolicy)
NodePort类型及LoadBalancer类型的Service接收请求时,会先访问到节点,然后转到Service,再由Service选择一个Pod转发到该Pod,但Service选择的Pod不一定在接收请求的节点上。默认情况下,从任意节点IP+服务端口都能访问到后端工作负载,当Pod不在接收请求的节点上时,请求会再跳转到Pod所在的节点,带来一定性能损失。
Service有一个配置参数(externalTrafficPolicy),用于设置Service是否希望将外部流量路由到节点本地或集群范围的端点,示例如下:
apiVersion: v1 kind: Service metadata: name: nginx-nodeport spec: externalTrafficPolicy: Local ports: - name: service nodePort: 30000 port: 80 protocol: TCP targetPort: 80 selector: app: nginx type: NodePort
当externalTrafficPolicy取值为Local时,通过节点IP:服务端口的请求只会转发给本节点上的Pod,如果节点没有Pod的话请求会挂起。
当externalTrafficPolicy取值为Cluster时,请求会在集群内转发,从任意节点IP+服务端口都能访问到后端工作负载。
如不设置externalTrafficPolicy,默认取值为Cluster。
在CCE 控制台创建NodePort类型Service时,也可以通过“服务亲和”选项配置该参数。
总结服务亲和(externalTrafficPolicy)的两个选项对比如下:
对比维度 |
服务亲和(externalTrafficPolicy) |
|
---|---|---|
集群级别(Cluster) |
节点级别(Local) |
|
使用场景 |
适用于对性能要求不高,无需保留客户端源IP场景,此方式能为集群各节点带来更均衡的负载。 |
适用于客户端源IP需要保留且对性能要求较高的业务,但是流量仅会转发至容器所在的节点,不会做源地址转换。 |
访问方式 |
集群下所有节点的IP+访问端口均可以访问到此服务关联的负载。 |
只有通过负载所在节点的IP+访问端口才可以访问此服务关联的负载。 |
获取客户端源IP |
无法获取到客户端源IP。 |
可以获取到客户端源IP。 |
访问性能 |
服务访问会因路由跳转导致一定性能损失,可能导致第二跳到另一个节点。 |
服务访问没有因路由跳转导致的性能损失。 |
负载均衡性 |
流量传播具有良好的整体负载均衡性。 |
存在潜在的不均衡流量传播风险。 |
其他特殊情况 |
- |
在不同容器网络模型和服务转发模式下,可能出现集群内无法访问Service的情况,详情请参见集群内无法访问Service的说明。 |
集群内无法访问Service的说明
upstream connect error or disconnect/reset before headers. reset reason: connection failure 或: curl: (7) Failed to connect to 192.168.10.36 port 900: Connection refused
在集群中访问ELB地址时出现无法访问的场景较为常见,这是由于Kubernetes在创建Service时,kube-proxy会把ELB的访问地址作为外部IP(即External-IP,如下方回显所示)添加到iptables或IPVS中。如果客户端从集群内部发起访问ELB地址的请求,该地址会被认为是服务的外部IP,被kube-proxy直接转发,而不再经过集群外部的ELB。
# kubectl get svc nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx LoadBalancer 10.247.76.156 123.**.**.**,192.168.0.133 80:32146/TCP 37s
- 多实例的工作负载需要保证所有实例均可正常访问,否则可能出现概率性访问不通的情况。
- 表格中仅列举了可能存在访问不通的场景,其他不在表格中的场景即表示可以正常访问。
服务端发布服务类型 |
访问类型 |
客户端请求发起位置 |
容器隧道集群(IPVS) |
VPC集群(IPVS) |
容器隧道集群(IPTABLES) |
VPC集群(IPTABLES) |
---|---|---|---|---|---|---|
节点访问类型Service |
公网/私网 |
与服务Pod同节点 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
与服务Pod不同节点 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
正常访问 |
正常访问 |
||
与服务Pod同节点的其他容器 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
无法访问 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
无法访问 |
||
与服务Pod不同节点的其他容器 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
访问服务端所在节点IP+NodePort — 正常访问 访问非服务端所在节点IP+NodePort — 无法访问 |
||
独享型负载均衡类型Service |
私网 |
与服务Pod同节点 |
无法访问 |
无法访问 |
无法访问 |
无法访问 |
与服务Pod同节点的其他容器 |
无法访问 |
无法访问 |
无法访问 |
无法访问 |
||
DNAT网关类型Service |
公网 |
与服务Pod同节点 |
无法访问 |
无法访问 |
无法访问 |
无法访问 |
与服务Pod不同节点 |
无法访问 |
无法访问 |
无法访问 |
无法访问 |
||
与服务Pod同节点的其他容器 |
无法访问 |
无法访问 |
无法访问 |
无法访问 |
||
与服务Pod不同节点的其他容器 |
无法访问 |
无法访问 |
无法访问 |
无法访问 |
||
nginx-ingress插件对接独享型ELB(Local) |
私网 |
与cceaddon-nginx-ingress-controller Pod同节点 |
无法访问 |
无法访问 |
无法访问 |
无法访问 |
与cceaddon-nginx-ingress-controller Pod同节点的其他容器 |
无法访问 |
无法访问 |
无法访问 |
无法访问 |
解决这个问题通常有如下办法:
- (推荐)在集群内部访问使用Service的ClusterIP或服务域名访问。
- 将Service的externalTrafficPolicy设置为Cluster,即集群级别服务亲和。不过需要注意这会影响源地址保持。
apiVersion: v1 kind: Service metadata: annotations: kubernetes.io/elb.class: union kubernetes.io/elb.autocreate: '{"type":"public","bandwidth_name":"cce-bandwidth","bandwidth_chargemode":"bandwidth","bandwidth_size":5,"bandwidth_sharetype":"PER","eip_type":"5_bgp","name":"james"}' labels: app: nginx name: nginx spec: externalTrafficPolicy: Cluster ports: - name: service0 port: 80 protocol: TCP targetPort: 80 selector: app: nginx type: LoadBalancer
- 使用Service的pass-through特性,使用ELB地址访问时绕过kube-proxy,先访问ELB,经过ELB再访问到负载。具体请参见LoadBalancer类型Service使用pass-through能力。
- 在CCE Standard集群中,当使用独享型负载均衡配置pass-through后,从工作负载Pod所在节点或同节点的其他容器中访问ELB的私网IP地址,会出现无法访问的问题。
- 1.15及以下老版本集群暂不支持该能力。
- IPVS网络模式下,对接同一个ELB的Service需保持pass-through设置情况一致。
- 使用节点级别(Local)的服务亲和的场景下,会自动设置kubernetes.io/elb.pass-through为onlyLocal,开启pass-through能力。
apiVersion: v1 kind: Service metadata: annotations: kubernetes.io/elb.pass-through: "true" kubernetes.io/elb.class: union kubernetes.io/elb.autocreate: '{"type":"public","bandwidth_name":"cce-bandwidth","bandwidth_chargemode":"bandwidth","bandwidth_size":5,"bandwidth_sharetype":"PER","eip_type":"5_bgp","name":"james"}' labels: app: nginx name: nginx spec: externalTrafficPolicy: Local ports: - name: service0 port: 80 protocol: TCP targetPort: 80 selector: app: nginx type: LoadBalancer