斷路器和艙壁模式
在微服務架構中,有兩個重要的模式,它們能夠讓服務實現自愈的效果。
斷路器模式(Circuit Breake)能夠阻止請求發送到不健康的服務實例上,這樣的話,服務能夠進行恢復,同時,客戶端的請求將會轉發到服務的健康實例上(增加了成功率)。
艙壁模式會隔離失敗,避免整個系統宕機,舉例來說,服務B處於有問題的狀態,另外一個服務(服務B的客戶端)往服務B發送請求將會導致客戶端耗盡線程池,從而不能爲其他請求提供服務(即便其他的請求與服務B毫無關係也是如此)。
這裏我不再展示這些模式的實現了,因爲我迫不及待想要分享認證和授權的功能,這些模式的實現你可以參考官方文檔。
Istio中的認證和授權
我從來沒有想到有一天我會對認證和授權感到如此興奮。在技術領域,Istio到底做了什麼能夠讓我對這樣恐怖的話題感到興奮呢,更重要的是它爲什麼能夠讓你也爲此感到興奮呢?
這是因爲,Istio將這些責任從我們的服務中剝離了出去,並將其委託給了Envoy代理,這意味着當請求抵達我們的服務時,它們已經經過了認證和授權,我們只需要編寫提供業務價值的代碼就可以了。
讓我們來深入瞭解一下!
使用Auth0進行認證
我們會使用Auth0作爲身份識別和訪問管理的服務器,它有一個試用方案,對我來說非常便利,我非常喜歡它!這也就是說,相同的理念可以用於任意的OpenID Connect實現,比如KeyCloak、IdentityServer等等。
首先,使用預設賬號導航至Auth0 Portal,在Applications > Default App下創建一個租戶,並記住Domain,如下所示:
更新resource-manifests/istio/security/auth-policy.yaml
文件,以便於使用該Domain:
apiVersion: authentication.istio.io/v1alpha1
kind: Policy
metadata:
name: auth-policy
spec:
targets:
- name: sa-web-app
- name: sa-feedback
origins:
- jwt:
issuer: "https://{YOUR_DOMAIN}/"
jwksUri: "https://{YOUR_DOMAIN}/.well-known/jwks.json"
principalBinding: USE_ORIGIN
藉助該資源,Pilot將會配置envoy在將請求轉發至sa-web-app和sa-feedback服務之前進行認證。同時,它不會將該規則應用於服務sa-frontend
的envoy,這樣的話,前端能夠不用進行認證。爲了應用該策略,我們需要執行如下命令:
$ kubectl apply -f resource-manifests/istio/security/auth-policy.yaml
policy.authentication.istio.io "auth-policy" created
回到頁面併發送一個請求,我們將會看到401 Unauthorized,現在,我們讓來自前端的用戶使用Auth0進行認證。
使用Auth0認證請求
要認證終端用戶的請求,我們需要在Auth0中創建一個API,它代表了經過認證的服務,比如評論、詳情和評級。爲了創建這樣的API,我們需要導航至Auth0 Portal > APIs > Create API,如下圖所示:
這裏重要的信息是Identifier,在隨後的腳本中,我們會以如下的形式進行使用:
- Audience: {YOUR_AUDIENCE}
其餘所需的細節在Auth0 Portal的Applications下進行配置,選擇自動創建的Test Application,其名稱與API相同。
這裏需要注意:
- Domain: {YOUR_DOMAIN}
- Client Id: {YOUR_CLIENT_ID}
在Test Application中向下滾動至Allowed Callback URLs文本域,在這裏我們可以指定在認證完成後要轉發到哪個URL,在我們的場景中,應該是:
http://{EXTERNAL_IP}/callback
在Allowed Logout URLs中添加如下的URL:
http://{EXTERNAL_IP}/logout
接下來,我們更新前端。
更新前端
切換至倉庫的auth0分支。在這個分支中,前端包含了將用戶轉發至Auth0進行認證的代碼變更,並在發送至其他服務的請求中包含了JWT Token,如下所示:
analyzeSentence() {
fetch('/sentiment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${auth.getAccessToken()}` // Access Token
},
body: JSON.stringify({ sentence: this.textField.getValue() })
})
.then(response => response.json())
.then(data => this.setState(data));
}
爲了更新前端以便於使用租戶的詳細信息,我們導航至sa-frontend/src/services/Auth.js
文件並替換如下的值,也就是在前文中我提醒注意的地方:
const Config = {
clientID: '{YOUR_CLIENT_ID}',
domain:'{YOUR_DOMAIN}',
audience: '{YOUR_AUDIENCE}',
ingressIP: '{EXTERNAL_IP}' // Used to redirect after authentication
}
應用已經就緒了,在如下的命令指定docker user id,然後構建和部署變更:
$ docker build -f sa-frontend/Dockerfile \
-t {DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0 sa-frontend
$ docker push {DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0
$ kubectl set image deployment/sa-frontend \
sa-frontend={DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0
我們可以嘗試運行一下應用。訪問的時候,你會被導航至Auth0,在這裏必要要登錄(或註冊),再次之後會重新轉發會頁面,這時候就可以發送認證後的請求了。如果你使用之前的curl命令的話,將會遇到401狀態碼,表明請求沒有經過授權。
接下來,我們更進一步,對請求進行授權。
使用Auth0進行授權
認證能夠讓我們知道用戶是誰,但是我們需要授權信息才能得知他們能夠訪問哪些內容。Istio也提供了這樣的工具。
作爲樣例,我們會創建兩組用戶(如圖24所示):
- Users:只能訪問SA-WebApp和SA-Frontend服務;
- Moderators:能夠訪問所有的三個服務。
爲了創建用戶組,我們需要使用Auth0 Authorization擴展,隨後藉助Istio,我們爲它們提供不同級別的訪問權限。
安裝和配置Auth0授權
在Auth0 Portal中,切換至Extensions,並安裝“Auth0 Authorization”擴展。安裝之後,切換至Authorization Extension並點擊右上角你的租戶並選擇“Configuration”選項進行配置。啓用組並點擊“Publish rule”按鈕。
創建Group
在Authorization Extension中,導航至Groups並創建Moderators組。我們會將所有已認證過的用戶視爲普通的User,這樣的話,就沒有必要額外再建立一個組了。
選擇Moderators組並點擊Add Members,然後添加你的主賬號。請將一些用戶不放到任何的組中,以便於檢驗禁止他們訪問(你可以通過Auth0 Portal > Users > Create User手動註冊新用戶)。
添加Group Claim到Access Token
用戶現在已經添加到組中了,但是這些信息還沒有反映到Access Token中。爲了確保OpenID Connect的一致性,同時又能返回組,我們需要在token中添加自定義的命名空間聲明。這可以通過Auth0規則來實現。
要在Auth0 Portal中創建規則,我們需要導航至Rules,點擊“Create Rule”,並從模板中選擇一個empty rule。
粘貼如下的代碼並將規則命名爲“Add Group Claim”。
function (user, context, callback) {
context.accessToken['https://sa.io/group'] = user.groups[0];
return callback(null, user, context);
}
注意: 這個規則會獲取Authorization Extension中定義的第一個用戶組,並將其添加到access token中,作爲自定義命名空間的聲明。
回到Rules頁面,確保我們按照如下順序有了兩個角色:
- auth0-authorization-extension
- Add Group Claim
這裏的順序是非常重要的,因爲組的字段會由auth0-authorization-extension
異步獲取,然後由第二個規則將其添加爲命名空間聲明,這將會形成如下的access token:
{
"https://sa.io/group": "Moderators",
"iss": "https://bookinfo.eu.auth0.com/",
"sub": "google-oauth2|196405271625531691872"
// [shortened for brevity]
}
現在,我們必須配置Envoy代理,從而能夠在返回的access token中抽取https://sa.io/group
聲明的組。這是下一部分的主題,我們馬上來看一下。
在Istio中配置授權
爲了實現授權,我們爲Istio啓用RBAC。爲了實現這一點,要將如下的配置用到Mesh中:
apiVersion: "rbac.istio.io/v1alpha1"
kind: RbacConfig
metadata:
name: default
spec:
mode: 'ON_WITH_INCLUSION' # 1
inclusion:
services: # 2
- "sa-frontend.default.svc.cluster.local"
- "sa-web-app.default.svc.cluster.local"
- "sa-feedback.default.svc.cluster.local"
- 僅爲“Inclusion”字段中的服務和命名空間啓用RBAC;
- 包含如下服務。
執行如下命令,應用該配置:
$ kubectl apply -f resource-manifests\istio\security\enable-rbac.yaml
rbacconfig.rbac.istio.io "default" created
現在,所有的服務都需要基於角色的訪問控制(Role-Based Access Control),換句話說,訪問所有的服務都會遭到拒絕並且響應中會包含“RBAC: access denied”。我們下一節的主題就是啓用對授權用戶的訪問。
配置常規的用戶訪問
所有的用戶應該都能訪問SA-Frontend和SA-WebApp服務,這是通過如下的Istio資源實現的:
- ServiceRole:指定用戶具有的權限;
- ServiceRoleBinding:指定ServiceRole應用到哪些用戶上。
對於常規用戶,我們允許訪問指定的服務:
apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRole
metadata:
name: regular-user
namespace: default
spec:
rules:
- services:
- "sa-frontend.default.svc.cluster.local"
- "sa-web-app.default.svc.cluster.local"
paths: ["*"]
methods: ["*"]
然後,藉助regular-user-binding,我們將該ServiceRole應用到頁面的所有訪問者上:
apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRoleBinding
metadata:
name: regular-user-binding
namespace: default
spec:
subjects:
- user: "*"
roleRef:
kind: ServiceRole
name: "regular-user"
這裏所說的所有用戶,是不是意味着未認證的用戶也能訪問SA WebApp呢?並非如此,該策略依然會檢查JWT token的合法性。
通過如下命令,應用該配置:
$ kubectl apply -f resource-manifests/istio/security/user-role.yaml
servicerole.rbac.istio.io/regular-user created
servicerolebinding.rbac.istio.io/regular-user-binding created
配置Moderator用戶訪問
對於Moderator,我們想要啓用對所有服務的訪問:
apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRole
metadata:
name: mod-user
namespace: default
spec:
rules:
- services: ["*"]
paths: ["*"]
methods: ["*"]
但是,我們只想要綁定Access Token中https://sa.io/group
聲明的值爲Moderators的用戶。
apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRoleBinding
metadata:
name: mod-user-binding
namespace: default
spec:
subjects:
- properties:
request.auth.claims[https://sa.io/group]: "Moderators"
roleRef:
kind: ServiceRole
name: "mod-user"
要應用該配置,需要執行下述命令:
$ kubectl apply -f resource-manifests/istio/security/mod-role.yaml
servicerole.rbac.istio.io/mod-user unchanged servicerolebinding.rbac.istio.io/mod-user-binding unchanged
Envoy中會有緩存,所以授權規則可能需要幾分鐘之後才能生效,在此之後,我們就可以校驗Users和Moderators會有不同級別的訪問權限。
坦白講,你見過像這樣毫不費力就能構建可擴展的認證和授權嗎,還有比這更簡單的過程嗎?反正我是沒有見過!接下來,我們總結一下得到的結論。
結論
想象不到吧?我們將其稱爲怪獸(Beast-io),因爲它確實太強大了。藉助它,我們能夠:
- 實現服務的可觀察性,藉助Kiali,我們能夠回答哪個服務在運行、它們的表現如何以及它們是如何關聯在一起的;
- 藉助Prometheus和Grafana,能夠實現指標收集和可視化,完全是開箱即用的;
- 使用Jaeger(德語中獵人的意思)實現請求跟蹤;
- 對網絡流量的完整且細粒度控制,能夠實現金絲雀部署、A/B測試和影子鏡像;
- 很容易實現重試、超時和斷路器;
- 極大地減少了爲集羣引入新服務的開銷;
- 爲各種語言編寫的微服務添加了認證和授權功能,服務端代碼無需任何修改。
有個這個Beastio,我們就能讓團隊真正地提供業務價值,並將資源聚焦在領域問題上,不用再擔心服務的開銷,讓服務真正做到“微型化”。
我是Rinor Maloku,非常感謝Istio之旅。你可以在Twitter 關注我,也可以訪問我的主頁 rinormaloku.com。