雲計算

如何使用 Istio 進行多集群部署管理(1): 單控制平面 VPN 連接拓撲

服務網格作為一個改善服務到服務通信的專用基礎設施層,是雲原生範疇中最熱門的話題。隨著容器愈加流行,服務拓撲也頻繁變動,這就需要更好的網絡性能。服務網格能夠通過服務發現、路由、負載均衡、心跳檢測和支持可觀測性,幫助我們管理網絡流量。服務網格試圖為無規則的複雜的容器問題提供規範化的解決方案。

服務網格也可以用於混沌工程 —— “一門在分佈式系統上進行實驗的學科,目的是構建能夠應對極端條件的可靠系統”。服務網格能夠將延遲和錯誤注入到環境中,而不需要在每個主機上安裝一個守護進程。

容器是雲原生應用的基石,通過應用容器化,使得應用開發部署更加敏捷、遷移更加靈活,並且這些實現都是基於標準化的。而容器編排則是更近一步,能夠更加有效地編排資源、更加高效地調度利用這些資源。而到了雲原生時代,在 Kubernetes 基礎架構之上,結合 Istio 服務網格,提供了多雲、混合雲的支持能力,針對微服務提供了有效的治理能力,並以 Kubernetes 和 Istio 為基礎,提供了針對特定應用負載的不同支持,例如針對 Kubeflow 服務的流量治理、為 Knative 提供負載的路由管理能力等。

儘管 Service Mesh 在雲原生系統方面的應用已經有了快速的增長,但仍然存在巨大的提升空間。無服務器(Serverless)計算正好需要 Service Mesh 的命名和鏈接模型,這讓 Service Mesh 在雲原生生態系統中的角色得到了彰顯。服務識別和訪問策略在雲原生環境中仍顯初級,而 Service Mesh 毫無疑問將成為這方面不可或缺的基礎。就像 TCP/IP 一樣,Service Mesh 將在底層基礎設施這條道路上更進一步。

混合雲可以採用多種形式。通常,混合雲指的是跨公有云和私有(內部部署)雲運行,而多雲意味著跨多個公有云平臺運行。

採用混合雲或多雲架構可以為你的組織帶來諸多好處。例如,使用多個雲提供商可以幫助你避免供應商鎖定,能夠讓你為實現目標選擇最佳的雲服務。使用雲和本地環境,你可以同時享受雲的優勢(靈活性、可擴展性、成本降低)和本地的好處(安全性、低延遲、硬件複用)。如果你是首次遷移到雲端,採用混合雲步驟可以讓你按照自己的節奏,以最適合你業務的方式進行。

根據我們在公有云上的實踐經驗及從客戶那裡得到的信息,我們認為採用混合服務網絡是簡化雲和本地環境中應用程序管理、安全性和可靠性的關鍵,無論你的應用程序是在容器中運行,或是在虛擬機中運行。

Istio 的一個關鍵特性是它為你的工作負載(例如 pod、job、基於 VM 的應用程序)提供服務抽象。當你轉向混合拓撲時,這種服務抽象變得更加重要,因為現在你不只需要關注一個環境,而是需要關注若干個環境。

當你在一個 Kubernetes 集群上使用 Istio 時,可以獲得包括可見性、細粒度流量策略、統一遙測和安全性在內的微服務的所有管理優勢。但是當你在多個環境中使用 Istio 時,實際上是為應用程序提供了一個新的超級能力。因為 Istio 不僅僅是 Kubernetes 的服務抽象,也是一種在整個環境中標準化網絡的方法。它是一種集中 API 管理並將 JWT 驗證與代碼分離的方法。它是跨雲提供商的安全、零信任網絡的快速通道。

那麼所有這些魔法是如何發生的呢?混合 Istio 是指一組 Istio Sidecar 代理,每一個 Envoy 代理位於所有服務的旁邊,而這些服務可能運行在不同環境中的每一個虛擬機、每一個容器中,而且這些 Sidecar 代理之前互相知道如何跨邊界交互。這些 Envoy Sidecar 代理可能由一箇中央 Istio 控制平面管理,或由每個環境中運行的多個控制平面管理。

多集群部署管理

服務網格本質上是將一組單獨的微服務組合成單個可控的複合應用程序,Istio 作為一種服務網格,也是旨在單一管理域下監視和管理協作微服務網絡。對於特定大小的應用程序,所有微服務是可以在單個編排平臺如一個 Kubernetes 集群上運行的。然而,由於規模不斷增大或者冗餘等原因,大多數應用程序最終將需要分發一些服務在其他地方運行。

社區越來越關注在多個集群上運行工作負載,以實現更好的擴展,故障可以更好地隔離,從而提升應用程序的敏捷性。Istio v1.0 開始支持一些多集群功能,並在之後的版本中添加了新功能。

Istio 服務網格支持許多可能的拓撲結構,用於在單個集群之外分發應用程序的服務,有兩種常見的模式或用例:單網格和網格聯合。顧名思義,單個網格將多個集群組合成一個單元,由一個 Istio 控制平面管理;它可以實現為一個物理控制平面,也可以實現為一組控制平面,同時所有控制平面都能通過複製配置保持同步。而網格聯合則會將多個集群分離作為單獨的管理域,有選擇地完成集群之間的連接,僅將服務的子集暴露給其他集群;自然它的實現會包含多個控制平面。

具體來說,這些不同的拓撲結構包括以下幾個方面:

  • 網格中的服務可以使用服務條目(Service Entry)來訪問獨立的外部服務或訪問由另一個鬆散耦合的服務網格公開的服務,通常稱為網格聯邦(Mesh Federation)。這種拓撲適合於互相獨立並且網絡隔離、只能通過公網交互的多集群的場景;
  • 支持在虛擬機或物理裸機上運行的服務進行服務網格擴展,通常稱為網格聯合(Mesh Expansion)。在前面章節中,我們已經講述了這種 Kubernetes 集群與虛擬機、物理裸機之間混合部署的場景;
  • 把來自多個集群的服務組合到單個服務網格中,通常稱為多集群網格(Multicluster Mesh)。根據網絡拓撲結構的不同,多集群網格通常分為單控制平面 VPN 連接、單控制平面網關連接以及多控制平面拓撲。

單控制平面 VPN 連接拓撲

作為基準,在 Istio 的 1.1 版本之前,Istio 1.0 多集群僅支持使用單網格設計。它允許多個集群連接到網格中,但所有集群都在一個共享網絡上。也就是說,所有集群中所有 pod 和服務的 IP 地址都是可直接路由的,不會發生衝突,同時保證在一個集群中分配的IP地址不會在另一個集群中同時重用。

在這種拓撲配置下,在其中一個集群上運行單個 Istio 控制平面。該控制平面的 Pilot 管理本地和遠程集群上的服務,併為所有集群配置 Envoy 代理。這種方法在所有參與集群都具有 VPN 連接的環境中效果最佳,因此可以使用相同的 IP 地址從其他任何地方訪問網格中的每個 pod。

在此配置中,Istio 控制平面部署在其中一個集群上,而所有其他集群運行更簡單的遠程 Istio 配置,該配置將它們連接到單個 Istio 控制平面,該平面將所有 Envoy 管理為單個網格。各個集群上的 IP 地址不允許重疊,並且遠程集群上的服務的 DNS 解析不是自動的。用戶需要在每個參與集群上覆制服務,這樣每個集群中的 Kubernetes 集群服務和應用程序都能夠將其內部 Kubernetes 網絡暴露給其他集群。一旦一個或多個遠程 Kubernetes 集群連接到 Istio 控制平面,Envoy 就可以與單個控制平面通信並形成跨多個集群的網狀網絡。

前提約束

事實上,我們已經瞭解到網格、集群和網絡之間的存在各種約束,例如,在某些環境中,網絡和集群直接相關。Istio單網格設計下的單控制平面VPN連接拓撲需要滿足以下幾個條件:

  • 運行 Kubernetes 1.9 或更高版本的兩個或更多集群;
  • 能夠在其中一個集群上部署 Istio 控制平面;
  • RFC1918 網絡、VPN 或滿足以下要求的更高級網絡技術:

    • 單個集群 pod CIDR 範圍和服務 CIDR 範圍在多集群環境中必須是唯一的,並且應當不重疊;
    • 每個集群中的所有 pod CIDR 必須可以相互路由
    • 所有 Kubernetes 控制平面 API 服務器必須可以相互路由。

此外,為了跨集群支持 DNS 名稱解析,必須確保在所有需要跨集群服務調用的集群中定義對應的命名空間、服務和服務賬戶;例如,集群 cluster1 中命名空間 ns1 的服務 service1 需要調用集群 cluster2 中命名空間 ns2 的服務 service2,那麼在集群 cluster1 中為了支持服務名的 DNS 解析,需要在集群 cluster1 中創建一個命名空間 ns2 以及該命名空間下的服務 service2。

以下示例中的兩個 Kubernetes 集群的網絡假定已經滿足上述要求,每個集群中的 pod 都能夠互相路由,也就是說網絡可通並且端口是可訪問的(如果採用的是類似於阿里雲的公有云服務,請確保這些端口在安全組規則下是可以訪問的;否則服務間的調用會受到影響)。

兩個 Kubernetes 集群的 pod CIDR 範圍和服務 CIDR 範圍定義如下表所示:

image.png

拓撲架構

image.png
從圖中可以看到整個多集群拓撲中只會在一個 Kubernetes 集群上安裝 Istio 控制平面。這個安裝 Istio 控制平面的集群通常被稱為本地集群,所有其它集群稱為遠程集群。

這些遠程集群只需要安裝 Istio 的 Citadel 和 Sidecar Injector 准入控制器,具有較小的 Istio 佔用空間,Citadel 用於這些遠程集群的安全管理,Sidecar Injector 准入控制器用於控制平面中的自動注入和數據平面中工作負載的 Sidecar 代理功能。

在這個架構中,Pilot 可以訪問所有集群中的所有 Kubernetes API 服務器,因此它具有全局網絡訪問視圖。Citadel 和 Sidecar Injector 准入控制器則只會在集群本地範圍內運行。每個集群都有唯一的 pod 和服務 CIDR,除此之外,集群之間還有一個共享的扁平網絡,以保證能直接路由到任何工作負載,包括到 Istio 的控制平面。例如,遠程集群上的 Envoy 代理需要從 Pilot 獲得配置,檢查並報告給 Mixer 等。

啟用雙向 TLS 通信

如果在多個集群中啟用跨集群的雙向 TLS 通信,就需要按照如下方式在各個集群中進行部署配置。首先,從共享的根 CA 為每個集群的 Citadel 生成中間 CA 證書,共享的根 CA 啟用跨不同集群的雙向 TLS 通信。為了便於說明,我們將 samples/certs 目錄下 Istio 安裝中提供的示例根 CA 證書用於兩個集群。在實際部署中,你可能會為每個集群使用不同的 CA 證書,所有 CA 證書都由公共根 CA 簽名。

在每個 Kubernetes 集群中(包括示例中的集群 cluster1 與 cluster2)創建密鑰。使用以下的命令為生成的 CA 證書創建 Kubernetes 密鑰:

kubectl create namespace istio-system
kubectl create secret generic cacerts -n istio-system \
  --from-file=samples/certs/ca-cert.pem \
  --from-file=samples/certs/ca-key.pem \
  --from-file=samples/certs/root-cert.pem \
  --from-file=samples/certs/cert-chain.pem

當然,如果你的環境只是開發測試或者不需要啟用雙向 TLS 通信,上述步驟完全可以跳過。

部署本地控制平面

在所謂的本地集群上安裝一個 Istio 控制平面的過程,與在單集群上安裝 Istio 並沒有太多差別,需要注意的一點是如何配置 Envoy 代理用於管理直接訪問某個 IP 範圍內的外部服務的參數。如果是使用 Helm 安裝 Istio,那麼在 Helm 中有一個名為 global.proxy.includeIPRanges 的變量,確保該變量為“*”或者包括本地集群、所有遠程集群的 pod CIDR 範圍和服務 CIDR。

可以通過查看命名空間 istio-system 下的配置項 istio-sidecar-injector 中的 traffic.sidecar.istio.io/includeOutboundIPRanges 來確認 global.proxy.includeIPRanges 參數設置,如下所示:

kubectl get configmap istio-sidecar-injector -n istio-system -o yaml| grep includeOutboundIPRanges

在部署 Istio 控制平面組件的集群 cluster1 中,按照以下步驟執行。

如果啟用了雙向 TLS 通信,則需要如下配置參數:

helm template --namespace=istio-system \
  --values
install/kubernetes/helm/istio/values.yaml \
  --set global.mtls.enabled=true \
  --set security.selfSigned=false \
  --set global.controlPlaneSecurityEnabled=true
\
  install/kubernetes/helm/istio > istio-auth.yaml
kubectl
apply -f istio-auth.yaml

如果不需要啟用雙向 TLS 通信,配置參數則需要做出如下修改:

helm template --namespace=istio-system \
  --values
install/kubernetes/helm/istio/values.yaml \
  --set global.mtls.enabled=false \
  --set security.selfSigned=true \
  --set global.controlPlaneSecurityEnabled=false
\
  install/kubernetes/helm/istio >
istio-noauth.yaml
kubectl
apply -f istio-noauth.yaml

修改 Istio 服務 istio-pilot、istio-telemetry、istio-policy 及 zipkin 的類型為內網負載均衡,將這些服務以內網方式暴露給遠程集群使用。不同的雲廠商實現機制不盡相同,但大都是通過修改 annotation 的方式實現。針對阿里雲容器服務來說,設置為內網負載均衡的方式非常簡單,只需要添加如下 annotation 到服務的 YAML 定義中即可:
service.beta.kubernetes.io/alicloud-loadbalancer-address-type: intranet。

此外,需要按照圖中的端口定義為每一個服務進行設置。
image.png

istio-pilot 服務端口如表 1 所示。

image.png

istio-telemetry 服務端口如表 2 所示

image.png

istio-policy 服務端口如表 3 所示。

image.png

zipkin 服務端口如表 4 所示。

image.png

安裝 istio-remote

在本地集群中安裝完控制平面之後,必須將 istio-remote 組件部署到每個遠程 Kubernetes 集群。等待 Istio 控制平面完成初始化,然後再執行本節中的步驟。你必須在 Istio 控制平面集群上運行這些操作以捕獲 Istio 控制平面服務端點,例如上述提到的 Istio 服務 istio-pilot、istio-telemetry、istio-policy 以及 zipkin。

在遠程集群 cluster2 中部署 Istio-remote 組件,按照以下步驟執行:

1.在本地集群上使用以下命令設置環境變量:

export
PILOT_IP=$(kubectl -n istio-system get service istio-pilot -o
jsonpath='{.status.loadBalancer.ingress[0].ip}')
export
POLICY_IP=$(kubectl -n istio-system get service istio-policy -o
jsonpath='{.status.loadBalancer.ingress[0].ip}')
export
TELEMETRY_IP=$(kubectl -n istio-system get service istio-telemetry -o
jsonpath='{.status.loadBalancer.ingress[0].ip}')
export
ZIPKIN_IP=$(kubectl -n istio-system get service zipkin -o
jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo
$PILOT_IP $POLICY_IP $TELEMETRY_IP $ZIPKIN_IP

2.如果在多個集群中啟用跨集群的雙向 TLS 通信,就需要在集群中進行部署配置。

當然,如果你的環境只是開發測試或者不需要啟用雙向 TLS 通信的話,該步驟完全可以跳過。在遠程 Kubernetes 集群 cluster2 上運行以下命令,在集群中為生成的 CA 證書創建 Kubernetes 密鑰:

kubectl
create namespace istio-system
kubectl
create secret generic cacerts -n istio-system \
    --from-file=samples/certs/ca-cert.pem \
    --from-file=samples/certs/ca-key.pem \
    --from-file=samples/certs/root-cert.pem \
--from-file=samples/certs/cert-chain.pem

3.在遠程 Kubernetes 集群 cluster2 上,通過執行以下命令,使用 Helm 創建 Istio remote 部署 YAML 文件。

如果啟用了雙向 TLS 通信,則需要如下配置參數:

helm
template install/kubernetes/helm/istio \
  --name istio-remote \
  --namespace istio-system \
  --values install/kubernetes/helm/istio/values-istio-remote.yaml
\
  --set global.mtls.enabled=true \
  --set security.selfSigned=false \
  --set global.controlPlaneSecurityEnabled=true
\
  --set
global.remotePilotCreateSvcEndpoint=true \
  --set global.remotePilotAddress=${PILOT_IP} \
  --set global.remotePolicyAddress=${POLICY_IP}
\
  --set
global.remoteTelemetryAddress=${TELEMETRY_IP}
  --set global.remoteZipkinAddress=${ZIPKIN_IP}
> istio-remote-auth.yaml

然後將 Istio remote 組件部署到 cluster2,如下所示:

kubectl apply -f ./istio-remote-auth.yaml

如果不需要啟用雙向 TLS 通信,配置參數則需要做出如下修改:

helm
template install/kubernetes/helm/istio \
  --name istio-remote \
  --namespace istio-system \
  --values
install/kubernetes/helm/istio/values-istio-remote.yaml \
  --set global.mtls.enabled=false \
  --set security.selfSigned=true \
  --set
global.controlPlaneSecurityEnabled=false \
  --set
global.remotePilotCreateSvcEndpoint=true \
  --set global.remotePilotAddress=${PILOT_IP} \
  --set global.remotePolicyAddress=${POLICY_IP}
\
  --set global.remoteTelemetryAddress=${TELEMETRY_IP}
  --set global.remoteZipkinAddress=${ZIPKIN_IP}
> istio-remote-noauth.yaml

然後將 Istio remote 組件部署到 cluster2,如下所示:

kubectl
apply -f ./istio-remote-noauth.yaml

確保上述步驟在 Kubernetes 集群中執行成功。

4.創建集群 cluster2 的 Kubeconfig。

安裝 Istio-remote Helm chart 後,在遠程集群中創建了一個叫 istio-multi 的 Kubernetes 服務帳號,該服務帳號用於最小化 RBAC 訪問請求,對應的集群角色定義如下:

kind:
ClusterRole
apiVersion:
rbac.authorization.k8s.io/v1
metadata:
  name: istio-reader
rules:
  - apiGroups: ['']
    resources: ['nodes', 'pods', 'services',
'endpoints']
    verbs: ['get', 'watch', 'list']

下面的過程通過使用先前所述的 istio-multi 服務帳號憑證生成一個遠程集群的 kubeconfig 配置文件。通過以下命令,在集群 cluster2 上創建服務帳號 istio-multi 的 Kubeconfig,並保存為文件 n2-k8s-config:

CLUSTER_NAME="cluster2"
SERVER=$(kubectl
config view --minify=true -o "jsonpath={.clusters[].cluster.server}")
SECRET_NAME=$(kubectl
get sa istio-multi -n istio-system -o jsonpath='{.secrets[].name}')
CA_DATA=$(kubectl
get secret ${SECRET_NAME} -n istio-system -o
"jsonpath={.data['ca\.crt']}")
TOKEN=$(kubectl
get secret ${SECRET_NAME} -n istio-system -o
"jsonpath={.data['token']}" | base64 --decode)
cat
<<EOF > n2-k8s-config
apiVersion:
v1
kind:
Config
clusters:
  - cluster:
      certificate-authority-data: ${CA_DATA}
      server: ${SERVER}
    name: ${CLUSTER_NAME}
contexts:
  - context:
      cluster: ${CLUSTER_NAME}
      user: ${CLUSTER_NAME}
    name: ${CLUSTER_NAME}
current-context:
${CLUSTER_NAME}
users:
  - name: ${CLUSTER_NAME}
    user:
      token: ${TOKEN}
EOF

5.將集群 cluster2 加入 Istio Pilot 所在集群中。

在集群 dusterl 執行以下命令,將上述生成的集群 cluster2 的 kubeconfig 添加到集群 cluster1 的 secret 中。執行這些命令後,集群 cluster1 中的 Istio Pilot 將開始監聽集群 cluster2 的服務和實例,就像監聽集群 cluster1 中的服務與實例一樣:

kubectl
create secret generic n2-k8s-secret --from-file n2-k8s-config -n istio-system
kubectl
label secret n2-k8s-secret istio/multiCluster=true -n istio-system

部署示例應用

為了演示跨集群訪問,在第一個 Kubernetes 集群 cluster1 中部署 sleep 應用服務和版本 v1 的 helloworld 服務,在第二個集群 cluster2 中部署版本 v2 的 helloworld 服務,然後驗證 sleep 應用是否可以調用本地或者遠程集群的 helloworld 服務。

1.部署 sleep 和版本 v1 的 helloworld 服務到第一個集群 cluster1 中,執行如下命令:

kubectl
create namespace app1
kubectl
label namespace app1 istio-injection=enabled
kubectl
apply -n app1 -f multicluster/sleep/sleep.yaml
kubectl
apply -n app1 -f multicluster/helloworld/service.yaml
kubectl
apply -n app1 -f multicluster/helloworld/helloworld.yaml -l version=v1
export
SLEEP_POD=$(kubectl get -n app1 pod -l app=sleep -o
jsonpath={.items..metadata.name})
  1. 部署版本 v2 的 helloworld 服務到第二個集群 cluster2 中,執行如下命令:
kubectl
create namespace app1
kubectl
label namespace app1 istio-injection=enabled
kubectl
apply -n app1 -f multicluster/helloworld/service.yaml
kubectl
apply -n app1 -f multicluster/helloworld/helloworld.yaml -l version=v2
  1. 驗證在集群 cluster1 中的 sleep 服務是否可以正常調用本地或者遠程集群的 helloworld 服務,在集群 cluster1 下執行如下命令:
kubectl
exec $SLEEP_POD -n app1 -c sleep -- curl helloworld.app1:5000/hello

如果設置正確,則在返回的調用結果中可以看到兩個版本的 helloworld 服務,同時可以通過查看 sleep 容器組中的 istio-proxy 容器日誌來驗證訪問的端點 IP 地址,返回結果如下所示:

image.png

  1. 驗證 Istio 路由規則是否生效。
    創建針對上述兩個版本的 helloworld 服務的路由規則,以便驗證 Istio 配置是否可以正常工作。

創建 Istio 虛擬服務 VirtualService,執行如下命令:

kubectl
apply -n app1 -f multicluster/helloworld/virtualservice.yaml

接著,創建 Istio 目標規則 DestinationRule:

  • 如果啟用了雙向 TLS 通信,則需要如下配置參數:
kubectl
apply -n app1 -f multicluster/helloworld/destinationrule.yaml
  • 如果不需要啟用雙向 TLS 通信,配置參數則需要做出修改,在 YAML 定義中添加trafficPolicy.tls.mode:ISTIO_MUTUAL,定義如下所示:
apiVersion:
networking.istio.io/v1alpha3
kind:
DestinationRule
metadata:
  name: helloworld
spec:
  host: helloworld
  **trafficPolicy:
  tls:
    mode: ISTIO_MUTUAL**
  subsets:
  - name: v1
  labels:
    version: v1
  - name: v2
  labels:
    version: v2

通過執行命令 kubectl apply 創建啟用雙向 TLS 的 Istio 目標規則,如下所示:

kubectl
apply -n app1 -f multicluster/helloworld/destinationrule-auth.yaml

多次調用 helloworld 服務,只會返回版本 v2 的響應結果,如下所示:

image.png

Leave a Reply

Your email address will not be published. Required fields are marked *