第一個單節點模式是邊車模式(sidecar pattern),由兩個容器組成:

  • 應用容器(application container):包含應用的核心邏輯,沒有它應用就不存在。
  • 邊車容器(sidecar container):角色是增強與改善應用容器,且通常應用容器對此毫無所悉。最簡單的用途,是為難以修改的容器添加功能。

兩個容器透過原子性的容器群組(如 Kubernetes 的 Pod API 物件)被共同排程到同一台機器,並共享許多資源:部分檔案系統、主機名稱與網路,以及其他多種命名空間。

圖 2-1:通用的邊車模式

範例:為老舊服務加上 HTTPS#

  • 情境:一個多年前建構的老舊(legacy)Web 服務,當年公司不重視內網安全,只支援未加密的 HTTP;如今公司因資安事件強制所有網站使用 HTTPS。雪上加霜的是,這個應用的原始碼是用已經無法運作的舊版建構系統編譯的——把它容器化很簡單(舊 Linux 發行版加上新核心即可跑起來),但要修改程式碼支援 HTTPS 極為困難。
  • 邊車解法:
    • 把老舊服務設定為只在 localhost(127.0.0.1)上服務,讓只有共享本地網路的服務才能存取它。
    • 加入一個 nginx 邊車容器:它與老舊應用共處同一個網路命名空間,因此能存取 localhost 上的服務;同時它在 Pod 的對外 IP 上終結 HTTPS 流量(SSL termination),再把流量代理給老舊應用。
    • 未加密流量只經過容器群組內部的本地迴路(loopback)介面,資安團隊可以接受;團隊也不必想辦法重建應用就完成了現代化。

圖 2-2:HTTPS 邊車

用邊車做動態設定#

  • 許多既有應用假設設定檔(純文字、XML、JSON 或 YAML)存在於檔案系統上並從那裡讀取;但在雲原生環境中,用 API 動態推送設定更為實用——不必手動登入每台伺服器下指令改檔案,也更容易加上回滾(rollback)等自動化,使設定與重新設定更安全、容易。
  • 邊車解法:serving 應用容器 + **設定管理器(configuration manager)**邊車,兩者在 Pod 中共享一個目錄,設定檔就放在那裡。
    • 老舊應用啟動時照常從檔案系統載入設定。
    • 設定管理器啟動後檢查設定 API,比對本地檔案與 API 中儲存的設定;若有差異,就下載新設定到本地檔案系統,並通知老舊應用重新載入。
    • 通知機制因應用而異:有的應用會監看設定檔變化,有的回應 SIGHUP 訊號;極端情況下設定管理器可送 SIGKILL 終止應用,讓容器編排系統重啟它以載入新設定。

圖 2-3:以邊車管理動態設定的範例

模組化的應用容器#

  • 邊車不只是為了「不想再改原始碼的老舊應用」而存在——另一大優點是邊車元件本身的模組化與重用
  • 例子:任何真實應用都需要除錯與管理功能,例如像 top 指令那樣列出容器內各程序的資源使用狀況。
    • 函式庫做法:要求每位開發者實作 HTTP /topz 介面,或提供各語言的外掛讓開發者連結進應用。但開發者必須主動採用,組織也得為每種語言各實作一次——除非極度嚴謹,否則必然導致語言間的差異,新語言也得不到支援。
    • 邊車做法:把 topz 功能部署為與應用容器共享 PID 命名空間的邊車容器,它能檢視所有執行中的程序並提供一致的介面;還可以透過編排系統自動把它加到所有部署的應用上,確保整個基礎設施有一致的工具集。

模組化容器與自行把程式碼寫進應用之間存在取捨:函式庫式的通用方案總是比較不貼合應用的特殊需求,可能在大小或效能上較差、API 也可能需要調整。作者把這比喻為成衣與訂製服——訂製服永遠更合身,但更貴也更慢。對大多數人來說,通用方案是合理選擇;若應用對效能有極端要求,永遠可以改採手寫方案。

動手做:部署 topz 容器

先用 Docker 部署任一應用容器:

docker run -d <my-app-image>
# 得到 <container-hash-value>,也可用 docker ps 查詢

假設容器 ID 已存入環境變數 APP_ID,在同一個 PID 命名空間中執行 topz 容器:

docker run --pid=container:${APP_ID} \
    -p 8080:8080 \
    brendanburns/topz:db0fa58 \
    /server --addr=0.0.0.0:8080

之後造訪 http://localhost:8080/topz 即可看到應用容器內所有程序及其資源使用狀況。若應用容器也用 8080 埠,需調整邊車的服務埠。這個邊車可以與任何既有容器搭配,輕鬆透過 Web 介面觀察容器資源使用。

用邊車打造簡易 PaaS#

  • 邊車不只能做適配與監控,還能以簡化、模組化的方式實作應用的完整邏輯

  • 例子:圍繞 Git 工作流建立簡易平台即服務(PaaS, platform as a service)——推送新程式碼到 Git 儲存庫,程式碼就自動部署到運行中的伺服器:

    • 主應用容器:Node.js Web 伺服器,透過 nodemon 工具在檔案更新時自動重新載入。

    • 邊車容器:與主容器共享檔案系統,跑一個簡單的迴圈將檔案系統與 Git 儲存庫同步:

      #!/bin/bash
      
      while true; do
        git pull
        sleep 10
      done
  • 兩者共同排程部署後,每次推送新程式碼,邊車就自動更新、伺服器就自動重載。(腳本刻意保持簡單以利閱讀,實務上可改為拉取特定分支等。)

圖 2-4:簡易的邊車式 PaaS

為模組化與可重用性設計邊車#

本章所有邊車範例的共同主題:每一個都是模組化、可重用的產出物。邊車要成功,就必須能跨各式各樣的應用與部署重用——而這種模組化跟高品質軟體開發一樣,需要專注與紀律,特別是三個面向:參數化容器、定義容器的 API、為容器撰寫文件

參數化容器#

  • 把容器想成程式中的一個函式:它有幾個參數?每個參數都是讓通用容器客製化到特定情境的輸入。

  • 例如前述的 SSL 邊車至少需要兩個參數:SSL 憑證名稱、localhost 上「老舊」應用伺服器的埠號——沒有這些參數,很難想像它能被廣泛重用。

  • 傳遞參數有兩種方式:環境變數或命令列。作者偏好環境變數

    docker run -e=PROXY_PORT=8080 -e=CERTIFICATE_PATH=/path/to/cert.crt ...
  • 容器內部通常用簡單的 shell 腳本讀取這些環境變數,據以調整設定檔(如 nginx.conf)或參數化底層應用。

定義每個容器的 API#

  • 參數化等於為容器定義了一個「函式」,這顯然是容器 API 的一部分;此外還包括容器對其他服務的呼叫、容器提供的 HTTP 等傳統 API——容器與外界互動的所有面向都是這個可重用容器的 API
  • 如同微服務世界,這些「微容器」依賴 API 確保主應用容器與邊車之間的乾淨分離,也確保後續版本釋出時所有使用者都能繼續正常運作;乾淨的 API 也讓邊車開發者能更快前進,因為他們對所提供的服務有清楚的定義(最好還有單元測試)。

破壞性的 API 變更不一定像「改參數名稱」那麼明顯。以設定管理邊車的 UPDATE_FREQUENCY 參數為例:

  • 把名稱改成 UPDATE_PERIOD——明顯的破壞性變更。
  • 原本接受秒數,後來改成接受 10 m5 s 這類字串——舊值(如 10)無法解析,也是破壞性變更。
  • 甚至「無單位的值從解析為秒改為解析為毫秒」——雖然不會報錯,但會讓設定檢查頻率暴增、雲端設定伺服器負載大增,仍然是破壞性變更

為容器撰寫文件#

  • 如同軟體函式庫,真正有用的關鍵是說明如何使用;做得再靈活可靠,沒人會用就沒有價值。容器映像檔的正式文件工具不多,但有一些最佳實踐,主要落在 Dockerfile 裡:

    • EXPOSE:標示映像檔監聽的埠,並加上註解說明該埠上跑的是什麼:

      # Main web server runs on port 8080
      EXPOSE 8080
    • ENV:為參數設定預設值,同時記錄其用途:

      # The PROXY_PORT parameter indicates the port on localhost to redirect
      # traffic to.
      ENV PROXY_PORT 8000
    • LABEL:加上維護者信箱、網頁、版本等中繼資料,名稱建議採用 Label Schema 專案建立的共享標籤分類法,讓社群工具能直接利用這些中繼資訊來視覺化、監控與正確使用應用:

      LABEL "org.label-schema.vendor"="name@company.com"
      LABEL "org.label.url"="http://images.company.com/my-cool-image"
      LABEL "org.label-schema.version"="1.0.3"

本章小結#

  • 邊車模式在單一機器上組合容器:邊車容器增強並擴展應用容器的功能。
  • 邊車可用於在「修改應用成本過高」時更新既有的老舊應用,也可用於建立標準化常見功能的模組化工具容器——後者能在大量應用中重用,提升一致性並降低每個應用的開發成本。