1

maxresdefault.jpg

contour 是基于envoy实现的kubernetes ingress controller。除了支持官方的Ingress对象,Ingress对象一直没有处于beta阶段,发展比较慢,无法满足实际生产使用,社区通过注解的方式扩展,以表达HTTP路由缺少的属性。

而且还支持了HTTPProxyHTTPProxy 是通过自定义资源定义(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运算符结合在一起。

条件可以是prefixheader条件。

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为数据层。

post-contour-split-deployment.png

如果是在公有云上,可以设置公有云的负载均衡器将流量打到边缘envoy上。如下图:

image.png

如果是私有云,则需要通过lvs集群。

结论

随着企业容器化的深入,官方的ingress往往不能满足需求。将contour 引入现在的技术栈中,可以很好的解决痛点。而且contour 基于envoy实现,envoy社区非常活跃,相应contour也会很快将新版本的envoy的特性集成到ingress中。


iyacontrol
1.4k 声望2.7k 粉丝

专注kubernetes,devops,aiops,service mesh。