601 views
The Sidecar Pattern === # Preface * 為因應環境、需求改變,工程師會替 Application 不斷增添新功能。然而這樣的做法卻會讓 Application 逐漸「特化」導致最後無法普遍應用在異質環境。 顯然隨時間演進會引發幾種問題: * Application 越趨複雜、難以偵錯,最後變成巨型單體 (monolith) * Git tree 出現許多分枝,管理困難 * 容器映像檔、程式碼重用性 (reusability) 降低 * 但正如微服務架構的概念一樣,適當的將主應用功能與新功能進行解耦,便能提高重用性與降低複雜度。以下將會為各位介紹邊車模式如何解決上述問題 # 邊車模式 * 不知道各位是否有印象,在看美劇或電影時常會看到有人騎哈雷時為了多載人會在旁邊外掛一輛車 ![](https://minio.mcl.math.ncu.edu.tw:443/hackmd/uploads/upload_1d7c5120030ec113256f0f4b878d0f70.png) * 整輛車的主體是位於中心的哈雷「負責前進」,而旁邊的側車「增加載物、載人的可能性」。 * 這正是邊車模式的概念來源: * 作為 Single-Node Pattern 的第一個設計模式,邊車模式的架構非常簡單 ![](https://minio.mcl.math.ncu.edu.tw:443/hackmd/uploads/upload_681acf4b4bac3cd46585cf08f9101ef7.png) * 它將數個容器 (原文寫兩個,但實際情況可能為多個) 置於同個 Container Group 內 (Kubernetes 內稱為 Pod),讓容器能夠一同分配 (coscheduled) 到同個運算節點上。 由於容器位處在同個 Pod 內,彼此間會共享 disk, network namespace 等等資源。而容器則被分成應用容器與邊車容器兩種,以下針對兩種容器分別作介紹。 # 容器分類 ## 應用容器 (Application Container) * 通常個人暱稱此類容器為主應用,原因在於該容器是負責主要業務邏輯之容器。 * 除一開始就採用微服務概念進行開發外,否則通常主應用會有幾種常見狀況: * Codebase 龐大且難以增加新功能 * 可以移植進容器但已無人維護的 Legacy 服務 * 所以當主應用因故無法進行再次開發時,透過增加邊車容器增加功能性就是個不錯的可行手段。 ## 邊車容器 (Sidecar Container) * 個人暱稱此類容器為輔應用,因為它透過不包含在主容器的功能,來擴增及改善主應用容器的功能性。 * 邊車容器本身並不負責主要業務邏輯,因此會盡量提高通用性使其能夠搭配不同的應用容器,減少維護不同容器映像檔的成本。 但通用性高低僅是作為參考並非鐵則,實際上仍需視現場狀況而定。 * 高通用性 * 透過組合搭配不同主應用實現多種可用性 * 受到通用性影響,少數情況可能無法完全發揮作用 * 低通用性 * 輔應用只對應單個主應用,可針對主應用客製化 * 過多的容器會造成管理及開發上的困擾 * 耦合性高 # 如何設計邊車容器以提高「模組性」與「可重用性」 * 文章內提到三種方式來提高「模組性」與「可重用性」: * Parameterizing your containers * Creating the API surface of your container * Documenting the operation of your container ## 參數化容器 (Parameterized Containers) * 顧名思義就是將容器可調整的變數參數化,讓第三者使用時能夠根據不同場景進行調整。 * 常見的參數化方式有兩種 * CLI flag * 環境變數 * 兩種方式都可以,但個人偏好採用環境變數的方式進行參數調整,因為較為簡單、明瞭。 * 以後面應用範例的 YAML 作為示範 (deploy.yaml), 透過修改環境變數,我們就可以將 proxy 的後方服務進行調整而無需更改映像檔本身。 ```yaml= - name: ssl-termination-proxy image: tachingchen/nginx-ssl-proxy env: - name: ENABLE_SSL value: "true" - name: TARGET_SERVICE value: "127.0.0.1:8080" ``` ## 制定容器 API 介面 (Define Each Container’s API) * 不管在營運過程中或是平常運作,容器本身都需要提供對外 API 介面以供外部存取、監控。比方說常用的 /healthz,只要容器提供該 API 就能立刻納入叢集的監控環節中。 * 一份定義完整的服務契約 (API Service Contract),能讓開發者理解該如何呼叫該容器,也可以在當介面出現異動時做出對應的程式修正。 值得注意的是,任何的 API 改動必須考慮到向後兼容性 (backward compatibility),這是由於叢集運作中很可能發生不同版本的容器同時存在的狀況,導致服務出現異常。 * 以 REST API 為例,若需要改變 API (breaking API change) 會建議加入版本號 (Versioning)的設計。 :::info https://w.x.y.z/api/v1.0/search https://w.x.y.z/api/v2.0/search ::: * 如此一來便可確保進行版本升級時仍然能夠維持舊有服務的可靠性。 ## 撰寫容器相關文件 (Documenting Your Containers) * 前面兩者主要著重在提高可用性以及清楚的 API 介面能夠避免服務異常,第三點則是注重在撰寫容器的文件部分。 * 原文內的文件係指該容器的 Dockerfile,透過註解標明該容器的相關資訊,比如説: ```yaml= EXPOSE 8080 ``` * 若容器同時聽很多 port,只看到這麼一行可能會無法知道是屬於哪個服務使用,加入註解或LABEL都能增加 Dockerfile 的可讀性。 ```yaml= # Main web server runs on port 8080 EXPOSE 8080 LABEL "org.label-shcema.version"="1.0.3" ``` * 另外,也可以利用ENV來為容器設定環境變數值,比方說: ## 應用範例: 為服務增加 HTTPS (SSL Termination Proxy) * 假設我們有個已經無人維護 legacy web server 且僅支援 HTTP 協定,由於安全性的需求必須要改採 HTTPS。在不更動程式的原則下我們便可以透過邊車模式 來為該服務增加 HTTPS 功能。 ![](https://minio.mcl.math.ncu.edu.tw:443/hackmd/uploads/upload_9f69efc6710ca2cee847675e2521a6d6.png) * 首先,我們先產生所需要的 ssl cert files ```shell= $ openssl dhparam -out dhparam.pem 512 $ openssl req -x509 -nodes -days 365 -newkey rsa:512 -keyout nginx.key -out nginx.crt ``` * 建立 secret 跟 deployment ```shell= $ kubectl create secret generic ssl-crt --from-file=dhparam.pem --from-file=nginx.crt--from-file=nginx.key $ kubectl apply -f sidecar-pattern/ssl-termination-proxy/deploy.yaml ``` * 建立 port-forward ```shell= $ kubectl port-forward <ssl-termination-proxy-pod-name> 8080:443 ``` * 到瀏覽器造訪 https://127.0.0.1:8080 就能看到我們成功加上 HTTPS 連線摟! ![](https://minio.mcl.math.ncu.edu.tw:443/hackmd/uploads/upload_dca03d97d40e03e1b4797c7f67e71374.png)