大使模式(ambassador pattern):由大使容器仲介(broker)應用容器與外部世界的互動。與其他單節點模式相同,兩個容器緊密共生、被排程到同一台機器上。
大使模式的價值有二:
- 與其他單節點模式一樣,模組化、可重用的容器本身就有價值——關注點分離讓容器更容易建構與維護。
- 大使容器可以與多個不同的應用容器搭配重用——程式碼一次寫好、多處使用,開發更快,實作也因為「建一次、用在許多情境」而更一致、品質更高。

圖 3-1:通用的大使模式
用大使為服務分片#
- 當儲存層的資料大到單一機器無法承載,就需要分片(sharding):把儲存層切成多個互斥的片段,各由一台機器承載。(本章只談「讓既有服務與已存在的分片服務對話」的單節點模式;多節點分片服務的設計詳見第 6 章。)
- 部署分片服務時的問題:如何與儲存資料的前端或中介層程式碼整合?需要有邏輯把特定請求路由到特定分片,但既有程式碼往往假設連向單一儲存後端,難以事後塞入分片客戶端;分片也讓開發環境(通常只有一個分片)與正式環境(通常很多分片)難以共享設定。

圖 3-2:通用的分片服務
兩種做法的取捨:
- 分片邏輯放在服務端:分片服務自帶無狀態負載平衡器,把流量導向正確分片——實質上是「作為服務的分散式大使」。客戶端不需要大使,但分片服務的部署更複雜。
- 客戶端整合單節點大使:由大使把流量路由到正確分片。客戶端部署稍微複雜,但簡化了分片服務的部署。
兩種做法都是合理的,選擇取決於應用的具體情況——例如團隊分界落在架構的哪裡、哪些部分是自己寫程式碼、哪些只是部署現成軟體。
- 客戶端大使的做法:引入一個包含所有路由邏輯的大使容器,前端或中介層應用只需連向「看起來像單一儲存後端」的 localhost 服務;實際上這是分片大使代理(sharding ambassador proxy),它接收應用的所有請求、轉發到正確的儲存分片、再把結果回傳。
- 結果是乾淨的關注點分離:應用容器只知道「要連 localhost 上的儲存服務」,大使只包含分片所需的程式碼;大使還能跨應用重用,甚至直接採用現成的開源實作。
動手做:實作分片 Redis(twemproxy)
以 Redis 作為快取,先用 StatefulSet 在 Kubernetes 叢集部署分片 Redis(StatefulSet 會給每個分片唯一的 DNS 名稱,方便設定代理):
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: sharded-redis
spec:
serviceName: "redis"
replicas: 3
template:
metadata:
labels:
app: redis
spec:
terminationGracePeriodSeconds: 10
containers:
- name: redis
image: redis
ports:
- containerPort: 6379
name: redis存成 redis-shards.yaml 後以 kubectl create -f redis-shards.yaml 部署,會建立三個 Redis 容器(kubectl get pods 可見 sharded-redis-[0,1,2])。
再建立 Headless Service 為副本產生 DNS 名稱:
apiVersion: v1
kind: Service
metadata:
name: redis
labels:
app: redis
spec:
ports:
- port: 6379
name: redis
clusterIP: None
selector:
app: redis部署後就有 sharded-redis-0.redis、sharded-redis-1.redis 等 DNS 條目,可用來設定 twemproxy——Twitter 開發、開源的輕量高效能 memcached/Redis 代理:
redis:
listen: 127.0.0.1:6379
hash: fnv1a_64
distribution: ketama
auto_eject_hosts: true
redis: true
timeout: 400
server_retry_timeout: 2000
server_failure_limit: 1
servers:
- sharded-redis-0.redis:6379:1
- sharded-redis-1.redis:6379:1
- sharded-redis-2.redis:6379:1代理在 localhost:6379 服務 Redis 協定,讓應用容器可以存取大使。設定用 ConfigMap 部署(kubectl create configmap --from-file=nutcracker.yaml),最後定義 Pod:
apiVersion: v1
kind: Pod
metadata:
name: ambassador-example
spec:
containers:
# This is where the application container would go, for example
# - name: nginx
# image: nginx
# This is the ambassador container
- name: twemproxy
image: ganomede/twemproxy
command:
- nutcracker
- -c
- /etc/config/nutcracker.yaml
- -v
- 7
- -s
- 6222
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: twem-config這個 Pod 定義了大使;再注入使用者自己的應用容器即完成。
用大使做服務仲介#
- 要讓應用可攜(portable)於多種環境(公有雲、實體資料中心、私有雲),主要挑戰之一是服務發現(service discovery)與設定。例如依賴 MySQL 的前端:在公有雲上 MySQL 可能是 SaaS 服務,在私有雲則可能得動態啟一台跑 MySQL 的虛擬機或容器。
- 可攜的應用必須知道如何檢視自身環境、找到正確的 MySQL 服務來連線;執行這種發現與連結的系統,通常稱為服務仲介(service broker)。
- 大使解法:應用永遠只連 localhost 上的服務實例(如 MySQL);由服務仲介大使負責檢視環境、仲介出正確的連線。

圖 3-3:服務仲介大使建立 MySQL 服務
用大使做實驗或請求分流#
- 請求分流(request splitting):讓一部分請求不由主要正式服務處理,而是導向服務的另一個實作。最常見的用途是對新的 Beta 版做實驗,判斷新版軟體的可靠性與效能是否與現行版本相當。
- 另一種用法是分接(tee)流量:所有流量同時送往正式系統與尚未上線的新版;回應以正式系統為準,分接服務的回應則被忽略——常用來在不影響正式使用者的前提下,對新版模擬正式負載。
- 大使解法:應用容器照樣連 localhost;大使容器接收請求、把請求代理給正式與實驗系統,再把正式系統的回應當作自己的處理結果回傳。
- 這種關注點分離讓每個容器的程式碼精簡聚焦,請求分流大使也能在各種應用與情境中重用。
動手做:實作 10% 實驗(nginx)
用 nginx 作為大使實作請求分流(此為 HTTP 設定,可輕易改為 HTTPS):
worker_processes 5;
error_log error.log;
pid nginx.pid;
worker_rlimit_nofile 8192;
events {
worker_connections 1024;
}
http {
upstream backend {
ip_hash;
server web weight=9;
server experiment;
}
server {
listen localhost:80;
location / {
proxy_pass http://backend;
}
}
}兩個設定重點:
ip_hash:確保同一位使用者不會在實驗版與主站之間來回跳動,維持一致的使用體驗。weight=9:90% 流量送往既有主應用,10% 導向實驗版。
設定同樣以 ConfigMap 部署(kubectl create configmaps --from-file=nginx.conf)。注意必須先建立 web 與 experiment 兩個 Service——nginx 找不到要代理的服務就不肯啟動:
# This is the 'experiment' service
apiVersion: v1
kind: Service
metadata:
name: experiment
labels:
app: experiment
spec:
ports:
- port: 80
name: web
selector:
# Change this selector to match your application's labels
app: experiment
---
# This is the 'prod' service
apiVersion: v1
kind: Service
metadata:
name: web
labels:
app: web
spec:
ports:
- port: 80
name: web
selector:
# Change this selector to match your application's labels
app: web最後把 nginx 部署為 Pod 內的大使容器:
apiVersion: v1
kind: Pod
metadata:
name: experiment-example
spec:
containers:
# This is where the application container would go, for example
# - name: some-name
# image: some-image
# This is the ambassador container
- name: nginx
image: nginx
volumeMounts:
- name: config-volume
mountPath: /etc/nginx
volumes:
- name: config-volume
configMap:
name: experiment-config之後即可在 Pod 中加入第二個(第三、第四個)容器來利用這個大使。
與分片服務的討論相同,實驗框架也可以部署為應用前方的獨立微服務,而非整合進客戶端 Pod。代價是多了一個需要維護、擴展、監控的服務——若實驗會是架構中的長期元件,這可能值得;若只是偶爾使用,客戶端大使更合理。