在服務網格內部呼叫外部 TCP 服務
原文地址: ofollow,noindex">使用外部 TCP 服務
英文作者:VADIM EISENBERG
修訂:楊傳勝
本篇部落格於 2018 年 7 月 23 日更新。新版本使用了 Istio 1.0,並使用了新的 v1alpha3 流量管理 API 。如果您使用的 Istio 是舊版本,請參考 這篇文件 。
在上一篇文章 在服務網格內部呼叫外部 Web 服務 中,我描述瞭如何讓 Istio 服務網格中的微服務通過 HTTPS 協議和外部的 Web 服務進行通訊。本文我將著重介紹如何讓 Istio 服務網格中的微服務通過 TCP
協議和外部服務進行通訊。講解的過程中會用到 Bookinfo 示例應用程式 中將書籍評級資料儲存在 SQL/">MySQL 資料庫中的那個版本。資料庫部署在叢集外, ratings
服務呼叫該資料庫,還要定義一個 ServiceEntry
以允許網格內的應用程式訪問外部的資料庫。
1. Bookinfo 示例應用程式與外部評級資料庫
首先,在 Kubernetes 叢集之外設定了一個 MySQL 資料庫例項來儲存 Bookinfo 評級資料,然後修改 Bookinfo 示例應用程式 以使用這個資料庫。
為評級資料設定資料庫
首先你需要建立一個 MySQL 資料庫例項,你可以使用任何 MySQL 例項,我自己用的是 Compose for MySQL ,我使用 mysqlsh
( MySQL Shell )作為 MySQL 客戶端來提供評級資料。
1. 設定 MYSQL_DB_HOST
和 MYSQL_DB_PORT
環境變數。
$ export MYSQL_DB_HOST=<your MySQL database host> $ export MYSQL_DB_PORT=<your MySQL database port>
如果你使用的是本地資料庫, host
和 port
使用的是預設值,分別是 localhost
和 3306
。
2. 執行以下命令初始化資料庫,請在出現提示時輸入密碼。這個命令通過 admin
資料庫使用者憑證來執行,該使用者是通過 Compose for Mysql 建立資料庫時預設建立的。
$ curl -s https://raw.githubusercontent.com/istio/istio/master/samples/bookinfo/src/mysql/mysqldb-init.sql | mysqlsh --sql --ssl-mode=REQUIRED -u admin -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT
或者
如果你使用的是本地資料庫例項,且客戶端使用的是 mysql
,可以執行以下命令來初始化:
$ curl -s https://raw.githubusercontent.com/istio/istio/master/samples/bookinfo/src/mysql/mysqldb-init.sql | mysql -u root -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT
3. 建立一個名為 bookinfo
的使用者,並在 test.ratings
表上授予它 SELECT 許可權:
$ mysqlsh --sql --ssl-mode=REQUIRED -u admin -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT -e "CREATE USER 'bookinfo' IDENTIFIED BY '<password you choose>'; GRANT SELECT ON test.ratings to 'bookinfo';"
或者
如果你使用的是本地資料庫例項,且客戶端使用的是 mysql
,可以執行以下命令:
$ mysql -u root -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT -e "CREATE USER 'bookinfo' IDENTIFIED BY '<password you choose>'; GRANT SELECT ON test.ratings to 'bookinfo';"
這裡一般遵循 最小許可權原則 ,這意味著在 Bookinfo 應用程式中不會直接使用 admin
使用者。相反,應該為 Bookinfo 應用程式建立一個最小許可權的特殊使用者 bookinfo
,該使用者只對單個表具有 SELECT 特權。
執行建立使用者的命令後,你可能希望通過檢查最後一個命令的編號和執行命令 history -d <建立使用者的命令編號>
來清理 bash 歷史記錄,我相信你不會想把新使用者的密碼儲存在 bash 歷史記錄中的。如果你使用的命令列工具是 mysql
,記得要刪除 ~/.mysql_history
檔案中的最後一條命令。可以在 MySQL 官方文件 中閱讀有關新建立使用者的密碼保護的更多資訊。
4. 檢視建立的評級資料是否跟預期的一致:
$ mysqlsh --sql --ssl-mode=REQUIRED -u bookinfo -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT -e "select * from test.ratings;" Enter password: +----------+--------+ | ReviewID | Rating | +----------+--------+ |1 |5 | |2 |4 | +----------+--------+
或者
如果你使用的是本地資料庫例項,且客戶端使用的是 mysql
,可以執行以下命令:
$ mysql -u bookinfo -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT -e "select * from test.ratings;" Enter password: +----------+--------+ | ReviewID | Rating | +----------+--------+ |1 |5 | |2 |4 | +----------+--------+
5. 暫時將評級設定為 1
,以便在 Bookinfo ratings 服務呼叫資料庫時提供直觀的線索。
$ mysqlsh --sql --ssl-mode=REQUIRED -u admin -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT -e "update test.ratings set rating=1; select * from test.ratings;" Enter password: Rows matched: 2Changed: 2Warnings: 0 +----------+--------+ | ReviewID | Rating | +----------+--------+ |1 |1 | |2 |1 | +----------+--------+
或者
如果你使用的是本地資料庫例項,且客戶端使用的是 mysql
,可以執行以下命令:
$ mysql -u root -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT -e "update test.ratings set rating=1; select * from test.ratings;" Enter password: +----------+--------+ | ReviewID | Rating | +----------+--------+ |1 |1 | |2 |1 | +----------+--------+
最後一個命令使用了 admin
使用者(本地資料庫例項使用的是 root
使用者),因為 bookinfo
使用者對 test.ratings
這個表沒有 UPDATE 許可權。
現在就可以部署一個使用外部資料庫的 Bookinfo 應用程式了。
Bookinfo 應用程式的初始設定
為了演示使用外部資料庫的場景,首先需要一個安裝了 Istio 的 Kubernetes 叢集,然後部署 Istio Bookinfo 示例應用程式 ,並且建立了預設的 DestinationRule
。
該應用程式使用 ratings
微服務來獲取書籍評級,評級在 1 到 5 之間,評級顯示為每個 review 的星號。有好幾個版本的 ratings
微服務,有些版本使用 MongoDB 作為資料庫,還有些版本使用 MySQL 作為資料庫。
本文的示例命令適用於 Istio 1.0+,無論你有沒有啟用 雙向 TLS 認證 。
以下是原始版本的 Bookinfo 示例應用程式中應用程式端到端架構的副本。
原 Bookinfo 應用程式
使用外部資料庫儲存 Bookinfo 應用程式的評級資料
1. 修改使用 MySQL 資料庫的 ratings 服務版本的 deployment 配置檔案中的環境變數,將其修改成你自己的資料庫例項資訊。該 yaml 檔案位於 Istio 發行存檔的 samples/bookinfo/platform/kube/bookinfo-ratings-v2-mysql.yaml 中。修改以下幾行:
- name: MYSQL_DB_HOST value: mysqldb - name: MYSQL_DB_PORT value: "3306" - name: MYSQL_DB_USER value: root - name: MYSQL_DB_PASSWORD value: password
將資料庫的 IP、埠、使用者名稱和密碼替換成實際的值。請注意,在 Kubernetes 中使用容器環境變數中密碼的正確方法是 使用 secret ,本文只是為了便於演示在 deployment spec 中直接配置明文密碼。 切記!不要在真實環境中這樣做! 我想你們應該也知道, "password"
這個值也不應該用作密碼。
2. 使用修改後的 deployment yaml 檔案來建立使用外部資料庫的 ratings 服務: v2-mysql
。
$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo-ratings-v2-mysql.yaml deployment "ratings-v2-mysql" created
3. 將發往 reviews 服務的所有流量都路由到 v3
版本,這樣做是為了確保 reviews 服務始終呼叫 ratings 服務。此外,將發往 ratings 服務的所有流量都路由到使用外部資料庫的 ratings v2-mysql
。
通過新增兩個 VirtualService
,可以為上述兩種服務指定路由。這些 VirtualService
在 Istio 發行檔案的 samples/bookinfo/networking/virtual-service-ratings-mysql.yaml 中指定。 注意: 確保你在添加了預設的 DestinationRule
之後再執行下面的命令。
$ kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-mysql.yaml
更新後的架構如下所示。請注意,網格內的藍色箭頭表示建立 VirtualService
之後的流量轉發路徑。根據建立的 VirtualService
,流量將被轉發到 reviews v3
和 ratings v2-mysql
。
使用外部 MySQL 資料庫的 ratings v2-mysql 版本的 Bookinfo 應用程式
請注意,MySQL 資料庫位於 Istio 服務網格之外,或者更準確地說是在 Kubernetes 叢集之外,服務網格的邊界由虛線標記。
訪問 Web 頁面
在 確定 ingress IP 和埠 之後, 就可以訪問應用程式的 Web 頁面了。
哎呀糟糕,出現問題了 :disappointed_relieved: 無論你怎麼重新整理瀏覽器,每個 review 下方都不會顯示評級星標,而是顯示 “Ratings service is currently unavailable”
。
Ratings 服務的錯誤資訊
與 在服務網格內部呼叫外部 Web 服務 這篇文章中遇到的情況一樣,你會體驗到優雅的服務降級,非常好。雖然 ratings 服務中有錯誤,但是應用程式並沒有因此而崩潰,Web 頁面雖然不能顯示評級星標,但可以正確顯示書籍資訊、details 資訊和 reviews 資訊。
預設情況下, Istio sidecar 代理(Envoy proxies) 會阻止到叢集外服務的所有流量(TCP 和 HTTP),要為 TCP 啟用此類流量,我們必須先定義 TCP 協議的 mesh-external ServiceEntry
。
外部 MySQL 例項的 Mesh-external ServiceEntry
下面就該 mesh-external ServiceEntry 上場了。
1. 獲取 MySQL 資料庫的 IP 地址。你可以通過 hosts 命令來獲取:
$ export MYSQL_DB_IP=$(host $MYSQL_DB_HOST | grep " has address " | cut -d" " -f4)
如果你使用的是本地資料庫例項,設定 MYSQL_DB_IP
環境變數為你的本機 IP,並且要保證這個環境變數能被叢集訪問到。
2. 定義一個 TCP mesh-external ServiceEntry
:
kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: mysql-external spec: hosts: - $MYSQL_DB_HOST addresses: - $MYSQL_DB_IP/32 ports: - name: tcp number: $MYSQL_DB_PORT protocol: tcp location: MESH_EXTERNAL EOF
3. 檢視建立好的 ServiceEntry
:
$ kubectl get serviceentry mysql-external -o yaml apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: ...
Note
對於 TCP ServiceEntry,你需要指定 port
列表的 protocol
欄位值為 tcp
,還要在 addresses
列表裡面指定外部服務的 IP 地址,該 IP 地址以網路號為 32
位的無型別域間選路( CIDR )形式表示。
下面我將詳細討論 TCP ServiceEntry。現在先來驗證新增 ServiceEntry
之後是否解決了上面遇到的問題,再次訪問 Web 頁面,看看評級星標是不是回來了。
果然有效!現在 Web 頁面的報錯已經消失了,正確顯示了評級:
Book Ratings 顯示正常
和預期的一樣,你會看到兩個 review 下面顯示的都是一星評級。因為之前我們在資料庫中將評級改為了一顆星,所以現在可以肯定 ratings 服務呼叫到了外部資料庫。
與 HTTP/HTTPS 協議的 ServiceEntry 一樣,你也可以使用 kubectl
動態刪除和建立 TCP ServiceEntry。
2. 控制出口 TCP 流量的動機
有時候,Istio 網格內的應用程式需要訪問外部服務,如遺留系統。並且很多情況下,網格內的微服務都不會通過 HTTP 或 HTTPS 協議來訪問外部服務,而是通過 TCP
協議或 TCP 協議的變種(如 MongoDB wire 協議 和 MySQL客戶端/伺服器協議 )來和外部資料庫通訊。
接下來我會重點介紹 TCP 流量的 ServiceEntry
。
3. TCP 流量的 ServiceEntry
用於啟用到特定埠的 TCP 流量的 ServiceEntry
必須將埠的協議指定為 TCP
。此外,對於 MongoDB Wire 協議 ,可以將協議指定為 MONGO
,而不是 TCP
。
對於 ServiceEntry 中的 addresses
列表,必須以網路號為 32 位的無型別域間選路( CIDR )形式表示。 注意:在 TCP ServiceEntry 中, hosts
欄位會被忽略掉。
想要通過其主機名(hostname)啟用到外部服務的 TCP 流量,必須指定主機名的所有 IP,每個 IP 必須以 CIDR 的形式表示。
有時候我們無法獲取外部服務的所有 IP,這時候要想往叢集外發送 TCP 流量,只能在 addresses
列表中指定那些已知的被應用程式使用的 IP。
有些情況下,外部服務的 IP 並不總是靜態 IP,例如在 CDN 的場景中。大多數情況下 IP 地址都是靜態的,但有時 IP 地址會被更改,例如由於基礎設施的變化。這時候如果你知道 IP 地址變化的範圍,就可以通過 CIDR 的形式指定範圍。如果你實在無法確定 IP 地址變化的範圍,就不能使用 TCP ServiceEntry,必須繞過 sidecar 代理 直接呼叫外部服務 。
4. 與網格擴充套件的關係
請注意,本文中描述的場景與 整合虛擬機器 示例中描述的網格擴充套件場景不同。 在整合虛擬機器的場景中,MySQL 例項在與 Istio 服務網格整合的外部(叢集外)機器(裸機或VM)上執行 ,MySQL 服務成為網格的一等公民,具有 Istio 的所有高階功能。除此之外,也不需要建立 ServiceEntry 來訪問 MySQL 服務,可以直接通過本地叢集域名(例如 mysqldb.vm.svc.cluster.local
)來定址,並且可以通過 雙向 TLS 身份驗證 來保護與其之間的通訊。但是該服務必須要在 Istio 中註冊,要啟用此類整合,必須在計算機上安裝 Istio 元件(Envoy proxy,node-agent,istio-agent),並且必須可以從中訪問 Istio 控制平面(Pilot,Mixer,Citadel)。詳細資訊請參考 Istio Mesh Expansion 。
但在本文的示例中,MySQL 例項可以在任何機器上執行,也可以由雲提供商提供,無需與 Istio 整合,也無需從 MySQL 例項所在的機器上訪問 Istio 控制平面。在 MySQL 作為服務的情況下,客戶端可能無法訪問 MySQL 所執行的機器,並且無法在該機器上安裝所需元件。本文示例中的 MySQL 例項可以通過其全域性域名進行定址,這對希望使用域名來定址的消費者客戶端來說是有益的。當在消費者應用程式的部署配置中無法更改預期的域名時,這項功能顯得尤為重要。
5. 清理
1. 刪除 test
資料庫和 bookinfo
使用者:
$ mysqlsh --sql --ssl-mode=REQUIRED -u admin -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT -e "drop database test; drop user bookinfo;"
或者
如果你使用的是本地資料庫例項,且客戶端使用的是 mysql
,可以執行以下命令:
$ mysql -u root -p --host $MYSQL_DB_HOST --port $MYSQL_DB_PORT -e "drop database test; drop user bookinfo;"
2. 刪除 VirtualService
$ kubectl delete -f samples/bookinfo/networking/virtual-service-ratings-mysql.yaml Deleted config: virtual-service/default/reviews Deleted config: virtual-service/default/ratings
3. 刪除 ratings v2-mysql:
$ kubectl delete -f samples/bookinfo/platform/kube/bookinfo-ratings-v2-mysql.yaml deployment "ratings-v2-mysql" deleted
4. 刪除 ServiceEntry
$ kubectl delete serviceentry mysql-external -n default Deleted config: serviceentry mysql-external
6. 總結
本文演示了 Istio 服務網格中的微服務如何通過 TCP
協議呼叫外部服務。預設情況下, Istio sidecar 代理(Envoy proxies) 會阻止到叢集外服務的所有流量(TCP 和 HTTP),要為 TCP 啟用此類流量,我們必須先定義 TCP 協議的 mesh-external ServiceEntry
。
-----------他日江湖相逢 再當杯酒言歡-----------