Istio 使用 Gateway API 實現(xiàn)流量管理
Gateway API 是由 SIG-NETWORK 社區(qū)管理的開源項目,項目地址:https://gateway-api.sigs.k8s.io/。主要原因是 Ingress 資源對象不能很好的滿足網(wǎng)絡(luò)需求,很多場景下 Ingress 控制器都需要通過定義 annotations 或者 crd 來進行功能擴展,這對于使用標準和支持是非常不利的,新推出的 Gateway API 旨在通過可擴展的面向角色的接口來增強服務(wù)網(wǎng)絡(luò)。
Gateway API 是 Kubernetes 中的一個 API 資源集合,包括 GatewayClass、Gateway、HTTPRoute、TCPRoute、Service 等,這些資源共同為各種網(wǎng)絡(luò)用例構(gòu)建模型。
Gateway API
Gateway API 最初設(shè)計用于管理從集群外部客戶端到集群內(nèi)部服務(wù)的流量(入口或北/南情況)。隨著時間的推移,服務(wù)網(wǎng)格用戶的興趣促使 GAMMA(Gateway API for Service Mesh)計劃的創(chuàng)建,以定義 Gateway API 如何用于同一集群內(nèi)的服務(wù)間或東/西流量。
Gateway API 的改進比當前的 Ingress 資源對象有很多更好的設(shè)計:
- 面向角色 - Gateway 由各種 API 資源組成,這些資源根據(jù)使用和配置 Kubernetes 服務(wù)網(wǎng)絡(luò)的角色進行建模。
- 通用性 - 和 Ingress 一樣是一個具有眾多實現(xiàn)的通用規(guī)范,Gateway API 是一個被設(shè)計成由許多實現(xiàn)支持的規(guī)范標準。
- 更具表現(xiàn)力 - Gateway API 資源支持基于 Header 頭的匹配、流量權(quán)重等核心功能,這些功能在 Ingress 中只能通過自定義注解才能實現(xiàn)。
- 可擴展性 - Gateway API 允許自定義資源鏈接到 API 的各個層,這就允許在 API 結(jié)構(gòu)的適當位置進行更精細的定制。
還有一些其他值得關(guān)注的功能:
- GatewayClasses - GatewayClasses 將負載均衡實現(xiàn)的類型形式化,這些類使用戶可以很容易了解到通過 Kubernetes 資源可以獲得什么樣的能力。
- 共享網(wǎng)關(guān)和跨命名空間支持 - 它們允許共享負載均衡器和 VIP,允許獨立的路由資源綁定到同一個網(wǎng)關(guān),這使得團隊可以安全地共享(包括跨命名空間)基礎(chǔ)設(shè)施,而不需要直接協(xié)調(diào)。
- 規(guī)范化路由和后端 - Gateway API 支持類型化的路由資源和不同類型的后端,這使得 API 可以靈活地支持各種協(xié)議(如 HTTP 和 gRPC)和各種后端服務(wù)(如 Kubernetes Service、存儲桶或函數(shù))。
- 服務(wù)網(wǎng)格支持 - Gateway API 支持將路由資源與服務(wù)資源關(guān)聯(lián),以配置服務(wù)網(wǎng)格以及入口控制器。
面向角色設(shè)計
無論是道路、電力、數(shù)據(jù)中心還是 Kubernetes 集群,基礎(chǔ)設(shè)施都是為了共享而建的,然而共享基礎(chǔ)設(shè)施提供了一個共同的挑戰(zhàn),那就是如何為基礎(chǔ)設(shè)施用戶提供靈活性的同時還能被所有者控制。
Gateway API 通過對 Kubernetes 服務(wù)網(wǎng)絡(luò)進行面向角色的設(shè)計來實現(xiàn)這一目標,平衡了靈活性和集中控制。它允許共享的網(wǎng)絡(luò)基礎(chǔ)設(shè)施(硬件負載均衡器、云網(wǎng)絡(luò)、集群托管的代理等)被許多不同的團隊使用,所有這些都受到集群運維設(shè)置的各種策略和約束。
gateway api demo
一個集群運維人員創(chuàng)建了一個基于 GatewayClass 的 Gateway 資源,這個 Gateway 配置了它所代表的基礎(chǔ)網(wǎng)絡(luò)資源,集群運維和特定的團隊必須溝通什么可以附加到這個 Gateway 上來暴露他們的應(yīng)用。 集中的策略,如 TLS,可以由集群運維在 Gateway 上強制執(zhí)行,同時,Store 和 Site 應(yīng)用在他們自己的命名空間中運行,但將他們的路由附加到相同的共享網(wǎng)關(guān)上,允許他們獨立控制他們的路由邏輯。
這種關(guān)注點分離的設(shè)計可以使不同的團隊能夠管理他們自己的流量,同時將集中的策略和控制留給集群運維。
概念
在整個 Gateway API 中涉及到 3 個角色:基礎(chǔ)設(shè)施提供商、集群管理員、應(yīng)用開發(fā)人員,在某些場景下可能還會涉及到應(yīng)用管理員等角色。Gateway API 中定義了 3 種主要的資源模型:GatewayClass、Gateway、Route。
GatewayClass
GatewayClass 定義了一組共享相同配置和動作的網(wǎng)關(guān)。每個 GatewayClass 由一個控制器處理,是一個集群范圍的資源,必須至少有一個 GatewayClass 被定義。
這與 Ingress 的 IngressClass 類似,在 Ingress v1beta1 版本中,與 GatewayClass 類似的是 ingress-class 注解,而在 Ingress V1 版本中,最接近的就是 IngressClass 資源對象。
Gateway
Gateway 網(wǎng)關(guān)描述了如何將流量轉(zhuǎn)化為集群內(nèi)的服務(wù),也就是說,它定義了一個請求,要求將流量從不了解 Kubernetes 的地方轉(zhuǎn)換到集群內(nèi)的服務(wù)。例如,由云端負載均衡器、集群內(nèi)代理或外部硬件負載均衡器發(fā)送到 Kubernetes 服務(wù)的流量。
它定義了對特定負載均衡器配置的請求,該配置實現(xiàn)了 GatewayClass 的配置和行為規(guī)范,該資源可以由管理員直接創(chuàng)建,也可以由處理 GatewayClass 的控制器創(chuàng)建。
Gateway 可以附加到一個或多個路由引用上,這些路由引用的作用是將流量的一個子集導(dǎo)向特定的服務(wù)。
Route 資源
路由資源定義了特定的規(guī)則,用于將請求從網(wǎng)關(guān)映射到 Kubernetes 服務(wù)。從 v1alpha2 版本開始,API 中包含四種 Route 路由資源類型。
HTTPRoute
HTTPRoute 是用于 HTTP 或 HTTPS 連接,適用于我們想要檢查 HTTP 請求并使用 HTTP 請求進行路由或修改的場景,比如使用 HTTP Headers 頭進行路由,或在請求過程中對它們進行修改。
TLSRoute
TLSRoute 用于 TLS 連接,通過 SNI 進行區(qū)分,它適用于希望使用 SNI 作為主要路由方法的地方,并且對 HTTP 等更高級別協(xié)議的屬性不感興趣,連接的字節(jié)流不經(jīng)任何檢查就被代理到后端。
TCPRoute 和 UDPRoute
TCPRoute(和 UDPRoute)旨在用于將一個或多個端口映射到單個后端。在這種情況下,沒有可以用來選擇同一端口的不同后端的判別器,所以每個 TCPRoute 在監(jiān)聽器上需要一個不同的端口。你可以使用 TLS,在這種情況下,未加密的字節(jié)流會被傳遞到后端,當然也可以不使用 TLS,這樣加密的字節(jié)流將傳遞到后端。
GRPCRoute
GRPCRoute 用于路由 gRPC 流量,支持 GRPCRoute 的網(wǎng)關(guān)必須支持 HTTP/2,無需從 HTTP/1 進行初始升級,因此可以確保 gRPC 流量正常進行。
組合
GatewayClass、Gateway、xRoute 和 Service 的組合定義了一個可實施的負載均衡器,下圖說明了不同資源之間的關(guān)系:
組合關(guān)系
使用反向代理實現(xiàn)的網(wǎng)關(guān)的典型客戶端/網(wǎng)關(guān) API 請求流程如下所示:
- 客戶端向 http://foo.example.com 發(fā)出請求。
- DNS 將域名解析為 Gateway 網(wǎng)關(guān)地址。
- 反向代理在監(jiān)聽器上接收請求,并使用 Host Header 來匹配 HTTPRoute。
- (可選)反向代理可以根據(jù) HTTPRoute 的匹配規(guī)則進行路由。
- (可選)反向代理可以根據(jù) HTTPRoute 的過濾規(guī)則修改請求,即添加或刪除 headers。
- 最后,反向代理根據(jù) HTTPRoute 的 forwardTo 規(guī)則,將請求轉(zhuǎn)發(fā)給集群中的一個或多個對象,即服務(wù)。
與 Istio API 的區(qū)別
我們這里主要是講解 Gateway API 在服務(wù)網(wǎng)格中的使用,首先我們先了解下 Gateway API 與 Istio API 的區(qū)別。
Gateway API 與 Istio API(如 Gateway 和 VirtualService)有很多相似之處。主資源使用相同的 Gateway 名稱,并且這些資源服務(wù)于相類似的目標。
新的 Gateway API 致力于從 Kubernetes 的各種 Ingress 實現(xiàn)(包括 Istio)中吸取經(jīng)驗,以構(gòu)建標準化的,獨立于供應(yīng)商的 API。這些 API 通常與 Istio Gateway 和 VirtualService 具有相同的用途,但依然有一些不同的地方:
- Istio API 中的 Gateway 僅配置已部署的現(xiàn)有網(wǎng)關(guān) Deployment/Service,而在 Gateway API 中的 Gateway 資源不僅配置也會部署網(wǎng)關(guān)。
- 在 Istio VirtualService 中,所有協(xié)議都在單一的資源中配置,而在 Gateway API 中,每種協(xié)議類型都有自己的資源,例如 HTTPRoute 和 TCPRoute。
- 雖然 Gateway API 提供了大量豐富的路由功能,但它還沒有全部涵蓋 Istio 的全部特性。
實現(xiàn)
接下來我們就來了解下如何在 Istio z 中使用 Gateway API。默認情況下 Kubernetes 集群中不會安裝 Gateway API,首先我們需要安裝 Gateway API CRD:
$ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/referencegrants.gateway.networking.k8s.io created
standard-install.yaml 包括所有已升級為 GA 或 Beta 的資源,包括 GatewayClass、Gateway、HTTPRoute 和 ReferenceGrant,由于 Istio 已經(jīng)對 Gateway API 提供了支持,所以現(xiàn)在我們就可以直接使用了。比如現(xiàn)在就會自動創(chuàng)建一個 istio 的 GatewayClass 資源對象,如下所示(另外還有一個名為 istio-remote):
$ kubectl get gatewayclass
NAME CONTROLLER ACCEPTED AGE
istio istio.io/gateway-controller True 106s
istio-remote istio.io/unmanaged-gateway True 106s
$ kubectl get gatewayclass istio -oyaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: istio
spec:
controllerName: istio.io/gateway-controller
description: The default Istio GatewayClass
因為大部分場景下在一個 Kubernetes 集群中只會有一個 Istio 集群,所以我們可以直接使用默認的 istio 這個 GatewayClass,如果你有多個 Istio 集群,那么你可以創(chuàng)建多個 GatewayClass 來區(qū)分不同的集群。
比如接下來我們來嘗試將 httpbin 應(yīng)用使用 Gateway API 暴露到外部,首先我們需要部署一個 httpbin 應(yīng)用:
kubectl apply -f samples/httpbin/httpbin.yaml
然后接下來同樣我們需要部署一個 Gateway 資源對象,用于將流量從外部負載均衡器轉(zhuǎn)發(fā)到集群內(nèi)的服務(wù),如下所示:
# default-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: istio-ingress # 網(wǎng)關(guān)資源對象所在的命名空間
spec:
gatewayClassName: istio # 使用默認的 istio GatewayClass
listeners: # 監(jiān)聽器
- name: default
hostname: "*.example.com"
port: 80
protocol: HTTP
allowedRoutes: # 允許的路由
namespaces:
from: All # 允許所有命名空間
Gateway 代表了邏輯負載均衡器的實例化,它是根據(jù)一個 istio 這個 GatewayClass 進行模板化的,網(wǎng)關(guān)在 80 端口上監(jiān)聽 HTTP 流量,這個特定的 GatewayClass 在部署后會自動分配一個 IP 地址,該地址會顯示在 Gateway.status 中。
需要注意的是這里我們聲明使用的命名空間為 istio-ingress, 這是因為 Istio Gateway 可以直接使用 istio ingressgateway 的 Deployment,而這個 Deployment 默認是部署在 istio-system 命名空間中的,我們這里單獨將 Gateway 資源對象創(chuàng)建在 istio-ingress 命名空間中,那么就會自動在這個命名空間中部署一個網(wǎng)關(guān)控制器,用于區(qū)分默認的 istio ingressgateway。
所以我們需要在 istio-ingress 命名空間中創(chuàng)建這個 Gateway 資源對象,這樣才能讓 istio ingressgateway 通過 Gateway 資源對象來獲取配置。
kubectl create namespace istio-ingress
然后直接應(yīng)用這個資源對象即可:
$ kubectl apply -f default-gateway.yaml
$ kubectl get gateway -n istio-ingress
NAME CLASS ADDRESS PROGRAMMED AGE
gateway istio False 8m23s
$ kubectl get deploy -n istio-ingress
NAME READY UP-TO-DATE AVAILABLE AGE
gateway-istio 1/1 1 1 8m35s
$ kubectl get svc -n istio-ingress
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gateway-istio LoadBalancer 10.100.103.86 <pending> 15021:31509/TCP,80:32530/TCP 9m38s
我們也可以去對比下上面生成的 gateway-istio 和 Istio 默認的 istio-ingressgateway 的 Deployment,他們的配置幾乎是一樣的。
接下來我們就需要去創(chuàng)建一個路由規(guī)則了,也就是想要如何訪問我們的 httpbin 應(yīng)用,類似于 VirtualService,我們可以使用 HTTPRoute 資源對象來定義這個路由規(guī)則,如下所示:
# httpbin-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httpbin
namespace: default
spec:
parentRefs: # 引用定義的 Gateway 對象
- name: gateway
namespace: istio-ingress
hostnames: ["httpbin.example.com"] # 域名
rules: # 具體的路由規(guī)則
- matches:
- path:
type: PathPrefix
value: /get # 匹配 /get 的請求
backendRefs: # 引用的后端服務(wù)
- name: httpbin
port: 8000
在上面的 HTTPRoute 對象中我們通過 parentRefs 字段指定要連接到的網(wǎng)關(guān),只要網(wǎng)關(guān)允許這種連接,這將允許路由接收來自父網(wǎng)關(guān)的流量,在 backendRefs 中定義將要發(fā)送流量的后端。但是需要注意我們這里只定義了匹配 /get 這個路徑的請求,然后將要訪問的域名通過 hostnames 來定義。
同樣直接應(yīng)用該資源對象即可:
$ kubectl apply -f httpbin-route.yaml
$ kubectl get httproute
NAME HOSTNAMES AGE
httpbin ["httpbin.example.com"] 5s
然后我們就可以通過 httpbin.example.com 來訪問 httpbin 應(yīng)用了:
# $ export INGRESS_HOST=$(kubectl get gateways.gateway.networking.k8s.io gateway -n istio-ingress -ojsnotallow='{.status.addresses[0].value}')
export GATEWAY_URL=$(kubectl get po -l istio.io/gateway-name=gateway -n istio-ingress -o 'jsnotallow={.items[0].status.hostIP}'):$(kubectl get svc gateway-istio -n istio-ingress -o 'jsnotallow={.spec.ports[?(@.name=="default")].nodePort}')
如果你的集群可以正常使用 LoadBalancer,那么 Gateway 控制器在部署后會自動分配一個 IP 地址,該地址會顯示在 Gateway.status 中。我們這里暫不支持,所以還是可以通過 NodePort 方式來進行訪問。然后可以使用 curl 訪問 httpbin 服務(wù):
$ curl -s -HHost:httpbin.example.com "http://$GATEWAY_URL/get"
HTTP/1.1 200 OK
server: istio-envoy
date: Mon, 18 Dec 2023 07:29:11 GMT
content-type: application/json
content-length: 494
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 2
請注意,使用 -H 標志可以將 Host HTTP 標頭設(shè)置為 httpbin.example.com。這一步是必需的,因為 HTTPRoute 已配置為處理 httpbin.example.com 的請求,但是在測試環(huán)境中,該主機沒有 DNS 綁定,只是將請求發(fā)送到入口 IP。
訪問其他沒有被顯式暴露的 URL 時,正常就會看到 HTTP 404 錯誤:
$ curl -s -I -HHost:httpbin.example.com "http://$INGRESS_HOST/headers"
HTTP/1.1 404 Not Found
date: Mon, 18 Dec 2023 07:31:51 GMT
server: istio-envoy
transfer-encoding: chunked
同樣我們也可以查看下 gateway-istio 的日志,可以看到類似于下面的日志:
$ kubectl -n istio-ingress logs -f gateway-istio-7474cd4d9b-8dw2k
# ......
2023-12-18T07:04:33.164968Z info cache returned workload trust anchor from cache ttl=23h59m59.835033638s
2023-12-18T07:04:33.621616Z info Readiness succeeded in 813.201909ms
2023-12-18T07:04:33.621946Z info Envoy proxy is ready
[2023-12-18T07:29:11.177Z] "HEAD /get HTTP/1.1" 200 - via_upstream - "-" 0 0 2 2 "10.244.1.1" "curl/7.29.0" "68f51e89-8125-4d9e-be36-e0cdc6e6ead8" "httpbin.example.com" "10.244.1.131:80" outbound|8000||httpbin.default.svc.cluster.local 10.244.1.132:46066 10.244.1.132:80 10.244.1.1:55626 - default.httpbin.0
[2023-12-18T07:31:52.051Z] "HEAD /headers HTTP/1.1" 404 NR route_not_found - "-" 0 0 0 - "10.244.1.1" "curl/7.29.0" "a9a1002f-d65c-40d8-861d-ec99d4a4a442" "httpbin.example.com" "-" - - 10.244.1.132:80 10.244.1.1:53583 - -
證明我們的路由規(guī)則已經(jīng)生效了。
同樣如果我們想要能夠正常訪問刀 /headers 路由,那么我們可以更新下 HTTPRoute 對象:
# httpbin-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httpbin
namespace: default
spec:
parentRefs: # 引用定義的 Gateway 對象
- name: gateway
namespace: istio-ingress
hostnames: ["httpbin.example.com"] # 域名
rules: # 具體的路由規(guī)則
- matches:
- path:
type: PathPrefix
value: /get # 匹配 /get 的請求
- path:
type: PathPrefix
value: /headers # 匹配 /headers 的請求
filters:
- type: RequestHeaderModifier # 添加一個修改請求頭的過濾器
requestHeaderModifier:
add: # 添加一個標頭
- name: my-added-header
value: added-value
backendRefs: # 引用的后端服務(wù)
- name: httpbin
port: 8000
我們除了在 rules 中添加了一個匹配 /headers 的規(guī)則外,還添加了一個 RequestHeaderModifier 過濾器,用于添加一個 Header 頭信息,更新這個資源對象后再次訪問 /headers,注意到 My-Added-Header 標頭已被添加到請求中了:
$ curl -s -HHost:httpbin.example.com "http://$GATEWAY_URL/headers"
{
"headers": {
"Accept": "*/*",
"Host": "httpbin.example.com",
"My-Added-Header": "added-value", # 添加了一個Header頭
"User-Agent": "curl/7.29.0",
"X-Envoy-Attempt-Count": "1",
"X-Envoy-Internal": "true",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=11fc66a1c6c9fc44e65ec67f7f8b16d06fbaa73d9729e141e0bd91134dc59db3;Subject=\"\";URI=spiffe://cluster.local/ns/istio-ingress/sa/gateway-istio"
}
}
在上面的示例中,在配置網(wǎng)關(guān)之前,我們并沒有去安裝 Ingress 網(wǎng)關(guān)的 Deployment,因為在默認配置中會根據(jù) Gateway 配置自動分發(fā)網(wǎng)關(guān) Deployment 和 Service。但是對于高級別的場景可能還是需要去手動部署。
自動部署
默認情況下,每個 Gateway 將自動提供相同名稱的 Service 和 Deployment。如果 Gateway 發(fā)生變化(例如添加了一個新端口),這些配置將會自動更新。這些資源可以通過以下幾種方式進行定義:
- 將 Gateway 上的注解和標簽復(fù)制到 Service 和 Deployment。這就允許配置從上述字段中讀取到的內(nèi)容,如配置內(nèi)部負載均衡器等。Istio 提供了一個額外的注解來配置生成的資源:
- networking.istio.io/service-type:控制 Service.spec.type 字段。例如設(shè)置 ClusterIP 為不對外暴露服務(wù),將會默認為 LoadBalancer。
- 通過配置addresses字段可以顯式設(shè)置Service.spec.loadBalancerIP字段:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: gateway
spec:
addresses:
- value: 192.0.2.0 # 僅能指定一個地址
type: IPAddress
手動部署
如果您不希望使用自動部署,可以進行手動配置 Deployment 和 Service。完成此選項后,您將需要手動將 Gateway 鏈接到 Service,并保持它們的端口配置同步。
要將 Gateway 鏈接到 Service,需要將 addresses 字段配置為指向單個 Hostname。
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: gateway
spec:
addresses:
- value: ingress.istio-gateways.svc.cluster.local
type: Hostname
當然我們這里只是一個最簡單的示例,我們將在后面的課程中繼續(xù)介紹 Gateway API 的更多功能。
配置請求路由
接下來我們來了解下如果通過 Gateway API 將請求動態(tài)路由到微服務(wù)的多個版本。
同樣我們這里以 Bookinfo 示例為例(首先要部署 Bookinfo 應(yīng)用),我們首先將所有流量路由到微服務(wù)的 v1 (版本 1),然后將應(yīng)用規(guī)則根據(jù) HTTP 請求 header 的值路由流量。
首先專門為 Bookinfo 應(yīng)用創(chuàng)建一個 Gateway 資源對象,如下所示:
# bookinfo-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
gatewayClassName: istio
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
上面的 Gateway 資源對象與之前的示例類似,只是這里我們將 allowedRoutes 設(shè)置為 Same,表示允許同一命名空間中的所有路由資源對象都可以連接到這個網(wǎng)關(guān)。
然后為 Productpage 應(yīng)用創(chuàng)建一個 HTTPRoute 資源對象,如下所示:
# productpage-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: bookinfo
spec:
parentRefs:
- name: bookinfo-gateway # 引用上面定義的 Gateway 對象
rules:
- matches:
- path:
type: Exact
value: /productpage
- path:
type: PathPrefix
value: /static
- path:
type: Exact
value: /login
- path:
type: Exact
value: /logout
- path:
type: PathPrefix
value: /api/v1/products
backendRefs:
- name: productpage # 引用的后端服務(wù)
port: 9080
和以前 VirtualService 的類似,我們?yōu)?Productpage 應(yīng)用配置了幾個路由規(guī)則,這樣我們就可以在頁面上正常訪問應(yīng)用了。直接應(yīng)用上面的兩個資源清單文件即可:
kubectl apply -f bookinfo-gateway.yaml
kubectl apply -f productpage-route.yaml
同樣當我們創(chuàng)建了 Gateway 對象后,會自動在 default 命名空間中部署一個 gateway-istio 的 Deployment 和 對應(yīng)的 Service:
$ kubectl get pods -l istio.io/gateway-name=bookinfo-gateway
NAME READY STATUS RESTARTS AGE
bookinfo-gateway-istio-548556df95-kggs2 1/1 Running 0 112s
$ kubectl get svc bookinfo-gateway-istio
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
bookinfo-gateway-istio LoadBalancer 10.111.86.147 <pending> 15021:30357/TCP,80:30749/TCP 2m19s
正常我們就可以通過上面的 30749 這個 NodePort 端口來訪問 Productpage 應(yīng)用了:
bookinfo
路由到版本 1
同樣的頁面上的評論區(qū)域會出現(xiàn) 3 種不同的狀態(tài),因為我們背后有 3 個不同的版本的評論服務(wù),我們可以創(chuàng)建一個如下所示的 HTTPRoute 資源對象,首先將流量路由到 v1 版本:
# route-reviews-v1.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: reviews
spec:
parentRefs: # 這里我們引用的是 reviews 這個 Service 對象
- group: ""
kind: Service
name: reviews
port: 9080
rules:
- backendRefs:
- name: reviews-v1
port: 9080
注意上面的資源對象中我們是通過 parentRefs 字段引用的是 reviews 這個 Service 對象,而不是 Gateway 對象了,因為我們流量并不是從 Gateway 直接過來的,而是通過 Productpage 訪問 reviews 服務(wù),也就是 Service 對象。然后背后的我們會將流量全部路由到 v1 版本的 reviews 服務(wù) reviews-v1。
現(xiàn)在我們再應(yīng)用上面的這個資源對象:
kubectl apply -f route-reviews-v1.yaml
不過這里需要注意不同于 Istio API 使用 DestinationRule 子集來定義服務(wù)的版本, Kubernetes Gateway API 將為此使用后端 Service 服務(wù)來進行定義。所以我們還需要運行以下命令為三個版本的 reviews 服務(wù)創(chuàng)建后端服務(wù)定義:
kubectl apply -f samples/bookinfo/platform/kube/bookinfo-versions.yaml
然后我們可以通過再次刷新 Bookinfo 應(yīng)用程序,現(xiàn)在我們無論刷新多少次,頁面的評論部分都不會顯示評級星標,這是因為我們將 Istio 配置為將評論服務(wù)的所有流量路由到版本 reviews:v1,而此版本的服務(wù)不訪問星級評分服務(wù)。
reviews v1
基于用戶身份路由
接下來我們來更改路由配置,將來自特定用戶的所有流量路由到特定服務(wù)版本,比如將來自名為 Jason 的用戶的所有流量被路由到服務(wù) reviews:v2(包含星級評分功能的版本)。
創(chuàng)建一個如下所示的 HTTPRoute 資源對象:
# route-reviews-jason-v2.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: reviews
spec:
parentRefs:
- group: ""
kind: Service
name: reviews
port: 9080
rules:
- matches:
- headers: # 匹配請求頭 end-user: jason
- name: end-user
value: jason
backendRefs:
- name: reviews-v2
port: 9080
- backendRefs:
- name: reviews-v1
port: 9080
在上面的 HTTPRoute 資源對象中,我們將訪問 reviews 的流量分為兩個規(guī)則,第一個規(guī)則匹配請求頭 end-user: jason,然后將流量路由到 reviews-v2 服務(wù),第二個則是如果不匹配將流量路由到 reviews-v1 服務(wù)。
然后我們再次應(yīng)用這個資源對象:
kubectl apply -f route-reviews-jason-v2.yaml
然后我們在 Bookinfo 頁面上面,可以以用戶 jason 身份進行登錄,登錄后無論如何刷新瀏覽器,我們將始終在頁面上看到黑色的星級評分,也就是 reviews 的 v2 版本。
jason 用于
我們也可以切換成其他用戶,或者不登錄,刷新瀏覽器,那么就不會顯示星級評分了。
基于權(quán)重的路由
接下來我們再來測試下基于權(quán)重的路由,常常我們有將流量從微服務(wù)的一個版本逐步遷移到另一個版本的需求,同樣使用 Gateway API 來實現(xiàn)也非常簡單。
下面我們將會把 50% 的流量發(fā)送到 reviews:v1,另外,50% 的流量發(fā)送到 reviews:v3。接著,再把 100% 的流量發(fā)送到 reviews:v3 來完成遷移。
首先重新運行下面的命令將所有流量路由到 review 服務(wù)的 v1 版本。
kubectl apply -f route-reviews-v1.yaml
接下來創(chuàng)建如下所示的資源對象,把 50% 的流量從 reviews:v1 轉(zhuǎn)移到 reviews:v3:
# route-reviews-50-v3.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: reviews
spec:
parentRefs:
- group: ""
kind: Service
name: reviews
port: 9080
rules:
- backendRefs:
- name: reviews-v1
port: 9080
weight: 50
- name: reviews-v3
port: 9080
weight: 50
上面的對象中在 backendRefs 中我們使用了一個 weight 字段,用于設(shè)置權(quán)重,這里我們將 reviews-v1 和 reviews-v3 的權(quán)重都設(shè)置為 50,也就是說將流量平均分配到這兩個版本的服務(wù)上。
然后我們應(yīng)用這個資源對象即可:
kubectl apply -f route-reviews-50-v3.yaml
等待幾秒鐘,等待新的規(guī)則傳播到代理中生效,然后我們再次刷新瀏覽器中的 productpage 頁面,大約有 50% 的幾率會看到頁面中帶紅色星級的評價內(nèi)容。 這是因為 reviews 的 v3 版本可以訪問帶星級評價,但 v1 版本不能。
如果你認為 reviews:v3 微服務(wù)已經(jīng)穩(wěn)定,那么接下來我們就可以將 100% 的流量路由 reviews:v3:
# route-reviews-v3.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: reviews
spec:
parentRefs:
- group: ""
kind: Service
name: reviews
port: 9080
rules:
- backendRefs:
- name: reviews-v3
port: 9080
然后我們再次應(yīng)用這個資源對象:
kubectl apply -f route-reviews-v3.yaml
現(xiàn)在,當我們訪問 Bookinfo 應(yīng)用時,將始終看到帶有紅色星級評分的書評。
使用 TLS 暴露服務(wù)
接下來我們來看下如何通過 TLS 來暴露服務(wù),這里我們以 httpbin 示例進行說明。
首先要部署 httpbin 示例:
kubectl apply -f samples/httpbin/httpbin.yaml
然后接下來生成客戶端和服務(wù)器證書和密鑰,這里我們使用 openssl 工具來生成。
首先創(chuàng)建用于服務(wù)簽名的根證書和私鑰:
$ mkdir example_certs1
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs1/example.com.key -out example_certs1/example.com.crt
為 httpbin.example.com 創(chuàng)建證書和私鑰:
$ openssl req -out example_certs1/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
$ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 0 -in example_certs1/httpbin.example.com.csr -out example_certs1/httpbin.example.com.crt
然后接下來需要為入口網(wǎng)關(guān)創(chuàng)建 Secret:
kubectl create -n istio-system secret tls httpbin-credential \
--key=example_certs1/httpbin.example.com.key \
--cert=example_certs1/httpbin.example.com.crt
這里我們創(chuàng)建一個獨立的 Kubernetes Gateway:
# httpbin-gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
在上面的對象中我們配置了 HTTPS 協(xié)議,并在 tls 中配置使用 Terminate 模式,也就是說我們將在網(wǎng)關(guān)上終止 TLS 連接,然后在 certificateRefs 中引用了之前創(chuàng)建的 Secret 對象,用于配置網(wǎng)關(guān)的憑據(jù)。最后通過 allowedRoutes 字段配置了允許的路由,這里我們配置的是將流量路由到 default 命名空間中的所有路由資源對象。
接下來,通過定義相應(yīng)的 HTTPRoute 配置網(wǎng)關(guān)的入口流量路由:
# httpbin-route.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httpbin
spec:
parentRefs:
- name: mygateway
namespace: istio-system
hostnames: ["httpbin.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /status
- path:
type: PathPrefix
value: /delay
backendRefs:
- name: httpbin
port: 8000
這里最重要的就是在 parentRefs 字段中引用了之前創(chuàng)建的 Gateway 對象。
然后我們直接應(yīng)用上面的兩個資源對象即可:
kubectl apply -f httpbin-gateway.yaml
kubectl apply -f httpbin-route.yaml
同樣應(yīng)用后會自動在 istio-system 命名空間中部署一個 gateway-istio 的 Deployment 和對應(yīng)的 Service:
$ kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
mygateway-istio-64676bfc88-q8nft 1/1 Running 0 32s
$ kubectl get svc -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mygateway-istio LoadBalancer 10.111.175.36 <pending> 15021:31206/TCP,443:32597/TCP 74s
然后我們可以通過 32597 這個 NodePort 端口向 httpbin 服務(wù)發(fā)送 HTTPS 請求:
$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:32597:192.168.0.100" --cacert example_certs1/example.com.crt "https://httpbin.example.com:32597/status/418"
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* CAfile: example_certs1/example.com.crt
CApath: none
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* subject: O=httpbin organization,CN=httpbin.example.com
* start date: Dec 22 07:13:47 2023 GMT
* expire date: Dec 21 07:13:47 2024 GMT
* common name: httpbin.example.com
* issuer: CN=example.com,O=example Inc.
# ......
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
* Connection #0 to host httpbin.example.com left intact
正常我們就可以看到輸出一個茶壺,這樣我們完成了通過 TLS 來暴露服務(wù)。
TCP 路由
除了 HTTP 路由外,Gateway API 還支持 TCP 和 UDP 路由,配置 TCP 的路由規(guī)則,需要使用單獨的資源對象 TCPRoute
,如下所示:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: tcp-echo-gateway
spec:
gatewayClassName: istio
listeners:
- name: tcp-31400
protocol: TCP
port: 31400
allowedRoutes: # 只允許 TCPRoute 資源對象連接到這個網(wǎng)關(guān)
kinds:
- kind: TCPRoute
---
apiVersion: v1
kind: Service
metadata:
name: tcp-echo-v1
spec:
ports:
- port: 9000
name: tcp
selector:
app: tcp-echo
version: v1
---
apiVersion: v1
kind: Service
metadata:
name: tcp-echo-v2
spec:
ports:
- port: 9000
name: tcp
selector:
app: tcp-echo
version: v2
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TCPRoute # TCPRoute 資源對象
metadata:
name: tcp-echo
spec:
parentRefs: # 引用定義的 Gateway 對象
- name: tcp-echo-gateway
sectionName: tcp-31400
rules:
- backendRefs:
- name: tcp-echo-v1
port: 9000
weight: 80
- name: tcp-echo-v2
port: 9000
weight: 20
其他使用
其他的流量管理比如故障注入、熔斷這些,Gateway API 尚不支持。
但是支持請求超時,比如對 reviews 服務(wù)的調(diào)用增加一個半秒的請求超時,可以使用下面的資源對象:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: reviews
spec:
parentRefs:
- group: ""
kind: Service
name: reviews
port: 9080
rules:
- backendRefs:
- name: reviews-v2
port: 9080
timeouts:
request: 500ms
上面的對象中我們添加了一個 timeouts 字段,用于設(shè)置請求超時時間。
同樣還支持流量鏡像,如下的資源對象:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httpbin
spec:
parentRefs:
- group: ""
kind: Service
name: httpbin
port: 8000
rules:
- filters:
- type: RequestMirror
requestMirror:
backendRef:
name: httpbin-v2
port: 80
backendRefs:
- name: httpbin-v1
port: 80
在上面的資源對象中我們添加了一個 RequestMirror 過濾器,該過濾器用于將流量鏡像到另外的服務(wù)上去。