Kubernetes中为了实现服务实例间的负载均衡和不同服务间的服务发现,创造了Service对象,同时又为从集群外部访问集群创建了Ingress对象。

3.7.1 Service

对于Kubernetes集群中的应用,Kubernetes提供了简单的Endpoints API,只要service中的一组Pod发生变更,应用程序就会被更新。
对于非Kubernetes集群中的应用,Kubernetes提供了基于VIP的网桥的方式访问Service,再由Service重定向到backend Pod。

VIP和Service代理

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

在Kubernetes v1.0版本,代理完全在userspace,Service是4层概念。
在Kubernetes v1.1版本,新增了Iptables代理,但并不是默认的运行模式。新增了Ingress API,用来表示7层服务。
从Kubernetes v1.2起,默认是iptables代理。
在Kubernetes v1.8.0-beta.0中,添加了ipvs代理。

userspace代理模式
这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。
任何连接到“代理端口”的请求,都会被代理到 Service 的backend Pods 中的某个上面(如 Endpoints 所报告的一样)。 使用哪个 backend Pod,是基于 ServiceSessionAffinity 来确定的。
最后,它安装 iptables 规则,捕获到达该 ServiceclusterIP(是虚拟 IP)和 Port 的请求,并重定向到代理端口,代理端口再代理请求到 backend Pod
网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。
默认的策略是,通过 round-robin 算法来选择 backend Pod。 实现基于客户端 IP 的会话亲和性,可以通过设置 service.spec.sessionAffinity 的值为 "ClientIP" (默认值为 "None")。
services-userspace-overview.jpg
iptables代理模式
这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会安装 iptables 规则,从而捕获到达该 ServiceclusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。对于每个 Endpoints 对象,它也会安装 iptables 规则,这个规则会选择一个 backend Pod
默认的策略是,随机选择一个 backend。实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 "ClientIP" (默认值为 "None")。
和 userspace 代理类似,网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。
这应该比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始选择的 Pod 没有响应,iptables 代理不能自动地重试另一个 Pod,所以它需要依赖 readiness probes
services-iptables-overview.jpg

ipvs代理模式

这种模式,kube-proxy会监视Kubernetes Service对象和Endpoints,调用netlink接口以相应地创建ipvs规则并定期与Kubernetes Service对象和Endpoints对象同步ipvs规则,以确保ipvs状态与期望一致。访问服务时,流量将被重定向到其中一个后端Pod。
与iptables类似,ipvs基于netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着ipvs可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs为负载均衡算法提供了更多选项,例如:

  • rr:轮询调度
  • lc:最小连接数
  • dh:目标哈希
  • sh:源哈希
  • sed:最短期望延迟
  • nq: 不排队调度

注意: ipvs模式假定在运行kube-proxy之前在节点上都已经安装了IPVS内核模块。当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装,则kube-proxy将回退到iptables代理模式。

service-ipvs-overview.png

多端口Service
Kubernetes 支持在 Service 对象中定义多个端口。 当使用多个端口时,必须给出所有的端口的名称,这样 Endpoint 就不会产生歧义,例如:

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. spec:
  6. selector:
  7. app: MyApp
  8. ports:
  9. - name: http
  10. protocol: TCP
  11. port: 80
  12. targetPort: 9376
  13. - name: https
  14. protocol: TCP
  15. port: 443
  16. targetPort: 9377

选择自己的IP地址
在Service创建的请求中,可以通过设置spec.clusterIP字段来指定自己的集群IP地址。
IP地址需要在service-cluster-ip-range CIDR范围内。

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

环境变量
当Pod运行在Node上,kubelet会为每个活跃的service添加一组环境变量。它同时支持Docker links兼容变量、简单的{SVCNAME}_SERVICE_HOST和{SVCNAME}_SERVICE_PORT变量,这里的Service名称需要大写,横线被转换成下划线。

Pod想要访问的任何Service必须在Pod之前被创建,否则这些环境变量就不会被赋值。
DNS没有这个限制。

DNS
DNS服务器监视着创建新Service的Kubernetes API,从而为每一个Service创建一组DNS记录。
如果整个集群的DNS一直被启用,那么所有的Pod应该能够自动对Service进行名称解析。

Kubernetes也支持对端口名称的DNS SRV(Service)记录。
Kubernetes DNS 服务器是唯一的一种能够访问ExternalName类型的Service的方式。

Headless Service

有时不需要或者不想要负载均衡,以及单独的Service IP。
可以通过指定Cluster IP的值为None来创建Headless Service。

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

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

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

  • ExternalName类型Service的CNAME记录

    1. 记录:与Service共享一个名称的任何Endpoints,以及所有其他类型

发布服务-服务类型
有些应用,可能希望通过外部(Kubernetes集群外部)IP地址暴露Service。

Kubernetes ServiceTypes允许指定一个需要的类型的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)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

NodePort 类型

如果设置 type 的值为 "NodePort",Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个 Node 上的同一端口)代理到 Service。该端口将通过 Servicespec.ports[*].nodePort 字段被指定。

外部IP
如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 externalIPs。通过外部 IP(作为目的 IP 地址)进入到集群,打到 Service 的端口上的流量,将会被路由到 Service 的 Endpoint 上。externalIPs 不会被 Kubernetes 管理,它属于集群管理员的职责范畴。
根据 Service 的规定,externalIPs 可以同任意的 ServiceType 来一起指定。在下面的例子中,my-service 可以在 80.11.12.10:80(外部 IP:端口)上被客户端访问。

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

3.7.2 Ingress

Ingress解析
Ingress是从Kubernetes集群外部访问集群内部服务的入口。

什么是Ingress?
通常情况下,service和pod仅可在集群内部网络中通过IP地址访问。所有到达边界路由器的流量或被丢弃或被转发到其他地方。

  1. internet
  2. |
  3. ------------
  4. [ Services ]

Ingress是授权入站连接到达集群服务的规则集合。

  1. internet
  2. |
  3. [ Ingress ]
  4. --|-----|--
  5. [ Services ]

可以给Ingress配置提供外部可访问的URL、负载均衡、SSL、基于名称的虚拟主机等。
用户通过POST Ingress资源到API Server的方式来请求Ingress。
Ingress Controller负责实现Ingress,通常使用负载均衡器,它还可以配置边界路由和其他前端。

Ingress Resource
最简化的Ingress配置:

  1. apiVersion: apps/v1
  2. kind: Ingress
  3. metadata:
  4. name: test-ingress
  5. spec:
  6. rules:
  7. - http:
  8. paths:
  9. - path: /testpath
  10. backend:
  11. serviceName: test
  12. servicePort: 80

Ingress Controllers
为了使Ingress正常工作,集群中必须运行Ingress controller。

  • Kubernetes当前支持并维护GCE和nginx两种Controller。
  • F5(公司)支持并维护F5 BIG-IP Controller for Kubernetes。
  • Kong同时支持并维护社区版和企业版的Kong Ingress Controller for Kubernetes。
  • Traefik是功能齐全的ingress controller。
  • Istio使用CRD Gateway来控制Ingress流量。

Ingress类型

单Service Ingress
创建一个没有rule的默认backend的方式:

  1. apiVersion: apps/v1
  2. kind: Ingress
  3. metadata:
  4. name: test-ingress
  5. spec:
  6. backend:
  7. serviceName: testsvc
  8. servicePort: 80

假如想创建这样一个设置:

  1. foo.bar.com -> 178.91.123.132 -> / foo s1:80
  2. / bar s2:80

则需要这个一个Ingress:

  1. apiVersion: apps/v1
  2. kind: Ingress
  3. metadata:
  4. name: test
  5. spec:
  6. rules:
  7. - host: foo.bar.com
  8. http:
  9. paths:
  10. - path: /foo
  11. backend:
  12. serviceName: s1
  13. servicePort: 80
  14. - path: /bar
  15. backend:
  16. serviceName: s2
  17. servicePort: 80

只要服务(s1,s2)存在,Ingress Controller就会将提供一个满足该Ingress的特定loadbalancer实现。
这一步完成后,您将在Ingress的最后一列看到loadbalancer的地址。

基于名称的虚拟主机
Name-Based的虚拟主机在同一个IP地址下拥有多个主机名。

  1. foo.bar.com --| |-> foo.bar.com s1:80
  2. | 178.91.123.132 |
  3. bar.foo.com --| |-> bar.foo.com s2:80

如下这个Ingress说明基于Host header的后端loadbalancer的路由请求:

  1. apiVersion: apps/v1
  2. kind: Ingress
  3. metadata:
  4. name: test
  5. spec:
  6. rules:
  7. - host: foo.bar.com
  8. http:
  9. paths:
  10. - backend:
  11. serviceName: s1
  12. servicePort: 80
  13. - host: bar.foo.com
  14. http:
  15. paths:
  16. - backend:
  17. serviceName: s2
  18. servicePort: 80

一个没有rule的Ingress,所有流量都将发送到一个默认的backend。

TLS
可以通过指定包含TLS私钥和证书的secret来加密Ingress。
目前Ingress仅支持单个TLS端口443,并假定TLS termination。
如果Ingress中的TLS配置部分指定了不同的主机,则它们将根据通过SNI TLS扩展指定为tls.crt和tls.key的密钥。

  1. apiVersion: apps/v1
  2. data:
  3. tls.crt: base64 encoded cert
  4. tls.key: base64 encoded key
  5. kind: Secret
  6. metadata:
  7. name: testsecret
  8. namespace: default
  9. type: Opaque

在Ingress中引用这个secret将通知Ingress Controller使用TLS加密从将客户端到loadbalancer的channel:

apiVersion: apps/v1
kind: Ingress
metadata:
  name: no-rules-map
spec:
  tls:
    - secretName: testsecret
  backend:
    serviceName: s1
    servicePort: 80

更新Ingress

kubectl edit ing test