1. 程式人生 > >一篇文章對Istio 0.8.0 做個簡單概括

一篇文章對Istio 0.8.0 做個簡單概括

首先我們來看一下Istio大概是個什麼東西呢?

我們經常聽到的,和它繫結的詞彙就是service mesh,但是如果又要追溯service mesh,然後看的越來越多,搞得自己都迷糊了。所以,我們就看Istio。

Istio是架構與Kubernetes之上的一個服務治理架構,我們可以看一下它在官網上的架構圖。
這裡寫圖片描述

Istio在邏輯上分為資料平面控制平面。可以在圖上看到控制平面,它負責了路由,策略配置,收集資訊,身份認證等多方面的功能;而資料平面就是下方的,pod集合,每個pod中除了我們部署的服務容器外,還包含一個proxy容器,它是由Envoy實現的,控制層的那些功能的實現,都要依託於它。

接下來,我們不扯那些概念,理念,開始的時候,看的越多越懵。我們從實操開始,一點一點講起。在進行實操之前,首先你需要有一定的Kubernetes的知識儲備,不然後面的內容仍然是雲裡霧裡。這裡只講Istio,所以如果沒有Kubernetes的相關知識,自己去補。

專案部署

首先,我們需要做什麼呢?當然是啟動服務啊,連服務都啟動不了,要它何用?
所以我們就從一個簡單服務啟動開始。我們需要一個YAML檔案,用於定義k8s中的資源。
我們先寫一個最簡單的,部署單一服務。

apiVersion: v1
kind: Service
metadata:
  name: route-guide
labels: app: route-guide spec: ports: - name: http port: 8848 selector: app: route-guide --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: route-guide-v1 spec: replicas: 1 template: metadata: labels: app: route-guide version: v1 spec:
containers: - image: yubotao/route-guide:v1 imagePullPolicy: IfNotPresent name: route-guide ports: - containerPort: 8848 ---

上面便是一個簡單的部署模板,其中的Deployment是k8s中的概念,用於控制pod;Service也是k8s的概念,用於代表後端的pod,是一個抽象,方便服務間的呼叫。需要注意的是,Deployment中的containerPort和Service中的port需要一致。Deployment中的replicas就是用於控制pod的數量的。

有了部署模板後,我們需要使用k8s命令及istio命令進行手動部署,如下:

kubectl apply -f <(istioctl kube-inject -f <filename>) [-n <namespace>]

上面的命令含義如下,<>中的內容是替換內容,[]中的內容是可選內容,第一個 -f 後的 < 是檔案傳輸的命令,-f 引數表示使用的是檔案;-n 引數表示名稱空間;kubectl 為k8s的控制命令,istioctl 為istio的控制命令;kube-inject 引數是istio對我們的k8s部署模板檔案加入了istio需要的相當部分配置,可以通過 istioctl kube-inject -f <filename> > <anotherfile> 命令,來對比<filename><anothername>中的內容,來觀察istio添加了什麼。

新建完成後,我們需要看一下pod的執行狀態,保證它們是Running,正常運作。
使用命令 kubectl get pod [-n <namespace>] 來檢視。

到此,我們就部署完pod和服務了,服務之間的互相呼叫,在同一名稱空間下,使用serviceName:port的形式即可訪問,不同名稱空間下,通過serviceName.namespace:port來訪問服務。

可以說,到這裡,我們就完成了最基本的功能實現了,因為istio自帶的服務註冊和發現功能讓我們可以實現上面的功能。

流量管理

首先是流量管理,這也是service mesh,或者說istio最值得稱道的地方了。
首先需要介紹的就是外部如何訪問istio內部的服務。因為istio的內部與外部是隔離的,所以說我們需要一個門,讓流量進來,這個門就是gateway。

在討論gateway之前,我們需要說一下,istio有關路由管理的四種類型模板:VirtualServiceDestinationRuleGatewayServiceEntry
這裡我分別簡單的介紹一下。
VirtualService:定義了在istio服務網格中控制如何路由到一個服務的請求的規則。
DestinationRule:配置了適用於VirtualService路由發生後的請求策略,由服務所有者撰寫。
Gateway:描述了一個執行在網格邊緣的負載均衡器,用於接受傳入或傳出的HTTP/TCP連線。
ServiceEntry:允許向Istio的內部服務登錄檔中新增其他條目,以便網格中自動發現的服務可以訪問/路由到這些手動指定的服務。

可能你看了上面的說明還是看不懂,我們還是先來看例子,邊看邊說。
首先我們回到上文,看一個gateway的模板定義。

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: route-gateway
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: route-virtualservice
spec:
  hosts:
  - "*"
  gateways:
  - route-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        port:
          number: 8848
        host: route-guide
---

我們看到這裡gateway和VirtualService搭配使用。首先,在VirtualService中的gateways對應了Gateway的name;其次兩者的hosts也對應(目前來看,直接寫”*”是最簡單方便地);最後,VirtualService中地http定義了匹配字首,及路由到的後端服務。這個匹配字首需要多提一嘴,就是改字首必須要在後端服務中,比如prefix值為/has/something/,那麼,後端服務中就必須要有相應的url何其匹配,且該gateway只能路由到這個路徑上;另外,gateway目前只能存在一個,即使不同名稱空間,也只能在存在一個,如果出現多個,則最早宣告的生效,其餘不生效,不知道後續是否會修復這個bug。

講到這裡,應該對VirtualService有個直觀的認識了,就是之前的那句話,它控制如何路由到一個服務。

現在我們看到外部訪問叢集內部,那內部互相訪問怎麼搞呢?
這個我不說,你可以翻到前面去看看。

接下來我們看看如何從叢集內部訪問外部。比如我們的服務啟動的時候都要連線外部的rds,如果不能從內部訪問外部服務,我們的服務就無法啟動。

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: rds-entry
spec:
  hosts:
  - rds地址
  ports:
  - number: 3306
    name: rds
    protocol: TCP

之前我們提到,使用ServiceEntry可以訪問外部服務,正如我們上面定義的。其中需要注意的地方是hosts填寫rds的外部訪問地址,域名或者ip(ip沒有測試,猜測可以),number對應port埠,其中的protocol需要注意,因為mysql傳輸時TCP連線,所以協議用的時tcp,如果連線的外部服務用的時HTTP,需要注意修改。

接下來,我們看一下如何路由流量,以及關於服務請求的一些管理。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: istio-route
spec:
  hosts:
    - istio-route
  http:
  - route:
    - destination:
        host: istio-route
        subset: v1
      weight: 50
    - destination:
        host: istio-route
        subset: v2
      weight: 50
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: istio-route
spec:
  host: istio-route
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---

我們可以看到這是一個版本管理的路由選擇,可以將請求平均分配給兩個版本服務。

這裡我們可以看到DestinationRule的作用,正如它的名字,表示目的地。也就是說VirtualService中的
destination:subset 和 DestinationRule 中 subsets 關聯,而DestinationRule 中 subsets的version,就和Deployment建立聯絡了。

此外,我們可以模擬網路延遲

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: istio-route
spec:
  hosts:
  - istio-route
  http:
  - fault:
      delay:
        percent: 100
        fixedDelay: 2s
    route:
    - destination:
        host: istio-route
        subset: v1

這表示,對於istio-route的請求,100%延遲2s,可以用於模擬網路波動。

當然我們還可以設定熔斷,不過在這裡就不展開了。

安全策略

講到安全,我們需要說的內容就有點多了。首先你需要了解RBAC,然後還要知道k8s中的相關概念。這裡還是去繁就簡。

我們只看兩個方面,一個是服務間開啟互相TLS認證,另一個就是RBAC策略。

首先說TLSTransport Layer Security(安全傳輸層協議)
當我們開啟相互TLS認證的時候,服務到服務之間的通訊都經過了加密。而對應的金鑰/證書是放在Envoy構成的Istio proxy中,由架構中的Citadel完成加解密等。

我們在安裝istio的時候,可以以兩種方式進行安裝,一種是預設沒有TLS認證的;另一種是預設帶有TLS認證的。

首先我們看一下,如果使用沒有TLS認證的安裝方式,我們需要開啟服務間互相TLS訪問應該怎麼做:

  • 我們首先需要一個策略;
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
  name: "example-1"
  namespace: "foo"
spec:
  peers:
  - mtls:
  • 接下來,需要一個開啟互相TLS的目標規則;
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
  name: "example-1"
  namespace: "foo"
spec:
  host: "*.foo.svc.cluster.local"
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL

我們稍微解釋一下,上面的策略表示的是,我們在foo名稱空間定義了一個名稱空間級的策略,但是這個策略是幹什麼用的,就需要我們定義一個相應的目標規則(注意該名稱空間下如果由其他目標規則,則需要進行相應的調整,儘量保證只有一個);下面的目標規則表示,我們在名稱空間foo中的所有服務,都開啟了互相TLS認證機制,也即其他名稱空間的服務向foo中的服務傳送請求時,需要進行TLS認證。

那如果我們想要取消互相TLS認證時(針對預設為開啟TLS的安裝模式,否則直接刪除對應規則和策略即可;另一種情況是,使用服務級的策略覆蓋名稱空間級的策略),該怎麼做呢?

  • 同樣是先定義一個策略;
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
  name: "example-3"
spec:
  targets:
  - name: two

這次我們可以看到,我們是直接針對某一服務進行策略的配置。

  • 然後我們定義相應的目標規則:
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
  name: "example-3"
spec:
  host: two.test-istio.svc.cluster.local
  trafficPolicy:
    tls:
      mode: DISABLE

上述策略很明顯,針對的是test-istio名稱空間中的two服務,取消了它的互相TLS認證。

那麼我們把TLS就簡單的介紹到這裡吧。

接下來介紹RBAC
RBAC:基於角色的許可權訪問控制(Role-Based Access Control)。這個概念我們應該聽爛了,有很多框架就是用來做這個的,比如shiro,spring security都可以做。
但是!istio中的RBAC是針對服務及服務管理者而言的,並不是我們應用層面的,但是功能性是一致的。

我們通過定義相應的服務角色(類似於服務的管理者),指定它的訪問許可權,然後為每個服務繫結相應的服務角色,最後達到通過不同的服務角色來限制對不同服務的訪問。

當然,這裡面也同樣包含了相當多的概念,這裡我簡單的闡述一下。

  • 首先我們要保證我們開啟了互相TLS認證,然後我們需要新建一些服務賬戶,並在這些賬戶下部署服務;
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-one
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-two
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: one
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: one
        version: v1
    spec:
      serviceAccountName: test-one
      containers:
      - image: yubotao/istio-one:test
        imagePullPolicy: IfNotPresent
        name: one
        ports:
        - containerPort: 8180
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: two
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: two
        version: v1
    spec:
      serviceAccountName: test-two
      containers:
      - image: yubotao/istio-two:test
        imagePullPolicy: IfNotPresent
        name: two
        ports:
        - containerPort: 8080

如上分別定義了兩個ServiceAccounts,並分別部署了服務。

  • 接下來,我們需要開啟RBAC,我們看一下是如何開啟RBAC的;
apiVersion: "config.istio.io/v1alpha2"
kind: authorization
metadata:
  name: requestcontext
  namespace: istio-system
spec:
  subject:
    user: source.user | ""
    groups: ""
    properties:
      app: source.labels["app"] | ""
      version: source.labels["version"] | ""
      namespace: source.namespace | ""
  action:
    namespace: destination.namespace | ""
    service: destination.service | ""
    method: request.method | ""
    path: request.path | ""
    properties:
      app: destination.labels["app"] | ""
      version: destination.labels["version"] | ""
---
apiVersion: "config.istio.io/v1alpha2"
kind: rbac
metadata:
  name: handler
  namespace: istio-system
spec:
  config_store_url: "k8s://"
---
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: rbaccheck
  namespace: istio-system
spec:
  match: destination.namespace == "test-istio"
  actions:
  - handler: handler.rbac
    instances:
    - requestcontext.authorization

我們可以看到,上述的定義都是針對名稱空間istio-system的,而需要修改的部分就在rule中的match部分,我們需要在哪個名稱空間開啟RBAC,就在這裡的destination.namespace欄位填上相應內容。

此時,由於存在RBAC策略,我們可以看到服務之間的訪問是不通的,會出現錯誤。

開啟RBAC後,我們看一下它的效果,首先我們測試名稱空間級的RBAC控制訪問策略

apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRole
metadata: 
  name: service-viewer
  namespace: test-istio
spec:
  rules:
  - services: ["*"]
    methods: ["*"]
    constraints:
    - key: "app"
      values: ["one","two"]
---
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRoleBinding
metadata:
  name: bind-service-viewer
  namespace: test-istio
spec:
  subjects:
  - properties:
      namespace: "istio-system"
  - properties:
      namespace: "test-istio"
  roleRef:
    kind: ServiceRole
    name: "service-viewer"

上述ServiceRole定義了一個service-viewer服務角色,它可以對兩個應用one/two(在constraints欄位體現)的所有服務(rules中對應欄位)有所有的訪問許可權方法,方法包含GET,POST,HEAD等,不明確的話,就推薦使用“*”。

而下面的ServiceRoleBinding就是將剛才定義的ServiceRole繫結到test-istio名稱空間上,這樣就可以保證該名稱空間的服務是互通的。

我們看到,上面出現三個名詞,ServiceAccount、ServiceRole、ServiceRoleBinding;那麼它們之間是什麼關係呢?
首先Istio RBAC定義了ServiceRole和ServiceRoleBinding。ServiceRole中的constraints的欄位和“authorization”模板中的action部分的properties一一對應。也就是說action中properties有什麼,constraints中才能寫什麼。而ServiceRoleBinding和ServiceRole一一繫結,在roleRef中可以體現出來。而其subjects中的屬性必須和“authorization”模板中的subject部分的各屬性匹配(“user” or “groups” or one of the “properties”)。所以說,ServiceRole和ServiceRoleBinding離不開啟動RBAC的“authorization”模板。

ServiceAccount是k8s中的身份與許可權控制,它為pod中的程序提供身份資訊,當pod中的程序與apiserver聯絡時,就帶有特定的ServiceAccount。換句話說,如果pod和ServiceAccount做了繫結,那麼只有在該ServiceAccount下,才能夠訪問pod中的資源,否則無法訪問。這也就是為什麼開啟RBAC後,會出現許可權問題,拒絕訪問服務;而沒開啟RBAC時,就不存在這些影響。

接下來,我們看一下服務級的訪問控制。服務級的訪問控制就需要針對每個服務做控制了,以我們上面的例子,需要對one和two分別做訪問控制,如果有其他服務在呼叫鏈路上,還需要新加策略。

  • 首先我們看one服務的策略,保證訪問one是暢通的;
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRole
metadata:
  name: one-viewer
  namespace: test-istio
spec:
  rules:
  - services: ["one.test-istio.svc.cluster.local"]
    methods: ["GET"]
---
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRoleBinding
metadata:
  name: bind-one-viewer
  namespace: test-istio
spec:
  subjects:
  - user: "*"
  roleRef:
    kind: ServiceRole
    name: "one-viewer"

這裡和名稱空間級策略有所不同,ServiceRole沒有constraints,ServiceRoleBinding使用的是user,代表每個服務(和服務對應的pod繫結的ServiceAccount)。這裡是保證gateway能夠訪問到one服務。

  • 接下來就是需要保證one服務能夠訪問到two服務了。
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRole
metadata:
  name: two-viewer
  namespace: test-istio
spec:
  rules:
  - services: ["two.test-istio.svc.cluster.local"]
    methods: ["GET"]
---
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRoleBinding
metadata:
  name: bind-two-viewer
  namespace: test-istio
spec:
  subjects:
  - user: "cluster.local/ns/test-istio/sa/test-one"
  roleRef:
    kind: ServiceRole
    name: "two-viewer"

和上面類似,不多說了。不過,這裡在ServiceRoleBinding處做了更精細的設定,在subject中的user裡,我們指定了特定的ServiceAccount(它和對應服務的pod進行了繫結。)。在Istio中的服務賬戶形式為: “spiffe://<domain>/ns/<namespace>/sa/<serviceaccount>” ;domain是當前的cluster.local

到此,我們就基本把istio的常用安全策略等內容講完了。

追蹤監控

這一部分是比較簡單的,不需要在應用程式碼中插入對接程式碼了,直接可以通過istio完成對應中介軟體的部署,並且都是現成模板,唯一一點就是,你需要會用相應的軟體。
目前提供的有Jaeger、Zipkin(追蹤);Prometheus(metric);Grafana(istio監控的圖形介面,這個挺不錯的);Service Graph(服務間呼叫關係)等。其中絕大部分中介軟體都是以Prometheus為基的,所以目前0.8版本預設就安裝好Prometheus了。

相關的安裝之類的可以看翻譯的文件,沒什麼好說的。

終於,目前關於Istio所暴露出來的常用功能就介紹完了,還有相當的待開發功能,能力有限,暫時沒有相關資料,有一點是我比較在意的,就是Istio的Mixer這個概念以及它所提供的功能,這個應該是一個功能比較強大的可插拔元件的功能模組,如果有餘力可以研究研究這個。