gloo基本知識

Architechture(架構)

Gloo通過Envoy XDS gRPC API來動態更新Envoy配置, 更方便的控制Envoy Proxy, 並保留擴展性..本質是一個Envoy xDS配置翻譯引擎, 爲Envoy提供高級配置(及定製的Envoy過濾器).它監控各種配置源的更新,並立即響應通過gRPC更新給Envoy.

Component Architechture

architechture

  • Config Watcher: 監控Upstreams和Virtual Services配置變化.

  • Secret Watcher: 監控敏感信息的配置變化,比如SSL配置信息.

  • Endpoint Discovery: 服務註冊和自動發現.

    如上圖kubenetes的Upstream自動發現機制: 通過自己的插件把註冊信息寫到Endpoint Discovery中,然後Gloo監控它變化,並把這些信息通過自己翻譯引擎(Translation Engine)成一個完整的xDS Server快照,傳給Envoy,讓他構建這個服務的路由規則及過濾器設置.

  • Reporter:會收集翻譯引擎處理的所有Upstream及Vritual service驗證報告.任何無效的配置對象都會反饋給用戶.無效的對象會被標記爲"Rejected",並在用戶配置中給出詳細的錯誤信息.

Discovery Architechture

discovery Architechture

Gloo支持k8s, consul的Upstream discovery, 還要以自己開發自定義的組件.

Deployment Architecture

Gloo可以在各種基礎設施上以多種方式部署, 推薦是使用kubernets,它可以簡化操作.但並不一定要部署在kubernets上.

sharded-gateway

點擊查看更多的部署方式.

Concepts(核心概念)

通過下面這個簡單的vritual services來理解gloo的核心概念:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: default
  namespace: gloo-system
spec:
  virtualHost:
    domains:
    - '*'
    routes:
    - matchers:
      - prefix: /
      routeAction:
        single:
          upstream:
            name: my-upstream
            namespace: gloo-system

Virtual Services

  • 將一組路由規則規範在某個或多個域(domains)下面.
  • Gloo會建一個默認的virtualService是 default, 它會和*域名匹配.這會把header中沒有Host(:authority)字段的請求,及那些不會找不到路由的請求都路由到這個域下面.
  • VirtualService都在同一個Gloo必須是唯一的,否則找不到路由.
  • 絕大多數實例使用中,讓所有路由都放在一個VirtualService下就足夠了,Gloo也會使用同一套路由規則來處理請求.如果只有一個VirtualServics時,會忽略header中的Host或:authority頭部信息.

Routes

  • Routes是VritualServices的核心組成.如果請求與路由上的matcher匹配了,那麼它就把請求路由到對應的目的地上.路由由一系列的匹配規則(a list of matchers)及各種目的地組成.

    • a single destination 一個目地的.
    • a list of weighted destinations 一組有權重的目地的.
    • **an upstream group ** 一組upstream.
  • 因爲多個matcher可以匹配一個請求,所以路由的先後順序很重要.Gloo會選擇第一個與請求匹配的路由.所以必須把匹配任何路徑(像自定義的404頁面)請求,放在路由列表的最後面.

Matchers

Matchers支持2種請求類型

  • HTTP requests中的請求屬性: 對HTTP 來說就是: path, method, header, query parameters, 對應的HTTP2.0 就是header中的:path, :method屬性.
  • HTTP events根據CloudEvents規範匹配HTTP事件屬性.但CloudEvents 規範還處於 0.2 版本,將來會有更改。Event Matcher目前唯一匹配的屬性是事件的事件類型(由 x-event-type 請求頭指定)

Destinations

  • 匹配路由後,要將請求轉發到Destinations,它可指向單一的目的地,也可以將路由流量分成到一系列加權的目地的上(a series of weighted destinations).
  • Desinations可以是Upstream destination也可以是Function destination.
  • Upstream destination類似於Evnoy集羣.
  • Function destination: Gloo支持將請求路由到各種Upstream中的函數中.函數可以是無服務器的函數調用(Lambda, Google Cloud Function)也可以是REST API OPENAPI, XML/SOAP請求.還可以發佈到消息隊列中.

Upstreams

Upstreams定義了路由規則最終去向(Destinations).一般是通過服務發現(services discovery)自動加入,最基本的Upstream類型就是靜態的Upstream: 它只需要告訴Gloo一個靜態主機或dns名列表.複雜的Upstream有kubernets及AWS lambda upstream.

一個簡單的Upsteams例子

apiVersion: gloo.solo.io/v1
kind: Upstream
metadata: 
  labels:
    discovered_by: kubernetesplugin
  name: default-redis-6379
  namespace: gloo-system
spec:
  discoveryMetadata: {}
  kube:
    selector:
      gloo: redis
    serviceName: redis
    serviceNamespace: gloo-system
    servicePort: 6379
status:
  reported_by: gloo
  state: 1 # Accepted
  • name: 如何在Gloo中找到這個upstream.是一個標識符.
  • spec: kubernetes插件的serviceName,serviceNamespaces,Gloo路由時需要用到.

Functions

有些Upstream支持函數destinations, 比如: 我們可以在Upstream中添加一些HTTP函數.讓Gloo根據這些函數把檢驗請求參數,然後將傳入的請求格式化爲Upstream服務所期望的參數.一個簡單的示例:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: default
  namespace: default
spec:
  virtualHost:
    domains:
    - '*'
    routes:
    - matchers:
       - prefix: /petstore/findWithId
      routeAction:
        single:
          destinationSpec:
            rest:
              functionName: findPetById
              parameters:
                headers:
                  :path: /petstore/findWithId/{id}
          upstream:
            name: petstore
            namespace: gloo-system
      options:
        prefixRewrite: /api/pets

調用curl http://url/petstore/findWithId/100會路由到函數findPetById(id)中,其中Id的是通過parameters中的規則賦值的.

Secrets

  • 某些插件(如AWS Lambda Plugin)需要使用secrets來進行身份驗證,配置SSL證書和其它不應該存儲在明文配置的數據.
  • Gloo運行一個獨立的(gorutine)控制器來保護Secrets.它有自己的storage layer.

Traffic Management

Gloo核心是一個強大的路由引擎.可以處理API到API的簡單路由.也可以處理HTTP到gRPC協議轉換.

Request -> Router -> Destinations(Upstream)

得益於envoy proxy靈活的擴展性,gloo中在上面每一個環節中支持的類型都非常多樣.
下面以HTTP REST API爲例子,演示一下基礎路由功能.

Gloo Configuration

Gloo配置佈局分3層: Gateway listeners, Virtual Services, Upstreams.大多數情況,我們只與VirtualServices進行交互.可以通過它配置暴露給Gateway的API細節,還可以配置具體的路由規則.
obserview
Upstream代表後端服務, Gateway控制監聽端口,請求的入口.

PetStore精確匹配

部署一個完整的PetStore應用.路由規則matcher使用Path精確匹配.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: petstore
  name: petstore
  namespace: default
spec:
  selector:
    matchLabels:
      app: petstore
  replicas: 1
  template:
    metadata:
      labels:
        app: petstore
    spec:
      containers:
      - image: soloio/petstore-example:latest
        name: petstore
        ports:
        - containerPort: 8080
          name: http
---
apiVersion: v1
kind: Service
metadata:
  name: petstore
  namespace: default
  labels:
    service: petstore
spec:
  ports:
  - port: 8080
    protocol: TCP
  selector:
    app: petstore

YAML中定義了使用soloio/petstore-example:latest鏡像創建一個app,並以8080端口對集羣內服務.使用kubectl執行.

$ kubect apply -f ./petstore.ymal
deployment.extensions/petstore created
service/petstore created

​ 檢查服務是否正常啓動:

$ kubectl -n default get pods 
NAME                        READY   STATUS    RESTARTS   AGE
petstore-6f67bbbb74-tg872   1/1     Running   0          20h
$ kubectl -n default get svc petstore
NAME       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
petstore   ClusterIP   10.105.234.177   <none>        8080/TCP   20h

因爲是k8s服務,所以會通過服務發現自動註冊到gloo中.使用gloo查看Upsteam.

$ glooctl get upstreams

這個可以看到所有運行中的upstreams.有一些是系統,比如gloo-system-gateway-443,你也可以在裏面找到

default-petstore-8080 Kubernetes | Accepted | svc name:      petstore         |

查看upstream的詳細情況:

$ glooctl get upstream default-petstore-8080 --output yaml

默認情況下,Upstream非常簡單。它代表了一個特定的kubernetes服務, 但petstore應用是一個swagger服務。Gloo可以發現這個swagger規範,但默認情況下,爲了提高性能,Gloo的函數發現功能被關閉了。爲了在我們的petstore上啓用函數發現服務(fds),我們需要給命名空間打上function_discovery標籤。

$ kubectl label namespace default  discovery.solo.io/function_discovery=enabled
apiVersion: gloo.solo.io/v1
kind: Upstream
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"service":"petstore"},"name":"petstore","namespace":"default"},"spec":{"ports":[{"port":8080,"protocol":"TCP"}],"selector":{"app":"petstore"}}}
  creationTimestamp: null
  generation: 4
  labels:
    discovered_by: kubernetesplugin
    service: petstore
  name: default-petstore-8080
  namespace: gloo-system
  resourceVersion: "5488"
spec:
  discoveryMetadata: {}
  kube:
    selector:
      app: petstore
    serviceName: petstore
    serviceNamespace: default
    servicePort: 8080
    serviceSpec:
      rest:
        swaggerInfo:
          url: http://petstore.default.svc.cluster.local:8080/swagger.json
        transformations:
          addPet:
            body:
              text: '{"id": {{ default(id, "") }},"name": "{{ default(name, "")}}","tag":
                "{{ default(tag, "")}}"}'
            headers:
              :method:
                text: POST
              :path:
                text: /api/pets
              content-type:
                text: application/json
          deletePet:
            headers:
              :method:
                text: DELETE
              :path:
                text: /api/pets/{{ default(id, "") }}
              content-type:
                text: application/json
          findPetById:
            body: {}
            headers:
              :method:
                text: GET
              :path:
                text: /api/pets/{{ default(id, "") }}
              content-length:
                text: "0"
              content-type: {}
              transfer-encoding: {}
          findPets:
            body: {}
            headers:
              :method:
                text: GET
              :path:
                text: /api/pets?tags={{default(tags, "")}}&limit={{default(limit,
                  "")}}
              content-length:
                text: "0"
              content-type: {}
              transfer-encoding: {}
status:
  reported_by: gloo
  state: 1

Endpoints是由Gloo的Function Discovery(fds)服務發現的。之所以能夠做到這一點,是因爲petstore實現了OpenAPI(在petstore-svc/swagger.json處發現了一個Swagger JSON文檔).

  • 增加精確路由規則

    glooctl add route \
      --path-exact /all-pets \
      --dest-name default-petstore-8080 \
      --prefix-rewrite /api/pets
    

    精確匹配path /all-pets -> default-petstore-8080/api/pets

    glooctl get vs可查看到對應路由.

    這裏面用了精確匹配path及transformation的prefix-rewrite.把path重寫了.

  • 測試路由中是否生效,得到所有pets列表.

    $ curl $(glooctl proxy url)/all-pets
    [{"id":1,"name":"Dog","status":"available"},{"id":2,"name":"Cat","status":"pending"}]
    

    其中glooctl proxy url 這個是用於測試或查bug時,可以在集羣外到達代理集羣內的HTTP URL,你可以用同一個網絡中的主機連接到這個地址上.簡單來說這個就是gateway對外的URL.

Prefix前置匹配

新增路由/find-pet/{id} -> default-petstore-8080/api/pets/{id}, 把Id傳到對應HTTP rest API中函數入參.

glooctl add route \
  --path-prefix /find-pet \
  --dest-name default-petstore-8080 \
  --prefix-rewrite /api/pets

這就是把/find-pet/{id} -> default-petstore-8080/api/pets/{id}

使用glooctl 查看virtual service的對應的配置

    - matchers:
      - prefix: /find-pet
      options:
        prefixRewrite: /api/pets
      routeAction:
        single:
          upstream:
            name: default-petstore-8080
            namespace: gloo-system

同時因爲這個是提供的是OPENAPI方式,上面的destination也可以指定函數來確定(達到一樣的路由效果):

glooctl add route \
  --path-prefix /pets \
  --dest-name default-petstore-8080 \
  --rest-function-name findPetById \
  --rest-parameters :path='/pets/{id}'

這就是把/pets/{id} -> default-petstore-8080中的findPetByIdrest函數中.函數的入參id通過--rest-parameters中取.

使用glooctl 查看virtual service的具體配置.

$ glooctl get vs default --output yaml
...
  - matchers:
    - prefix: /pets
    routeAction:
      single:
        destinationSpec:
          rest:
            functionName: findPetById
            parameters:
              headers:
                :path: /pets/{id}
        upstream:
          name: default-petstore-8080
          namespace: gloo-system
...
$ glooctl get upstream --name default-petstore-8080 --output yaml
---
....
  serviceSpec:
    rest:
      swaggerInfo:
        url: http://petstore.default.svc.cluster.local:8080/swagger.json
      transformations:
        addPet:
          body:
            text: '{"id": {{ default(id, "") }},"name": "{{ default(name, "")}}","tag":
              "{{ default(tag, "")}}"}'
          headers:
            :method:
              text: POST
            :path:
              text: /api/pets
            content-type:
              text: application/json
        deletePet:
          headers:
            :method:
              text: DELETE
            :path:
              text: /api/pets/{{ default(id, "") }}
            content-type:
              text: application/json
        findPetById:
          body: {}
          headers:
            :method:
              text: GET
            :path:
              text: /api/pets/{{ default(id, "") }}
            content-length:
              text: "0"
            content-type: {}
            transfer-encoding: {}
        findPets:
          body: {}
          headers:
            :method:
              text: GET
            :path:
              text: /api/pets?tags={{default(tags, "")}}&limit={{default(limit, "")}}
            content-length:
              text: "0"
            content-type: {}
            transfer-encoding: {}
....

可以看到Virtual Service中的destinationSpec 與Upstream中serviceSpec對應上了.都是findPetById(Id),所以路由才能通.

$ curl "$(glooctl proxy url)/pets/1"
{"id":1,"name":"Dog","status":"available"}

注意: paramters中的:path是精確匹配的.如果你把url最後多寫一個/, 變成/pets/1/,那就會

curl "$(glooctl proxy url)/pets/1/"
[{"id":1,"name":"Dog","status":"available"},{"id":2,"name":"Cat","status":"pending"}]

這裏返回了所有pets,因爲多了/後rest-parameters裏面的:path是/pets/{id},多了/後變得無法匹配,所以相當於沒有傳Id,導致請求的是findPetById(""),此函數返回的是所有pets.

regex正則匹配

由於find-pet路由沒有增加對查詢Id的範圍限制,所以我們可以把它使用regex作限制.

glooctl add route \
  --path-regex '/find-pet-1/[1-9]' \
  --dest-name default-petstore-8080 \
  --rest-function-name findPetById \
  --rest-parameters :path='/find-pet-1/{id}'
$ curl http://localhost:80/find-pet-1/1
{"id":1,"name":"Dog","status":"available"}
$ curl http://localhost:80/find-pet-1/11
{"code":404,"message":"path /api/pets-1/11 was not found"}%

可以看到參數已經被限制在1-10之間了.

前面增加Router都是通過glooctl add route 命令行來完成的..下面我們再通過YAML配置文本來做管理.
誤區: 由於PetStore是k8s中的一個services, 他可以直接通過命令

kubectl edit service -n default petstore

打開編輯器直接編輯, 但是這個打開的內容是沒有Gloo附加在上面的路由信息的.路由信息存在vritual service裏面,所以也不能在這裏編輯.你通過 kubectl get service -n default petstore --output yaml

apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"service":"petstore"},"name":"petstore","namespace":"default"},"spec":{"ports":[{"port":8080,"protocol":"TCP"}],"selector":{"app":"petstore"}}}
  creationTimestamp: "2020-04-22T14:32:09Z"
  labels:
    service: petstore
  name: petstore
  namespace: default
  resourceVersion: "729"
  selfLink: /api/v1/namespaces/default/services/petstore
  uid: f55adfc3-7181-414f-809d-b29cf5e163b7
spec:
  clusterIP: 10.105.234.177
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: petstore
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

可以看到在這個k8s的service中根本沒有我們剛加入的gloo中的routers.

所以我們只能在gloo中的virtual service中找到routers編輯.

得到virtual service配置:glooctl get vs default --output kube-yaml

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"gateway.solo.io/v1","kind":"VirtualService","metadata":{"annotations":{},"creationTimestamp":null,"generation":48,"name":"default","namespace":"gloo-system","resourceVersion":"93283"},"spec":{"virtualHost":{"domains":["*"],"routes":[{"matchers":[{"prefix":"/pets"}],"routeAction":{"single":{"destinationSpec":{"rest":{"functionName":"findPetById","parameters":{"headers":{":path":"/pets/{id}"}}}},"upstream":{"name":"default-petstore-8080","namespace":"gloo-system"}}}},{"matchers":[{"exact":"/all-pets"}],"options":{"prefixRewrite":"/api/pets"},"routeAction":{"single":{"upstream":{"name":"default-petstore-8080","namespace":"gloo-system"}}}},{"matchers":[{"regex":"/add-pet/[1-9]/[a-z]{2,10}/(pending|available)"},{"methods":["GET"]}],"routeAction":{"single":{"destinationSpec":{"rest":{"functionName":"addPet","parameters":{"headers":{":path":"/add-pet/{id}/{name}/{tag}"}}}},"upstream":{"name":"default-petstore-8080","namespace":"gloo-system"}}}}]}},"status":{"reported_by":"gateway","state":1,"subresource_statuses":{"*v1.Proxy.gloo-system.gateway-proxy":{"reported_by":"gloo","state":1}}}}
  creationTimestamp: null
  generation: 73
  name: default
  namespace: gloo-system
  resourceVersion: "99511"
spec:
  virtualHost:
    domains:
    - '*'
    routes:
    - matchers:
      - regex: /find-pet-1/[1-9]
      routeAction:
        single:
          destinationSpec:
            rest:
              functionName: findPetById
              parameters:
                headers:
                  :path: /find-pet-1/{id}
          upstream:
            name: default-petstore-8080
            namespace: gloo-system
    - matchers:
      - prefix: /pets
      routeAction:
        single:
          destinationSpec:
            rest:
              functionName: findPetById
              parameters:
                headers:
                  :path: /pets/{id}
          upstream:
            name: default-petstore-8080
            namespace: gloo-system
    - matchers:
      - prefix: /find-pet
      options:
        prefixRewrite: /api/pets
      routeAction:
        single:
          upstream:
            name: default-petstore-8080
            namespace: gloo-system
    - matchers:
      - exact: /all-pets
      options:
        prefixRewrite: /api/pets
      routeAction:
        single:
          upstream:
            name: default-petstore-8080
            namespace: gloo-system
status:
  reported_by: gateway
  state: 1
  subresource_statuses:
    '*v1.Proxy.gloo-system.gateway-proxy':
      reported_by: gloo
      state: 1

複製後保存爲yaml文件,並在router結尾中增加路由規則.

- matchers:
      - regex: /add-pet/[1-9]/[a-z]{2,10}/(pending|available)      
      routeAction:
        single:
          destinationSpec:
            rest:
              functionName: addPet
              parameters:
                headers:
                  :path: /add-pet/{id}/{name}/{tag}
          upstream:
            name: default-petstore-8080
            namespace: gloo-system

這個命令會直接通過編輯器打開它的YAML配置文件.我們直接加入新路由配置後保存.

這個命令把path上的參數匹配後傳到了destination中的addPet的body中,完成了路由regx及body transformation.

Tips:爲了做好版管理,所以用get得到的YAML格式中有一個字段resourceVersion.如果你apply同一個文件2次,第二次會出錯.你必須重新get最新的YAML文件以獲取新的resourceVersion.

刪除route

你可以使用glooctl刪除不需要的路由規則.

glooctl rm route -i

-i----interactive模式,一步步通過提示刪除路由

Matcher陳了上面說過的對Path進行匹配外,還可以對Header, Query Parameter, Method也作同樣的匹配.

Header路由示例

- matchers:
         - headers:
            - name: version
              value: "v1"
            - name: os_type
            - name: type
              regex: true
              value: "[a-z]{1}"
            - name: Istest
              invertMatch: true
            - name: Istrace
              value: 0
              invertMatch: true
           prefix: /        

各個條件之間是與(and)的關係.上面就是:
version=v1 and 必須有os_type字段 and type在小寫的a-z之間and 沒有Istest字段andIstrace必須有且不等於0

Query Parameter路由示例

- matchers:
   - queryParameters:
      - name: os
        value: ios
      - name: location
      - name: userno
        regex: true
        value: "a[a-z]{9}"
      prefix: /

os是ios and 必須有location字段 and userno 是以a開頭,全小寫,共10位的用戶.

Method路由示例

- matchers:
   - methods:
      - GET
     prefix: /

限制HTTP Method,可以指定一個列表.

Transformations

Gloo可以在請求到達到指定的Service前把請求進行任意修改(requestTransformation),也可以在應答返回給Client之前把應答進行任意修改(responseTransformation).

Transformations屬性定義在Virtual Services, 你可以在它的VritualHosts, Routes, WeightedDestionations的屬性下定義Transformations, 它們的格式都是一樣的.唯一的區別是作用的範圍大小不一樣.所有的子屬性都會受到對應的transformations影響.如果你要同時在VritualHostsRoutes都定義了2個transformations,那Routers不會合並VritualHosts,兩者各不影響.

transformations:
  clearRouteCache: bool
  requestTransformation: {}
  responseTransformation: {}
  • clearRouterCache: 有時transformation會改變路由比如改了path後不應該再到這個路由條件下)後,如果設置爲true,則在改變後會重新(根據新的path)找路由,如果是false,則還是走轉換前的路由.默認爲false.
  • requestTransformationresponseTransformation一樣的格式,處理方法也是一樣的.他有兩種形式
    • headerBodyTransform: 把所有的header內容json的形式都寫到body裏面.分成headersbody字段.
    • transformationTemplate: 使用轉換模板.這是最靈活的.下面會詳細介紹屬性.

transformationTemplate

transformationTemplate:
  parseBodyBehavior: {}
  ignoreErrorOnParse: bool
  extractors:  {}
  headers: {}
  # Only one of body, passthrough, and mergeExtractorsToBody can be specified
  body: {} 
  passthrough: {}
  mergeExtractorsToBody: {}
  dynamicMetadataValues: []
  advancedTemplates: bool

Templates是Transformation的核心,本質就是利用上面這幾個關鍵字對Request/Response的所有內容進行任意轉換,寫出一個你想要的轉換函數API.

  • parseBodyBehavior: 默認爲**ParseAsJson, **json的方式解析body , DontParse: 以`plain text的方式處理.

  • ignoreErrorOnParse: 解析body爲json時出錯是否拋出異常, 默認爲false.即拋出異常.

  • extractors: 可以提出header及body裏面的值作爲變量,相當於定義變量,然後變量賦值.

    extractors:
      myFooHeader:  #這個變是變量名
        header: 'foo' # 這個就是從頭裏面取值,然後放到變量中,還可以寫在body: {},這樣就是取body的內容
    
    • header 提取header裏面爲foo的值.
    • 你也可以在Extractors中使用正則來提取.
    • 兩種方式取到這值:默認下{{myFooHeader}}, 如果設置中advancedTemplates是true,則需要像函數一樣調用它: {{ extraction(myFooHeader) }}
  • headers : 注意這裏的headers不是extractors中的header, extractors是取值給變量,這裏是把變量轉換到請求/應答中的頭中.

    transformationTemplate:
      headers:
        bar:
          text: '{{ extration("myFooHeader") }}'
    

    流程是提取的值放到myFooHeader然後再把myFooHeader的值放到頭中爲bar的字段中.

    這種簡單的轉換你也不使用中間變量達到一樣的效果, 直接使用{{ header("foo") }}, 替換text內容. header("foo")函數是一個和extraction一樣的內置函數,等下面會列出所有的內置函數.

  • body: 注意這裏的body不是extractors中的body, extractors是取值給變量,這裏是把變量轉換到請求/應答中的body中.

    transformationTemplate:
      # ...
      body: 
        text: '{% if header(":status") == "404" %}{ "error": "Not found!" }{% else %}{{ body() }}{% endif %}'
      # ...
    

    前面已經說過了header(":status")是內置函數,這裏面的body()也同樣是. 如果status是404,則把body內容重寫.否則保持不變.

  • passthrough: 完全不想處理body,則設置它爲true,這和parseBodyBehavior裏面的DontParse有區別.如果完全不想管body,則設置爲true, DontParse是以plain text處理.

  • mergeExtractorsToBody: 他會把所有extrations得到的變量都合併到body裏面.比如:

    transformationTemplate:
      mergeExtractorsToBody: {}
      extractors:
      path:
        header: ':path'
        regex: '.*'
      # The name of this attribute determines where the value will be nested in the body
      host.name:
        header: 'host'
        regex: '.*'
    

    轉換後的body爲:

    {
      "path": "/the/request/path",
      "host": {
        "name": "value of the 'host' header"
      }
    }
    
  • dynamicMetadataValues: 動態設置metadata值.因爲內置的這些函數和extractor值只能在TransformationTemplate中使用,有時我們需要其它的地方使用,這時間就要需要把在template中得到值賦值到動態的metadata中, 動態的metadata是可以全局使用的.比如:

    options:
          transformations:        
            responseTransformation:
              transformationTemplate:
                dynamicMetadataValues:
                # 設置dynamic metadata entry 叫"pod_name"
                - key: 'pod_name'
                  value:
                    # The POD_NAME env is set by default on the gateway-proxy pods
                    text: '{{ env("POD_NAME") }}'            
                # Set a dynamic metadata entry using an request body attribute
                - key: 'endpoint_url'
                  value:
                    # The "url" attribute in the JSON response body
                    text: '{{ url }}'
    

    比如我們在設置全局log裏需要使用到這個pod_name和endpoint_url時,就可以配置爲:

    apiVersion: gateway.solo.io/v1
    kind: Gateway
    metadata:
      labels:
        app: gloo
      name: gateway-proxy
      namespace: gloo-system
    proxyNames:
    - gateway-proxy
    spec:
      bindAddress: '::'
      bindPort: 8080
      httpGateway: {}
      options:
        accessLoggingService:
          accessLog:
          - fileSink:
              jsonFormat:
                httpMethod: '%REQ(:METHOD)%'
                pod_name: '%DYNAMIC_METADATA(io.solo.transformation:pod_name)%'
                endpoint_url: '%DYNAMIC_METADATA(io.solo.transformation:endpoint_url)%'
              path: /dev/stdout
    

    這樣看到的log就可以是:

    kubectl logs -n gloo-system deployment/gateway-proxy | grep '^{' | jq
    {  
      "pod_name": "\"gateway-proxy-f46b58f89-5fkmd\"",
      "httpMethod": "GET",
      "endpoint_url": "\"https://postman-echo.com/get\""
    }
    
  • 內置函數

    除了支持https://pantor.github.io/inja/裏面的模板函數,可以寫循環,if,math計算.還有gloo自定義函數:

    • header(header_name):返回header中叫header_name的值.
    • extraction(extraction_name):返回extraction中叫extraction_name的值.
    • env(env_var_name): 返回環境變量值
    • body(): 返回body.
    • context():以json的方式返回所有的上下文(幾乎是所有信息了,你打出來一看就知道了).

Update Response Code

很多Rest API的設計會把Response請求都返回200 ok, 業務出錯的情況則在body裏面規定一個ret返回碼,和err_msg字段.比如:騰訊公開的API都是這樣設計的:https://wiki.open.qq.com/wiki/v3/user/get_info
如果我們不希望把具體的業務錯返回用戶,則可以寫一個transformations只有body裏面有ret不爲0,則返回400.

options:
  transformations:
    responseTransformation:
      transformationTemplate:
        headers:              
          ":status":
           text: '{% if default(ret, 0) != 0 %}400{% else %}{{ header(":status") }}{% endif %}'

這裏可以直接使用ret變量,是因爲前面默認是以json解析body,然後inja template支持這樣的語法取json body.

Extrac Query Parameters

把QueryString變成header裏面的kv.

options:
  transformations:
    requestTransformation:
      transformationTemplate:
        extractors:              
          foo: #extractors的名字,相當於變量名
            # The :path pseudo-header contains the URI
            header: ':path'
            # Use a nested capturing group to extract the query param
            regex: '(.*foo=([^&]*).*)'
            subgroup: 2
          bar: #extractors的名字,相當於變量名                
            header: ':path'                
            regex: '(.*bar=([^&]*).*)'
            subgroup: 2            
        headers:
          foo:
            text: '{{ foo }}'
          bar:
            text: '{{ bar }}'

header中使用:path是因爲envoy使用的是http2的協議來做transformat,所以如果你使用的是http1.1的話,就需要使用 :path. http2的path就是header中的:path字段.

curl "http:xxxxx/get?foo=foo-value&bar=bar=bar-value"
#轉換後效果相當於
curl -H foo=foo-value -H bar=bar-value "http:xxxxx/get"

Update Request Path

options:
  transformations:
    requestTransformation:
      transformationTemplate:
        headers:
          # By updating the :path pseudo-header, we update the request URI
          ":path":
            text: '{% if header("foo") == "bar" %}/post{% else %}{{ header(":path") }}{% endif %}'          
          ":method":
            text: '{% if header("foo") == "bar" %}POST{% else %}{{ header(":method") }}{% endif %}'

這個比較簡單,都沒有用到extractor.效果相當於: 如果header有字段foo=bar則無把path改成/post.並把http方法也改成POST.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章