code

2022年3月8日 星期二

Cloud Native - Gateways

 問題:

client要怎麼知道你的各個服務的endpoints是什麼?或是更新後的endpoints?

API Gateway 主要是routing traffic到該去的service endpoints / expose API / tasks (SSL)。此外API gateway可以有多個layer topology,某些前面的gateway可以做traffic offloading / SSL,下一層的gatewau可以做身份認證或是真的導向service endpoints。


Routing

API gateway扮演reverse proxy角色,將requests route到後端service (這也就是reverse proxy定義)。


這個pattern的問題在於,gateway變成single point of failure + bottleneck! 或許可以使用雲gateway服務。


Aggregation

gateway也可以扮演aggregator角色,將多個backend service的response集合起來變成一個返回client端的response:


這個pattern的風險除了一樣bottleneck之外,另外就是不要讓gateway以及這些services之間形成高耦合狀態,還有aggregation也會對gateway帶來額外的CPU load。好處是在client端的,只需要一個request就可以使用多個service。要是aggregation的load或是邏輯太大,會讓gateway變成monolithic,此時最好(以microservices觀點)可以拆出另一個aggregation service:



Offloading

gateway可以把某些不屬於個別service該做的load接手過去做,例如SSL termination,額外的好處是讓SSL certificate這樣的安全性資產可以分散。

這類的load最好是所有service共有的額外項目才放到gateway去做,稱為cross-cutting concerns,例如:

- 安全性認證與授權

- Rate limiting / retry policy / circuit breaking (?!)

- Caching

- Compression

- SSL offloading

- logging & monitoring


實作gateway

最受歡迎的:NGINX / HAProxy / Envoy,都提供了reverse proxy / load balancing / SSL / routing,雲端服務的話有 Azure Application Gateway / Azure Frontdoor / Amazon API Gateway。


Egress gateway

上面講的是ingress gateway,將外部流量往內導向該去的地方。

相對的有一個egress gateway會將內部流量導離自己的private network,來存取外部資源,e.g. 使用一個egress gateway來對某些outbound流量做安全性的blocking動作(可以阻擋不安全往外流量)。總之當你需要monitor outbound requests時,可以用Istio這類的egress gateway。



2022年3月7日 星期一

Cloud Native - Service Communication

 At high level

1. external communication (North-South traffic) - 不在同一個K8s cluster之中的service之間的通訊,使用K8s 的exgress controller

2. internal communication (East-West traffic) - 在同一個K8s cluster之中的service之間的通訊,使用K8s 的ingress controller


Protocols

HTTP是最常使用但不見得是最好效能者,優化效能的話,可以透過proxy轉換protocol:



1. Websockets - 適合雙向長時間,透過單一TCP socket建立全雙工低overhead的連線。首先透過HTTP request要求建立WebSocket連線。低延遲也是一個特點。

2. HTTP/2 - binary data protocol,特別適合低延遲高效率的串流通訊。

3. gRPC - 建立在HTTP/2上的lightweight transport protocol,在微服務社群中逐漸受到歡迎,提供包括身份驗證 / 雙向streaming / flow control / blocking bindings / cancellation & timeouts。


Messaging Protocols

cloud native天生使用event-driven / message-based approach,所以我們來看一下兩個主要的protocols:

1. MQTT (Message Queue Telemetry Transport)

通常用在IoT / 機器間 的binary protocol,例如在low-bandwidth環境下的sensor跟gateway的溝通。非常lightweight的publisher/subscriber的scheme。


2. AMQP (Advanced Message Queuing Protocol)

MQTT的rich feature set版本,所以相對不是那麼lightweight跟快速,但是也被許多vendor驗證過是很可靠的,所以在不同vendor的service間溝通可以使用AMQP。


Serialization Considerations

data serialization也是影響效能的一個可能。

1. JSON - 最常使用,但是有較大的memory footprint,所以也會花費計算在serialization / deserialization較大。JSON serializer的選擇就很關乎效能。

2. Protobuf - 由於是binary format,所以效能較佳,幾乎所有現代語言都有相對應的generator。message schema會預先定義在某個proto file,所以每個service預先會有此proto file才知道如何serialize/deserialize data。


Idempotency (冪等性)

service對任意的request每次都能產出一樣結果的話,稱為idempotency,一個可能發生不idempotency的場景是,某個API因為網路不穩而重新送了兩次request,造成service端重複process了同樣的data兩次,若是不符合idempotency的API有可能會造成錯誤。常見的解決方法是加上unique request ID,所以已經process過的ID就不會再process,避免了non-idempotency的可能,也稱為de-duping。


Request/Response

synchronous pattern就沒什麼好說的,一來一往等待:


asynchronous pattern的話,就要確保request方知道目前的response是對哪一個request的回應,這可以用message queue + correlation id (CID) 來達成:



publisher/subscriber

cloud native app中最常見的async communication pattern,多個subscriber訂閱某個topic,當publisher push message時,中間角色的broker對所有subscriber廣播出去。

- pub / sub 是完全獨立解耦合的

- event driven design


注意事項

1. async communication不保證接收到訊息的順序,所以需要注意idempotency issue

2. 如果care接收到訊息的順序,可以善用messaging system的內建ordering functionality或是使用priority queue pattern


要選擇Pub/Sub or Request/Response?

case by case依照application需求而定,例如以下為同一個cloud native app採用兩種approach:


data flow是 S1收到client async request後,處理完後又對S2 / S3也發出request。S3處理完後,向S4 / S5發出request。

Request/Response - S3要等待S4/S5處理完才能respond to S1,有可能因為fail而timeout,但是client端可能要很久之後才發現fail,而且必須處理idempotency,免得客戶多發了很多次request造成資料異常。換言之,request/response對每個參與的service來說是高度耦合的。


Pub/Sub - 較低耦合度,透過中間pub/sub系統例如 Redis/RabbitMQ/Apache Kafka。


Synchronous vs Asynchronous

所有的service instance基本上就是一個process,彼此間的溝通就是IPC (interprocess communication)機制,用synchronous communication有主要三個問題:

1. scale up時會因為等待response而被block住,容易產生資源瓶頸

2. response延遲時間是喔死有節點的延遲加總,所以其中一個慢,會讓client端的整體response latency高。

3. 錯誤累積,這類似2. 的問題。


所以cloud native app基本上是採用asynchronous communication。


2022年3月5日 星期六

Cloud Native - API Design & Versioning

API Versioning

服務間依賴API溝通,但API versioning是一件不容易的事,有以下三種常見的approaches:

1. the knot - API版本永遠不更改(single version),所以每次API更動,所有的clients都要更動

2. point-to-point - API提供者提供不同版本的API,client自己採用需要的版本,但是對API提供者來說維護成本高。

3. compatible versioning - API版本永遠不更改(single version),但是向下相容,也是對API提供者較高的開發跟維護成本。

compatible versioning根據研究,會有較高的效率。

REST規格本身並沒有提供versioning規範,一般採用subdomain或是url path方式來區分不同version,例如

api-v1.test.com/users

./api/v2/users

另一種方式是mime-based approach,透過對header的resource version/type來標記API version,例如 (Accept: application/vnd.test.users.v2+json),但是API url path並不會改變。


Backward Compatibility

對於採用compatible versioning approach來說,



上圖中,如果Service B v2.0要達成向下相容,代表他要能跟Service A v1.0能完全溝通,因為service A並不會有任何異動。要注意以下best practice來達成:

1. 新的API提供default values或是optional values,如果無法達成則應該要開新的API uri

2. 絕對不要重新命名或是移除現有的參數

3. 標記沒在使用的API endpoint obsolete

4. 同時測試新舊version API,傳舊的message data給兩者。

5. forward compatibility: 設計API時考慮未來相容性的話,implementation要能ignore新的參數,並不會出現任何錯誤!


Semantic Versioning

versioning的命名應該要有語意,讓人們能了解版本的差異 (major.minor.patch):

major - 當不相容的定義出現時,要增加major version

minor - 當向下相容的功能出現時,要增加minor version

patch - 當向下相容的bug fixes






2022年1月21日 星期五

Cloud Native - Designing Cloud Native Applications

 General Guides

每個服務商(e.g. Azure / AWS / GCP ...) 都有自己的cloud patterns guide,可以generalize成以下五點:

1. operational excellence - 在設計cloud native app的時候就要考慮維運與開發的因子,透過DevOps的practice來實踐:

a. 所有東西都要自動化 - 減少人為錯誤到最低程度,配合上Infrastructure as Code (IaC),整個環境設定都是程式控制。

b. 所有東西都要監控 - 對如何持續改善有幫助

c. 自動化文件化所有東西 - 透過Swagger這類tool

d. 所有動作都是reversible - 所以IaC是很重要的

e. 設計錯誤時的處理 - gracefully fail

2. 資安 - 目前普遍接受的說法是cloud application比地端要安全,但還是要特別著墨,採用defense-in-depth的approach,在設計架構時就把資安納入考量。以下是一個voting cloud app:


此app採用的defense-in-depth的方向如下(假設使用container + K8s):

 - 採用安全的code repository + CI整合靜態分析code安全性

 - container image: 永遠只加入與expose最低限度需要的東西

- container registry: 使用private registry來存放image,需要有權限控管,以及安全性掃描image例如使用Twistlock

- Pod: container images只能從被許可的registry pull後來,每個pod要有identity才能讓其中的程式存取其他的service,再來要考慮node-node間如何安全的通訊。

- Cluster and orchestrator: cluster要放在內網還是外網,以及要把Kubernetes RBAC enabled。

3. Reliability vs Availability

reliability - application能持續運作,即便存在某些錯誤。所以application要能被設計成即便發生錯誤持續使用之外,還要能恢復正常。

availability - application能被使用在一段持續的時間內,例如多個redundant instances or replicas。


4. Scalability and cost

1. 如果不自動決定何時該scale up,則需要在一開始就支付某個規模程度的cluster of nodes,則不是很cost-effective,因為多數時間可能都資源遠大於需求

2. 如果採取自動scale up當資源不足時,則會被啟動new node (VM)的速度遠慢於啟動container的速度所困擾,這需要考慮進去。可以考慮Container as a Service (CaaS)包含Azure Kubernetes Service virtual node,或是AWS Fargate。


Cloud Native vs 傳統架構

差異包括:

1. state處理 - state包含session / application configuration等,傳統架構通常是stateful的,通常暫存放於compute instance variables,所以需要load balancer透過sticky session的方式來確保同一個session都是連到同一個compute instance。


上圖中,LB發現VM0死掉的話,會將user 導向VM1,但是原本的session data將會遺失,造成問題。

而cloud native由於compute instances是動態增減的,所以在設計之初就會朝向stateless的方向去設計,通常session data是被外部化的存放於data store,不隨著compute instance的增減被影響。如下圖:


2. orchestration - 傳統架構通常使用service orchestration service來處理request,而且通常是synchronous等待的:


這樣synchronous waiting當然就有他的問題

cloud native通常採用event-driven pattern來溝通,把一個request分給多個低耦合的服務來執行稱為service choreography,每個service maintain自己的state:


上圖紅色方塊就是event-driven communication component,整個流程就是所謂的service choreography。


Functions vs Services

在設計一個application之初,就會面臨要選擇一個容器化的service,還是要走FaaS?

FaaS雖然比較適合短生命週期跟獨立的tasks,但目前許多FaaS已經成熟到能讓整個application都建立在FaaS上面(e.g. Azure Durable Functions or AWS Step Functions)。FaaS比較大的限制是執行時間timeout短(不能執行長時間運算的東西)

FaaS適合以下scenarios: 平行化獨立事件 / 排程 / map-reduce / message routing。

使用FaaS也有以下要考慮的問題:

1. 一個monolithic切分成小function的複雜度

2. 有限的執行生命週期

3. 目前尚無(人)法用特異化的硬體,例如GPU

4. 由於每個function沒有state,所以所有計算都要pass in data,並且pass out data,會有較多節點的溝通,形成網路上的延遲

5. local開發不一定能成立,因為FaaS不一定有local開發環境

6. FaaS不一定比較便宜,因為節省了compute instance cost,但是又加入了networking / storage / eventing等supporting services費用。


容器化的服務沒有以上的問題,所以可以混合使用services + FaaS來達成彈性。之前提到的voting application可以在某些component使用FaaS:



這個FaaS component只負責將event發生時的某些條件,變成data放入data store,非常方便去scale up。但是要注意FaaS scales up的時候,瓶頸會發生在與他對接的系統中,例如圖中的data store,所以好的practice是data store也採用managed services (e.g. AWS RDBMS)。

此外,你應該要知道自己的application scale的特性,例如container image大的話,根本就來不及在高峰期scale up,或是你的container雖然冷啟動很快,但是她依賴的相關的service要scale up的冷啟動時間很長,也會使得高峰期(busrt scenarios)的scale up失敗。


2022年1月20日 星期四

Cloud Native - Microservices

 Microservices Architecture

1. application被拆分成低耦合的多個小服務
2. 根據business / functionality 來區分服務範圍
3. 由不同的獨立team維運開發
4. services彼此間跑不同process,透過API溝通

monolithic arch 是微服務的對立面,有以下特徵:

1. single code base
2. shared data store / data structure

 


優點

1. 敏捷度 - 由於每個更動都侷限在某個小code base,部署/測試/開發 速度都變快
2. 持續創新 - ? 我覺得還好
3. evolutionary design - 由於影響範圍小,可以快速做技術更新
4. 小而美的team
5. 侷限性錯誤
6. 每個service可以有不同的scaling

挑戰

1. 複雜度提高 - 分散式系統的所有問題都會出現
2. data 完整度與consistency - 因為data store也是分散式的,所以也是會遇到分散式系統的問題
3. 效能 - API calls會增加networking overhead,需要使用某些design patterns
4. 服務版控 - 每個微服務都有自己的版本,在跟其他服務整合時,版本控管複雜度增加
5. logging/monitoring複雜度增加
6. service discovery - 所有component都是API溝通的微服務,需要好的方法來找到彼此