contour 是基于envoy实现的kubernetes ingress controller。除了支持官方的Ingress对象,Ingress对象一直没有处于beta阶段,发展比较慢,无法满足实际生产使用,社区通过注解的方式扩展,以表达HTTP路由缺少的属性。
而且还支持了HTTPProxy
。HTTPProxy
是通过自定义资源定义(CRD)的方式扩展Ingress API的功能,以提供更丰富的用户体验,并解决后者在多租户环境中使用的限制。
有如下的优势:
- 安全地支持多团队Kubernetes集群,并能够限制哪些命名空间可以配置虚拟主机和TLS凭据。
- 允许包括来自另一个HTTPProxy(可能在另一个命名空间中)的路径或域的路由配置。
- 在一条路由中接受多种服务,并在它们之间负载均衡流量。
- 本机允许定义服务加权和负载平衡策略而无需注解。
- 在创建时验证HTTPProxy对象,并为创建后的有效性进行状态报告。
此外contour 已经可以代替istio,成为knative 网络管理组件。非常值得投入。具体参见 Knative Install using Contour on a Kubernetes Cluster。
下面主要我们介绍一下HTTPProxy API规范
HTTPProxy API规范
虚拟 Host 配置
完全合格的域名
与Ingress相似,HTTPProxy支持基于名称的虚拟Host。基于名称的虚拟Host使用具有相同IP地址的多个Host名。
foo.bar.com --| |-> foo.bar.com s1:80
| 178.91.123.132 |
bar.foo.com --| |-> bar.foo.com s2:80
与Ingress不同,每个HTTPProxy对象仅支持一个根域。例如,此Ingress对象:
# ingress-name.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: name-example
spec:
rules:
- host: foo1.bar.com
http:
paths:
- backend:
serviceName: s1
servicePort: 80
- host: bar1.bar.com
http:
paths:
- backend:
serviceName: s2
servicePort: 80
必须由两个不同的HTTPProxy
对象表示:
# httpproxy-name.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: name-example-foo
namespace: default
spec:
virtualhost:
fqdn: foo1.bar.com
routes:
- services:
- name: s1
port: 80
---
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: name-example-bar
namespace: default
spec:
virtualhost:
fqdn: bar1.bar.com
routes:
- services:
- name: s2
port: 80
TLS
HTTPProxy遵循与Ingress类似的模式来配置TLS secret。
您可以通过指定包含TLS私钥和证书信息的Secret来保护HTTPProxy。Contour(通过Envoy)使用SNI TLS扩展来处理此行为。如果多个HTTPProxy使用同一secret,则证书必须为每个fqdn包括必要的Subject Authority Name(SAN)。
Contour也遵循“安全第一”的方法。为虚拟Host启用TLS后,对不安全端口的任何请求都将使用301重定向重定向到安全接口。通过启用Route上的spec.routes.permitInsecure
参数,可以将特定的路由配置为覆盖此行为并处理不安全的请求。
TLS密钥必须包含名为tls.crt和tls.key的密钥,其中包含要用于TLS的证书和私钥,例如:
# ingress-tls.secret.yaml
apiVersion: v1
data:
tls.crt: base64 encoded cert
tls.key: base64 encoded key
kind: Secret
metadata:
name: testsecret
namespace: default
type: kubernetes.io/tls
可以使用tls.secretName
属性将HTTPProxy配置为使用此secret:
# httpproxy-tls.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: tls-example
namespace: default
spec:
virtualhost:
fqdn: foo2.bar.com
tls:
secretName: testsecret
routes:
- services:
- name: s1
port: 80
如果tls.secretName
属性包含斜杠,例如。然后,根据TLS证书委派,将从somenamespace中的somesecret读取TLS证书。有关更多信息,请参见下面的TLS证书委派。
可以通过设置spec.virtualhost.tls.minimumProtocolVersion
来指定vhost应使用的TLS最低协议版本:
- 1.3
- 1.2
- 1.1 (Default)
Upstream TLS
HTTPProxy可以通过首先使用以下内容注释上游Kubernetes服务来代理上游TLS连接:projectcontour.io/upstream-protocol.tls:"443,https“。此注释告诉Contour TLS连接应使用哪个端口。在此示例中,上游服务名为https,并且使用端口443。此外,Envoy可以验证后端服务的证书。 HTTPProxy的服务可以选择指定一个验证结构,该结构具有强制的caSecret密钥和强制的subjectName。
注意:如果存在spec.routes.services [].validation
,则spec.routes.services[].{name,port}
必须指向具有匹配projectcontour.io/upstream-protocol.tls服务注释的服务。
示例 YAML
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: secure-backend
spec:
virtualhost:
fqdn: www.example.com
routes:
- services:
- name: service
port: 8443
validation:
caSecret: my-certificate-authority
subjectName: backend.example.com
Error conditions
如果验证规范是在服务上定义的,但是它所引用的secret不存在,则Contour将拒绝更新并相应地设置HTTPProxy对象的状态。这有助于防止代理到请求验证但尚不可用的上游的情况。
Status:
Current Status: invalid
Description: route "/": service "tls-nginx": upstreamValidation requested but secret not found or misconfigured
TLS Certificate 委托
为了支持通配符证书,*.somedomain.com的TLS证书(存储在群集管理员控制的名称空间中),Contour支持一种称为TLS证书委派的功能。此功能允许TLS证书的所有者委派(为了引用TLS证书)许可Contour从另一个名称空间读取Secret对象。
TLSCertificateDelegation
资源在规范中定义了一组委托。每个委托都从创建TLSCertificateDelegation
的名称空间引用一个secretName,并描述一组可以引用证书的targetNamespaces。如果所有名称空间都能够引用该secret,则将“*”设置为targetNamespaces的值(请参见下面的示例)。
apiVersion: projectcontour.io/v1
kind: TLSCertificateDelegation
metadata:
name: example-com-wildcard
namespace: www-admin
spec:
delegations:
- secretName: example-com-wildcard
targetNamespaces:
- example-com
- secretName: another-com-wildcard
targetNamespaces:
- "\*"
---
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: www
namespace: example-com
spec:
virtualhost:
fqdn: foo2.bar.com
tls:
secretName: www-admin/example-com-wildcard
routes:
- services:
- name: s1
在此示例中,Contour在admin名称空间中引用Secret example-com-wildcard的权限已委派给example-com名称空间中的HTTPProxy对象。另外,Contour引用所有命名空间中的Secret another-com-wildcard的权限已委派给群集中的所有HTTPProxy对象。
Conditions
HTTPProxy中的每个Route条目可能包含一个或多个条件。这些条件在传递给Envoy的路线上与AND运算符结合在一起。
条件可以是prefix
或header
条件。
Prefix conditions
对于prefix
,这将添加 path prefix。
任何条件块中最多可以存在一个prefix
条件。
prefix
条件必须以/开头(如果存在)。
Header conditions
对于header
条件,有一个必填字段,name
和五个运算符字段:present
,contains
,notcontains
,exact
, 和notexact
。
present
是一个布尔值,并检查header是否存在。
contains
是一个字符串,并检查header是否包含该字符串。 notcontains
同样检查header不包含字符串。
exact
是一个字符串,并检查标题是否与整个字符串完全匹配。 notexact
检查标头与整个字符串不完全匹配。
Routes
HTTPProxy必须至少具有一条路由或包括在内。使用prefix
条件匹配定义的路径。在此示例中,对multi-path.bar.com/blog或multi-path.bar.com/blog/*的任何请求都将被路由到Service s2。对host 为multi-path.bar.com的所有其他请求将被路由到服务s1。
# httpproxy-multiple-paths.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: multiple-paths
namespace: default
spec:
virtualhost:
fqdn: multi-path.bar.com
routes:
- conditions:
- prefix: / # matches everything else
services:
- name: s1
port: 80
- conditions:
- prefix: /blog # matches \`multi-path.bar.com/blog\` or \`multi-path.bar.com/blog/\*\`
services:
- name: s2
port: 80
在以下示例中,我们匹配header并发送到不同的服务,如果不匹配,则使用默认路由。
# httpproxy-multiple-headers.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: multiple-paths
namespace: default
spec:
virtualhost:
fqdn: multi-path.bar.com
routes:
- conditions:
- header:
name: x-os
contains: ios
services:
- name: s1
port: 80
- conditions:
- header:
name: x-os
contains: android
services:
- name: s2
port: 80
- services:
- name: s3
port: 80
Multiple Upstreams
HTTPProxy的主要功能之一是能够针对一个固定的path支持多个服务:
# httpproxy-multiple-upstreams.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: multiple-upstreams
namespace: default
spec:
virtualhost:
fqdn: multi.bar.com
routes:
- services:
- name: s1
port: 80
- name: s2
port: 80
在此示例中,对multi.bar.com/的请求将在两个Kubernetes服务s1和s2之间进行负载平衡。当您需要在两个不同版本的应用程序之间分配给定URL的流量时,这很有用。
Upstream Weighting
在多个上游构建的功能是可以定义上游服务的相对权重。当您要将少量流量发送到特定服务时,通常用于对应用程序新版本进行金丝雀测试。
# httpproxy-weight-shfiting.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: weight-shifting
namespace: default
spec:
virtualhost:
fqdn: weights.bar.com
routes:
- services:
- name: s1
port: 80
weight: 10
- name: s2
port: 80
weight: 90
在此示例中,我们向服务s1发送10%的流量,而服务s2接收其余90%的流量。
HTTPProxy加权遵循一些特定规则:
- 如果没有为给定路由指定权重,则假定在服务之间平均分配。
- 权重是相对的,不需要加起来为100。如果指定了一条路线的所有权重,则“总”权重就是所指定的权重之和。例如,如果三个上游的权重分别为20、30、20,则总权重为70。在此示例中,权重为30的流量约占42.9%(30/70 = 0.4285)。
- 如果指定了一些权重,但未指定其他权重,则假定没有权重的上游的隐式权重为零,因此将不会接收流量。
Request and Response Header Policies
每个服务或每个路由还支持操纵header。可以如下设置header或从请求或响应中删除header:
per-Service:
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: header-manipulation
namespace: default
spec:
virtualhost:
fqdn: headers.bar.com
routes:
- services:
- name: s1
port: 80
requestHeadersPolicy:
set:
- name: X-Foo
value: bar
remove:
- X-Baz
responseHeaderPolicy:
set:
- name: X-Service-Name
value: s1
remove:
- X-Internal-Secret
per-Route:
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: header-manipulation
namespace: default
spec:
virtualhost:
fqdn: headers.bar.com
routes:
- services:
- name: s1
port: 80
requestHeadersPolicy:
set:
- name: X-Foo
value: bar
remove:
- X-Baz
responseHeadersPolicy:
set:
- name: X-Service-Name
value: s1
remove:
- X-Internal-Secret
在这些示例中,我们为请求设置头X-Foo的值baz并删除X-Baz。然后,我们在响应上将X-Service-Name设置为值s1,并删除X-Internal-Secret。
Traffic mirroring
每个路由都可以将服务指定为镜像。镜像服务将接收发送到任何非镜像服务的读取流量的副本。镜像流量被视为只读,镜像的任何响应都将被丢弃。
该服务对于记录流量以供以后重播或对新部署进行烟雾测试很有用。
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: traffic-mirror
namespace: default
spec:
virtualhost:
fqdn: www.example.com
routes:
- conditions:
- prefix: /
services:
- name: www
port: 80
- name: www-mirror
port: 80
mirror: true
Response Timeout
可以将每个路由配置为具有超时策略和重试策略,如下所示:
# httpproxy-response-timeout.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: response-timeout
namespace: default
spec:
virtualhost:
fqdn: timeout.bar.com
routes:
- timeoutPolicy:
response: 1s
idle: 10s
retryPolicy:
count: 3
perTryTimeout: 150ms
services:
- name: s1
port: 80
在此示例中,对timeout.bar.com/的请求的响应超时策略为1s。这是指从代理处理完完整的客户端请求到服务器的响应被完全处理之间的时间。
-
timeoutPolicy.response
该字段可以是任何正时间段或“无穷大”。 0s的时间段也将被视为无穷大。此超时涵盖从客户端请求结束到上游响应结束的时间。默认情况下,Envoy的超时时间为15秒。可以在Envoy的文档中找到更多信息。 -
timeoutPolicy.idle
该字段可以是任何正时间段或“无穷大”。 0s的时间段也将被视为无穷大。默认情况下,没有按路由的空闲超时。请注意,如果未设置,默认的连接管理器空闲超时为5分钟。
TimeoutPolicy持续时间按照ParseDuration文档中指定的格式表示。输入值示例:“ 300ms”,“ 5s”,“ 1m”。有效时间单位为“ ns”,“ us”(或“ µs”),“ ms”,“ s”,“ m”,“ h”。字符串'infinity'也是有效输入,没有指定超时。
可以在Envoy的文档中找到更多信息。
-
retryPolicy:如果服务器返回5xx范围内的错误代码,或者服务器花费多于retryPolicy.perTryTimeout来处理请求,则将尝试重试。
- retryPolicy.count指定允许的最大重试次数。该参数是可选的,默认为1。
- retryPolicy.perTryTimeout指定每次重试的超时时间。如果此字段大于请求超时,则将其忽略。此参数是可选的。如果未指定,将使用timeoutPolicy.request。
Load Balancing Strategy
每个路由都可以应用负载平衡策略来确定为请求选择了哪个端点。以下列表是可供选择的选项:
- RoundRobin:按循环顺序选择每个正常的上游端点(如果未选择,则为默认策略)。
- WeightedLeastRequest:最少请求策略使用O(1)算法,该算法选择两个随机的健康端点,并选择活动请求较少的端点。注意:此算法非常简单,足以进行负载测试。如果需要真正的加权最小请求行为,则不应使用它。
- 随机:随机策略选择一个随机的健康端点。
# httpproxy-lb-strategy.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: lb-strategy
namespace: default
spec:
virtualhost:
fqdn: strategy.bar.com
routes:
- conditions:
- prefix: /
services:
- name: s1-strategy
port: 80
- name: s2-strategy
port: 80
loadBalancerPolicy:
strategy: WeightedLeastRequest
Session Affinity
会话亲缘关系(也称为粘性会话)是一种负载平衡策略,通过该策略,来自单个客户端的一系列请求将始终路由到同一应用程序后端。 Contour通过loadBalancerPolicy策略:Cookie支持基于路由的会话亲缘关系。
# httpproxy-sticky-sessions.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: httpbin
namespace: default
spec:
virtualhost:
fqdn: httpbin.davecheney.com
routes:
- services:
- name: httpbin
port: 8080
loadBalancerPolicy:
strategy: Cookie
WebSocket Support
可以使用enableWebsockets字段在特定路由上启用WebSocket支持:
# httpproxy-websockets.yaml
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: chat
namespace: default
spec:
virtualhost:
fqdn: chat.example.com
routes:
- services:
- name: chat-app
port: 80
- conditions:
- prefix: /websocket
enableWebsockets: true # Setting this to true enables websocket for all paths that match /websocket
services:
- name: chat-app
port: 80
Path Rewriting
HTTPProxy支持在将请求传递到后端服务之前重写HTTP请求URL路径。重写是在做出路由决定之后执行的,并且永远不会更改请求目的地。
pathRewritePolicy
字段指定应如何重写路径前缀。 pathRewritePolicy
为HTTP请求路径前缀匹配指定替换字符串。存在此字段时,将用替换字段中指定的文本替换请求匹配的路径前缀。如果HTTP请求路径长于匹配的前缀,则路径的其余部分保持不变。
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: rewrite-example
namespace: default
spec:
virtualhost:
fqdn: rewrite.bar.com
routes:
- services:
- name: s1
port: 80
pathRewritePolicy:
replacePrefix:
- replacement: /new/prefix
replacePrefix
字段接受一系列可能的替换。如果存在多个replacePrefix
数组元素,则前缀字段可用于消除要应用的替换的歧义。 如果不存在前缀字段,则替换将应用于对路由进行的所有前缀匹配。如果存在前缀字段,则替换仅应用于具有完全匹配的前缀条件的路由。当一个HTTPProxy文档包含在多个父文档中时,指定一个以上的replacePrefix条目主要有用。
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: rewrite-example
namespace: default
spec:
virtualhost:
fqdn: rewrite.bar.com
routes:
- services:
- name: s1
port: 80
conditions:
- prefix: /v1/api
pathRewritePolicy:
replacePrefix:
- prefix: /v1/api
replacement: /app/api/v1
- prefix: /
replacement: /app
上面介绍了contour支持的ingress若干重要特性。此外还支持路由监控检测,Header策略等特性。如果需要更详细的介绍,可以阅读官方文档。
部署
新版本的contour部署相对旧的版本,更加健壮。官方建议通过deployment部署contour(2个副本),然后通过daemonset的方式部署envoy。contour组件是contour ingress controller的控制层,而envoy为数据层。
如果是在公有云上,可以设置公有云的负载均衡器将流量打到边缘envoy上。如下图:
如果是私有云,则需要通过lvs集群。
结论
随着企业容器化的深入,官方的ingress往往不能满足需求。将contour 引入现在的技术栈中,可以很好的解决痛点。而且contour 基于envoy实现,envoy社区非常活跃,相应contour也会很快将新版本的envoy的特性集成到ingress中。