在 Istio 服务网格中使用 Traefik Ingress Controller

本文译自 Using Traefik Ingress Controller with Istio Service Mesh。

Istio 服务网格自带 ingress,但我们经常看到有要求使用非 Istio ingress 的客户。此前,我们已经介绍过将 NGINX 与 Istio 集成的情况。最近,我们一直在与使用 Traefik ingress 的客户合作。通过对我们之前建议的方法进行一些轻微调整,我将向你介绍如何实现 Traefik 作为 Istio 服务网格的入口网关。

流量的流向如下图所示。一旦请求从 Traefik Ingress 到达服务网格,Istio 就能够对请求应用安全性、可观察性和流量引导规则。

传入的流量绕过 Istio sidecar,直接到达 Traefik,所以请求终止在 Traefik ingress。

Traefik 使用 IngressRoute 配置重写 Host 头以匹配目的地,并将请求转发到目标服务,这是一个多步骤的过程。

  • 从 Traefik Ingress 出来的请求被重定向到 Istio sidecar(由 iptables)。
  • Sidecar 接收请求,对其进行加密(因为我们的 Istio PeerAuthentication 策略要求使用 STRICT mTLS),然后将请求转发到目标服务的 pod。

下面是一个端到端的部署示例,使用 Istio 的 bookinfo 演示应用,但用 Traefik ingress 来支撑整个部署。简而言之,要想在自己的环境中实现这个功能。

  • 用 Istio sidecar 部署 Traefik 控制器,注释部署,使入站流量绕过 Istio Sidecar:
# Exclude the ports that Traefik receives traffic on
traffic.sidecar.istio.io/excludeInboundPorts: “80” 

# Make sure Traefik controller can talk to the Kubernetes API server 

traffic.sidecar.istio.io/excludeOutboundIPRanges: X.X.X.X/32 
  • 在应用程序命名空间中启用 Istio sidecar 注入,并部署任何你需要的 Istio 特定配置。
  • 用 Traefik Middleware 对象创建 IngressRoute,将主机名改写为网格识别的主机名(即集群中的服务;下文将通过一个例子详细讨论)。

使用 Traefik Ingress 的 Bookinfo

这篇文章的其余部分涵盖了部署 Istio 的 Bookinfo 示例应用程序,使用 Traefik 作为部署的 Ingress 代理。

设置环境

参考以下步骤。

  • 部署一个至少 1.17 版本的 Kuberentes 集群(最小支持 Istio 1.8 版本)。我们使用 Google Kubernetes Engine 创建的集群。
  • gcloud container clusters create istio-traefik 
      --cluster-version=1.17 
      --region <GCP region> 
      --machine-type=e2-standard-4 
      --project <GCP Project> 
      --num-nodes 1 
      --node-locations <GCP Zone> # i.e us-west2-b (otherwise 1 node per zone)
    
  • 下载 Istio 1.8
  • curl -sL https://git.io/getLatestIstio |
    ISTIO_VERSION=1.8.1 sh -
    
  • 在启用 HTTP 访问日志的情况下进行安装。
  • ./istio-1.8.1/bin/istioctl install 
      --set meshConfig.accessLogFile=/dev/stdout 
      --skip-confirmation
    

    部署 Bookinfo 应用

    安装好 Istio 后,我们就可以开始部署我们的应用程序了。我们将使用 Istio 的 Bookinfo 应用程序进行演示。这个示例应用程序是 Istio 发行版的一部分(在./istio-1.8.1/samples/ 文件夹中)。

  • 创建 bookinfo namespace。
  • kubectl create ns bookinfo
    
  • 设置 label,让 sidecar 自动注入。
  • kubectl label namespace bookinfo istio-injection=enabled
    
  • 在该 namespace 中部署 bookinfo 应用程序。
  • kubectl apply -f istio-1.8.1/samples/bookinfo/platform/kube/bookinfo.yaml -n bookinfo
    

    确认所有的吊舱都已启动,并部署了侧车。

    启用 Istio mTLS 为应用程序命名空间的服务到服务通信。

    cat <<EOF | kubectl apply -f - 
    apiVersion: security.istio.io/v1beta1
    kind: PeerAuthentication
    metadata:
      name: default
      namespace: bookinfo
    spec:
      mtls:
        mode: STRICT
    EOF
    

    部署 Traefik 入口

    现在是时候按照 v2.3 文档来部署 Traefik 了(本篇文章中 Traefik 的最新版本是 2.3,但如果你根据你的版本调整了 IngressRouteMiddleware 资源,它将适用于任何版本的 Traefik)。

  • 部署 Traefik。请注意,Traefik 网站上的文档部署有一些修改(代替 Traefik 文档中的默认命名空间,将指定 bookinfo 命名空间)。该文件可以在这里访问并应用如下。
  • $ kubectl apply -f http://bit.ly/Traefik-CRDs-and-Roles
    customresourcedefinition.apiextensions.k8s.io/ingressroutes.traefik.containo.us created
    customresourcedefinition.apiextensions.k8s.io/middlewares.traefik.containo.us created
    customresourcedefinition.apiextensions.k8s.io/ingressroutetcps.traefik.containo.us created
    customresourcedefinition.apiextensions.k8s.io/ingressrouteudps.traefik.containo.us created
    customresourcedefinition.apiextensions.k8s.io/tlsoptions.traefik.containo.us created
    customresourcedefinition.apiextensions.k8s.io/tlsstores.traefik.containo.us created
    customresourcedefinition.apiextensions.k8s.io/traefikservices.traefik.containo.us created
    clusterrole.rbac.authorization.k8s.io/traefik-ingress-lb created
    clusterrolebinding.rbac.authorization.k8s.io/traefik-ingress-lb created
    
  • 为传入的请求创建一个服务。该服务将接收外部 IP 地址。(Traefik 网站上的例子有一些变化)。
    • 需要指定 Namespace。
    • 只发布两个端口:80 用于 Bookinfo 应用,8080 用于 Traefik 管理。
    • 服务需要使用的标签(traefik-ingress-lb)指向 Traefik。
    • Type: Loadbalancer 是为了告诉 GCP 给服务分配一个外部 IP。
    cat <<EOF | kubectl apply -f - 
    apiVersion: v1
    kind: Service
    metadata:
      name: traefik
      namespace: bookinfo
    spec:
      ports:
        - protocol: TCP
          name: web
          port: 80
        - protocol: TCP
          name: admin
          port: 8080
      selector:
        app: traefik-ingress-lb
      type: LoadBalancer
    EOF
    
  • 确认服务的创建符合预期。
  • $ kubectl get svc traefik -n bookinfo
    NAME      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                       AGE
    traefik   LoadBalancer   10.35.244.227   35.236.XXX.XXX   80:31718/TCP,8080:31334/TCP   2m6s
    
  • 正如 Traefik 网站中介绍的,需要应用 ServiceAccount 的 Kubernetes 部署。除了名称和命名空间,网站示例还引入了以下变化。
    • 为了简单起见,删除了安全端点。
      • Accesslog:增加了 =true,因为没有这个值就不行。
      • Log.level 设置为 DEBUG 将帮助我们看到发生了什么。
      • 增加了 traffic.sidecar.istio.io 注释(更多细节请参考之前提到的 Tetrate NGINX 文章)。
    KUBERNETES_SVC_IP=$( kubectl get svc kubernetes -n default -o jsonpath='{.spec.clusterIP}' )
     
    cat <<EOF | kubectl apply -f - 
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      namespace: bookinfo
      name: traefik-ingress-lb
     
    ---
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      namespace: bookinfo
      name: traefik-ingress-lb
      labels:
        app: traefik-ingress-lb
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: traefik-ingress-lb
      template:
        metadata:
          labels:
            app: traefik-ingress-lb
          annotations:
            traffic.sidecar.istio.io/excludeInboundPorts: "80"
            traffic.sidecar.istio.io/excludeOutboundIPRanges: ${KUBERNETES_SVC_IP}/32
        spec:
          serviceAccountName: traefik-ingress-lb
          containers:
            - name: traefik-ingress-lb
              image: traefik:v2.3
              args:
                - --api.insecure
                - --accesslog=true
                - --providers.kubernetescrd
                - --entrypoints.web.address=:80
                - --log.level=DEBUG
              ports:
                - name: web
                  containerPort: 80
                - name: admin
                  containerPort: 8080
    EOF
    
  • 确认 Traefik 在 Bookinfo 命名空间的部署。
  • $  kubectl get pods -n bookinfo -l app=traefik-ingress-lb
    NAME                                  READY   STATUS    RESTARTS   AGE
    traefik-ingress-lb-669fc4b77d-74mpx   2/2     Running   0          2m35s
    
  • 获取服务 IP 并记录 BOOKINFO_IP 变量值。
  • BOOKINFO_IP=$(kubectl -n bookinfo get service traefik -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    
  • 测试来自 Ingress 80 端口的响应,看看它是否有通往应用的路由。
  • curl -I $BOOKINFO_IP
    

    确保它返回 “404 Not Found”—— 由于尚未实施 Ingress 规则,所以预计会有 not-200 响应。

    配置 Traefik Ingress 规则

  • Traefik 的 Middleware 头重写功能将使 Istio 服务网格正常运行。本例中,需要将主机定义为 productpage.bookinfo.svc。头可以根据 Traefik 文档来定义。
  • cat <<EOF | kubectl apply -f - 
    apiVersion: traefik.containo.us/v1alpha1
    kind: Middleware
    metadata:
      name: productpage-header
      namespace: bookinfo
    spec:
      headers:
        customRequestHeaders:
          Host: productpage.bookinfo.svc
    EOF
    
  • 最后一步是指定 ingress 请求的路由逻辑,因为本文的重点是服务网格集成。这个定义非常简单,将所有到达 80 端口的入站请求转发到名为 ProductPage 的前沿 bookinfo 应用服务(服务于 9080 端口的流量)。它还使用了上一步创建的中间件对象。
  • cat <<EOF | kubectl apply -f - 
    apiVersion: traefik.containo.us/v1alpha1
    kind: IngressRoute
    metadata:
      name: productpage
      namespace: bookinfo
    spec:
      entryPoints:
        - web
      routes:
      - match: PathPrefix(`/`)
        kind: Rule
        middlewares:
        - name: productpage-header
        services:
        - name: productpage
          port: 9080
    EOF
    

    验证部署功能

  • 重新测试应用程序的响应。
  • curl -I $BOOKINFO_IP
    

    我们会收到 “200 OK “的回复。也可以通过浏览器测试,使用 http://<BOOKINFO_IP>/productpage

  • 如果在 http://<BOOKINFO_IP>/productpage,就会返回应用响应。
  • 通过查询 istio-proxy 容器的 bookinfo 命名空间中的 Traefik pod 日志,可以在 istio-proxy 日志中看到向应用程序发出的请求。没有传入请求,因为它们直接到达 Traefik Ingress。
  • TRAEFIK_POD=$( kubectl -n bookinfo get pods -l app=traefik-ingress-lb -o jsonpath='{.items[0].metadata.name}' )
    kubectl -n bookinfo logs ${TRAEFIK_POD} -c istio-proxy
    

    请注意,请求处理后,日志需要几秒钟才能显示。只有在 Istio 安装时使用 meshConfig.accessLogFile=/dev/stdout 标志的情况下,才会显示日志。

    [2021-01-05T20:13:55.015Z] "GET /productpage HTTP/1.1" 200 - "-" 0 5179 1069 1069 "10.32.0.1" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" "4bd443e9-1a2e-4d30-b1e3-398a5005f240" "productpage.bookinfo.svc" "10.32.0.18:9080" outbound|9080||productpage.bookinfo.svc.cluster.local 10.32.0.19:51810 10.32.0.18:9080 10.32.0.1:0 - default
    [2021-01-05T20:13:56.301Z] "GET /static/bootstrap/fonts/glyphicons-halflings-regular.woff2 HTTP/1.1" 200 - "-" 0 18028 3 3 "10.32.0.1" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" "8cb44552-c3c8-45dd-8674-4af207ce1648" "productpage.bookinfo.svc" "10.32.0.18:9080" outbound|9080||productpage.bookinfo.svc.cluster.local 10.32.0.19:51810 10.32.0.18:9080 10.32.0.1:0 - default
    

    总结

    本文演示了如何将 Traefik Ingress 作为 Istio 服务网格的入口点。这里应用的基本方法应该是适用的,即使你的环境与我们例子中使用的环境不同。当引入服务网格时,Traefik / 服务网格集成可以在不同的云中成功实施,并使用全新或现有(也就是棕地)部署 Traefik。最终,您将获得两者最好的东西:Istio 服务网格与您所选择的 Ingress 控制器相集成!