Architechture(架構)
Gloo通過Envoy XDS gRPC API來動態更新Envoy配置, 更方便的控制Envoy Proxy, 並保留擴展性..本質是一個Envoy xDS配置翻譯引擎, 爲Envoy提供高級配置(及定製的Envoy過濾器).它監控各種配置源的更新,並立即響應通過gRPC更新給Envoy.
Component 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
Gloo支持k8s, consul的Upstream discovery, 還要以自己開發自定義的組件.
Deployment Architecture
Gloo可以在各種基礎設施上以多種方式部署, 推薦是使用kubernets,它可以簡化操作.但並不一定要部署在kubernets上.
點擊查看更多的部署方式.
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細節,還可以配置具體的路由規則.
Upstream代表後端服務, Gateway控制監聽端口,請求的入口.
PetStore精確匹配
部署一個完整的PetStore應用.路由規則matcher使用Path精確匹配.
- 根據官方指引安裝kubernetes與gloo.
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
中的findPetById
rest函數中.函數的入參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字段and
Istrace必須有且不等於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影響.如果你要同時在VritualHosts和Routes都定義了2個transformations,那Routers不會合並VritualHosts,兩者各不影響.
transformations:
clearRouteCache: bool
requestTransformation: {}
responseTransformation: {}
- clearRouterCache: 有時transformation會改變路由比如改了path後不應該再到這個路由條件下)後,如果設置爲true,則在改變後會重新(根據新的path)找路由,如果是false,則還是走轉換前的路由.默認爲false.
- requestTransformation和responseTransformation一樣的格式,處理方法也是一樣的.他有兩種形式
- headerBodyTransform: 把所有的header內容json的形式都寫到body裏面.分成headers及body字段.
- 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) }}
- header 提取header裏面爲
-
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
.