作者:陸胤任
背景
在 AI 大爆發的時代,已經有非常多的 AI 助手,結合 RAG 通過智能問答幫助用户解答問題。單純地依靠智能問答幫助客户自助解答是遠遠不夠的,我們需要讓 AI 助手能夠直接調用已有的豐富接口,朝着更強大的智能體演進。我們選用當下最為火熱,且已逐步成為標準的 MCP 作為模型和接口之間通信的傳輸協議。關於 MCP,已有非常多的介紹文章,本文不再贅述。
在企業對外服務的場景下,MCP Server 需要解決以下幾個問題:
- 在服務的多實例高可用場景下,使用 SSE 通信方式如何維護 session;
- 如何做到動態更新 MCP 工具 Prompt,做到快速更新&調試&驗證;
- 租户隔離的雲服務場景下如何對用户的工具調用進行鑑權。
Higress 可以很好地解決上面的問題 1,同時還有完善的運維監控體系,可視化易操作的控制枱界面。為了解決問題 2,我們引入了 Nacos 負責註冊後端服務以及管理維護 MCP 工具的元數據等信息。在整個 MCP 服務中,Higress 擔任 MCP Proxy 的角色,Nacos 擔任 MCP Registry 的角色。對於問題 3 租户隔離問題,會在下面鑑權章節中進行詳細説明。
Higress 和 Nacos 都是雲原生的應用,在部署方面,自然選擇使用 K8s 集羣進行雲原生部署。同時很多企業有自己的專屬生產網絡環境,一般和外網不通,因此本文會圍繞如何利用社區版本的 Higress 和 Nacos(Apache-2.0 開源協議)進行私有化部署。因為內部環境的限制,我們沒有辦法直接通過 Helm 操作 K8s 集羣進行部署,因此本文會圍繞如何基於 Higress 和 Nacos 的 docker 鏡像在 K8s 集羣上進行分角色部署。
通過這套自建的網關服務,使用配置即可實現零代碼擴展 Tool,新應用的註冊、應用下面工具的擴展、工具 prompt 更新驗證都能通過服務集成的可視化控制枱,更新發布配置快速完成,接入方式極其簡單!更新驗證極其快速! 同時利用 Nacos 的命名空間能力可以做到服務和工具集的隔離,給不同的用户提供不同的 MCP 工具集。
私有化部署
Higress
Higress 支持三種部署方式:Helm、docker compose 和基於 all-in-one 的 docker 鏡像進行部署。Higress 官方推薦使用 Helm 的方式進行生產環境的部署,將依賴的模塊部署在不同的 pod 上。而因上述環境原因,這裏選擇使用第三種基於 all-in-one 的 docker 鏡像 Dockerfile [ 1] 進行部署,將 Higress 依賴的組件以進程的方式部署在同一 pod 上面,通過多副本的方式實現服務高可用,也實現了對 K8s 集羣 Ingress 的無侵入式部署。
我們先嚐試直接引用 docker 鏡像進行部署時,會報 WASM 的插件錯誤,查看報錯信息是通過 oci 地址去下載 WASM 插件的時候出現了問題。同時 Higress 實現 MCP 功能也依賴了 WASM 插件,這是一個繞不開的問題。
FROM higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/all-in-one:latest
WASM 插件獨立部署
Higress 的 plugin-server [ 2] 項目就是為了“解決私有化部署 Higress 網關時拉取插件的痛點,優化了插件的下載與管理效率”,使 Higress 通過 http 的方式去下載獨立部署的插件庫,而不是通過 oci 去訪問外部公開倉庫,避免因網絡問題導致插件拉取不下來。解決過程主要分為以下三個步驟:
1)私有化部署 plugin-server
FROM higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/plugin-server:1.0.0
2)為 plugin-server 集羣申請 K8s Service(Cluster IP)
apiVersion: v1
kind: Service
metadata:
name: higress-plugin-server
namespace: higress-system
labels:
app: higress-plugin-server
higress: higress-plugin-server
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: higress-plugin-server
higress: higress-plugin-server
K8s 集羣內置的 DNS 為此創建的域名解析記錄的格式為 <service-name>.<namespace>.svc.cluster.local。
在沒有 K8s 的場景下也可以為 plugin-server 集羣申請內網 VIP 或者 SLB 做好服務發現和負載均衡。
3)修改 Higress 內置插件下載地址
依照 github 中的示例,在基於 Higress 鏡像的項目 Dockerfile 中聲明插件的下載地址。這裏有個地方需要注意下,readme 中給出的示例是環境變量的格式。
在 Dockerfile 中聲明需要轉義一下,\${name}/\${version} 的形式才可以被正確解析。
...
# 模版
ENV HIGRESS_ADMIN_WASM_PLUGIN_CUSTOM_IMAGE_URL_PATTERN=http://[申請的k8s service地址]/plugins/\${name}/\${version}/plugin.wasm
# mcp wasm 插件下載地址
ENV MCP_SERVER_WASM_IMAGE_URL=http://[申請的k8s service地址]/plugins/mcp-server/1.0.0/plugin.wasm
...
配置完獨立的插件 HTTP 下載地址後重新部署,在服務器上可以看到 8080 端口以及 8443 端口可以被正常監聽,説明 Higress 具備代理和網關功能的核心數據面組件已經可以正常服務了。
解決完 WASM 插件下載問題,基於 docker 鏡像的 Higress 服務就可以被成功拉起並運行了。只不過基於這種模式部署的每個 pod 都是獨立、對等、包含全部組件、功能完整的 Higress 服務,需要通過多副本的方式實現高可用。
這種部署模式下,通過 Higress 自身集成的控制枱去運維服務&更改配置是不現實的,只能操作一台實例的配置變更,無法讓實例間進行配置同步。因此在這種模式下的缺點是,只能通過在項目代碼中維護配置文件,需要更改時走發佈流程,將配置發佈到每台實例上面。不過在我們這個場景下,需要變更配置的情況不多。
粘性會話
在 MCP SSE 通信方式下,天然需要解決粘性會話的問題,Higress 基於 Redis 幫我們解決了這個問題。提前部署好 Redis 實例之後,打開 Higress 的 MCP 功能,並將 Redis 配置更新進去,重新部署一下就可以使用 MCP 的功能了。
...
data:
higress: |-
mcpServer:
enable: true
sse_path_suffix: /sse
redis:
address: xxx.redis.zhangbei.rds.aliyuncs.com:6379
username: ""
password: "xxx"
db: 0
...
這份配置文件可以維護在自己的基於 Higress 鏡像的項目中,在部署的時候將配置文件 COPY 到指定目錄(這種部署模式下,所有的配置文件都應該這麼做)。
...
# custom config
COPY config/configmaps/higress-config.yaml /data/configmaps/higress-config.yaml
COPY config/mcpbridges/default.yaml /data/mcpbridges/default.yaml
COPY config/secrets/higress-console.yaml /data/secrets/higress-console.yaml
RUN chmod +x /data/configmaps/higress-config.yaml && \
chmod +x /data/secrets/higress-console.yaml && \
chmod +x /data/mcpbridges/*
...
當整個 MCP 網關搭建完並使用的時候,在 redis 上通過 PSUBSCRIBE mcp-server-sse:* 命令可以看到如下的調用信息。
自定義構建鏡像
官方構建出來的鏡像一般會要求體積小,滿足最小運行要求,所以很多功能其實並不集成在 Higress 的鏡像中。如果你的企業有自己約定的通用鏡像,或者是想在原本的基礎上集成一些新的功能,如使用阿里雲的 SLS、雲監控等功能,就需要根據 all-in-one 鏡像的 Dockerfile 內容進行自定義構建。這裏有個注意的點是,Higress 中的 envoy 模塊要求的 glibc 是 2.18 及以上版本。
其實只需要將 Higress 的 Dockerfile 文件內容移植過來就行,然後再聲明下獨立部署的 WASM 插件下載地址,就能實現基於指定鏡像進行 Higress 自定義構建打包部署了。
Higress 服務搭建好後,就可以走對外公網訪問的流程了:(1)一個是綁定 8001 端口,通過 Higress 控制枱進行查看相關配置的域名,限制為只允許內網訪問。注:這種模式下無法通過控制枱直接去更改配置;(2)另一個是綁定 8080 端口,對外提供 MCP 網關服務的域名。
完整的 Dockerfile 如下:
FROM [企業內部基礎鏡像]
# 下面為 Higress all-in-one dockerfile中的內容
ARG HUB=higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
...
# 模版
ENV HIGRESS_ADMIN_WASM_PLUGIN_CUSTOM_IMAGE_URL_PATTERN=http://[申請的k8s service地址]/plugins/\${name}/\${version}/plugin.wasm
# mcp wasm 插件下載地址
ENV MCP_SERVER_WASM_IMAGE_URL=http://[申請的k8s service地址]/plugins/mcp-server/1.0.0/plugin.wasm
...
# 注意 dockerfile 中會去 github 下載對應處理器架構下的 yq 模塊,企業內網環境下可以提前下載下來
COPY ./yq_linux_[arch] /usr/local/bin/yq
...
# custom config
COPY config/configmaps/higress-config.yaml /data/configmaps/higress-config.yaml
COPY config/mcpbridges/default.yaml /data/mcpbridges/default.yaml
COPY config/secrets/higress-console.yaml /data/secrets/higress-console.yaml
RUN chmod +x /data/configmaps/higress-config.yaml && \
chmod +x /data/secrets/higress-console.yaml && \
chmod +x /data/mcpbridges/*
...
Nacos
Nacos 的部署相對簡單,除了通過 kubectl 或者 nacos-operator 工具直接操作 K8s 集羣部署外,還可以直接基於 nacos-server 的鏡像進行部署 Dockerfile [ 3] 。因上文提到的內部環境問題,我們這裏選擇基於 nacos-server 的鏡像,將服務部署於 K8s 集羣上面。
FROM nacos-registry.cn-hangzhou.cr.aliyuncs.com/nacos/nacos-server:latest
集羣模式部署
Nacos 集羣模式下使用的一致性協議是基於 Raft 實現的,因此最小需要部署 3 台實例。
在引用 nacos-server 鏡像的 dockerfile 中,聲明 cluster 的部署模式。我們查看 nacos 的啓動腳本,發現在 peer-finder(插件)目錄不存在的情況下,如果定義了 $NACOS_SERVERS 變量,會將 $NACOS_SERVERS 變量中的值寫入 CLUSTER_CONF 文件的默認路徑是 /home/nacos/conf/cluster.conf,其中定義的就是 Nacos 集羣的靜態成員地址列表,它在集羣首次啓動時會被讀取,用於告知每個節點“鄰居”在哪,從而讓它們能夠互相發現、建立連接,並初始化 Raft 一致性協議。
...
PLUGINS_DIR="/home/nacos/plugins/peer-finder"
function print_servers() {
if [[ ! -d "${PLUGINS_DIR}" ]]; then
echo "" >"$CLUSTER_CONF"
for server in ${NACOS_SERVERS}; do
echo "$server" >>"$CLUSTER_CONF"
done
else
bash $PLUGINS_DIR/plugin.sh
sleep 30
fi
}
...
因此我們可以在 Dockerfile 中維護當前集羣下的 [實例 IP:端口] 列表,供 Nacos 集羣啓動時讀取並初始化。
...
ENV MODE=cluster
ENV NACOS_AUTH_TOKEN=xxx
ENV NACOS_AUTH_IDENTITY_KEY=xxx
ENV NACOS_AUTH_IDENTITY_VALUE=xxx
ENV NACOS_SERVERS="10.0.0.1:8848 10.0.0.2:8848 10.0.0.3:8848"
# nacos 用户名密碼
ENV NACOS_USERNAME=xxx
ENV NACOS_PASSWORD=xxx
...
實例間動態發現
上面這種固定 IP 列表的方式缺點是顯而易見的。它是一個靜態的配置,當出現集羣的擴縮容時,實例是沒有辦法自動去更新成員 IP 列表的,需要手動修改併發布,整個過程非常繁瑣,嚴重情況下可能會影響線上服務的穩定性;且在雲原生容器化背景下,IP 並不是固定的,隨時有可能會因為故障遷移而改變 IP,維護靜態 IP 列表與雲原生的理念背道而馳。線上生產是完全不推薦這種方式的。
再回到上面 docker-startup.sh 腳本,可以通過 peer-finder 插件來實現集羣間實例的發現,取代手動維護 cluster.conf 文件。peer-finder 插件運行依賴於 K8s 集羣 Headless Service 域名,會去執行類似於 nslookup 命令查找 Service 下面的所有健康 Pod 的 IP 列表,類比於服務發現的能力 [ 4] ,這樣就不用再手動去維護實例 IP 列表。
但是 peer-finder 的運行依賴於 StatefulSet 的實例部署模式,需要每個實例有固定的實例名。因為我們內部環境的限制,我們現在部署的都是無狀態的實例,所以沒有辦法通過 peer-finder 來做這個事情。但是我們可以參照 peer-finder 腳本的實現思路,來自己寫一個啓動腳本。
1)首先為 Nacos 集羣申請 Headless 的 Service。
apiVersion: v1
kind: Service
metadata:
name: nacos-headless
namespace: mcp-nacos
labels:
app: mcp-nacos
nacos: mcp-nacos
spec:
clusterIP: None
ports:
- name: peer-finder-port
port: 8848
protocol: TCP
targetPort: 8848
selector:
app: mcp-nacos
sessionAffinity: None
type: ClusterIP
2)這裏修改下 nacos-docker 的啓動腳本,提供一個簡單的實現。(僅供參考)
...
原docker-startup.sh內容
...
# 新增內容
# 註釋掉 JAVA啓動命令
# exec $JAVA ${JAVA_OPT}
export JAVA_OPT # export JAVA 啓動參數,方面下面讀取
HEADLESS_SERVICE_FQDN="xxx.svc.cluster.local"
CLUSTER_CONF_FILE="/home/nacos/conf/cluster.conf"
UPDATE_SCRIPT="/home/nacos/bin/update-cluster.sh" # 原子更新腳本
NACOS_START_CMD="$JAVA $JAVA_OPT"
# 1. 動態創建 update-cluster.sh 腳本
cat > ${UPDATE_SCRIPT} << 'EOF'
#!/bin/bash
set -e
NACOS_PORT=${NACOS_APPLICATION_PORT:-8848}
CLUSTER_CONF_FILE="/home/nacos/conf/cluster.conf"
TMP_CONF_FILE="/home/nacos/conf/cluster.conf.tmp"
> "${TMP_CONF_FILE}"
# 從標準輸入讀取 nslookup 的原始輸出
awk '
/^Name:/ { flag=1; next }
flag && /^Address:/ { print $2; flag=0 }
' | while IFS= read -r ip; do
if [ -n "$ip" ]; then
echo "${ip}:${NACOS_PORT}" >> "${TMP_CONF_FILE}"
fi
done
# 排序以確保文件內容的一致性,避免不必要的更新
sort -o "${TMP_CONF_FILE}" "${TMP_CONF_FILE}"
# 只有在新舊配置不同時才執行更新
# 檢查舊文件是否存在
if [ ! -f "${CLUSTER_CONF_FILE}" ] || ! cmp -s "${TMP_CONF_FILE}" "${CLUSTER_CONF_FILE}"; then
echo "[$(date)][update-script] Peer list changed. Updating config."
mv "${TMP_CONF_FILE}" "${CLUSTER_CONF_FILE}"
echo "[$(date)][update-script] cluster.conf updated:"
cat "${CLUSTER_CONF_FILE}"
else
rm "${TMP_CONF_FILE}"
fi
EOF
chmod +x ${UPDATE_SCRIPT}
# 2. 啓動前的初始化循環
MAX_INIT_RETRIES=30
RETRY_COUNT=0
MIN_PEERS=3 # 期望的集羣最小副本數量
echo "[INFO] Initializing cluster config. Waiting for at least ${MIN_PEERS} peers to be available..."
while true; do
# 直接將 nslookup 的輸出通過管道傳給更新腳本
nslookup "${HEADLESS_SERVICE_FQDN}" | ${UPDATE_SCRIPT}
# 檢查生成的配置文件行數
LINE_COUNT=$(wc -l < "${CLUSTER_CONF_FILE}")
if [ "${LINE_COUNT}" -ge "${MIN_PEERS}" ]; then
echo "[INFO] Initial cluster.conf is ready with ${LINE_COUNT} peers."
break
fi
RETRY_COUNT=$((RETRY_COUNT+1))
if [ "${RETRY_COUNT}" -gt "${MAX_INIT_RETRIES}" ]; then
echo "[WARN] Could not find ${MIN_PEERS} peers after ${MAX_INIT_RETRIES} retries. Starting with ${LINE_COUNT} peers found."
break
fi
echo "[INFO] Found ${LINE_COUNT} peers. Waiting for more... Retrying in 5 seconds."
sleep 5
done
# 3. 在後台啓動我們自己的監控循環
(
while true; do
sleep 15 # 每 15 秒檢查一次
echo "[$(date)][monitor] Checking for peer updates..."
nslookup "${HEADLESS_SERVICE_FQDN}" | ${UPDATE_SCRIPT}
done
) &
# 4. 啓動 Nacos 主進程
echo "[INFO] Starting Nacos server..."
exec sh -c "${NACOS_START_CMD}"
這樣我們 cluster.conf 文件中的成員 IP 列表就實現了自動更新。
線上生產環境還是推薦使用有狀態 StatefulSet 的部署模式,並結合 peer-finder 的能力實現實例間的互相發現。而不是用無狀態的實例,自己去寫腳本實現。後續我們也會升級到 StatefulSet 的模式進行部署。
配置外置 Mysql
在集羣部署模式下,就無法使用 Nacos 內置的不支持數據共享的 Derby 數據庫,需要配置外置的 Mysql 數據庫。提前部署好 Mysql 實例之後,按照 Nacos 中的 mysql-schema.sql [ 5] 數據庫配置文件將表初始化,再將 mysql 配置信息寫入 Dockerfile 中即可。
...
# mysql config
ENV SPRING_DATASOURCE_PLATFORM=mysql
ENV MYSQL_DATABASE_NUM=1
ENV MYSQL_SERVICE_HOST=xxx.mysql.zhangbei.rds.aliyuncs.com
ENV MYSQL_SERVICE_PORT=3306
ENV MYSQL_SERVICE_DB_NAME=nacos
ENV MYSQL_SERVICE_USER=xxx
ENV MYSQL_SERVICE_PASSWORD=xxx
...
在為 Nacos 做服務暴露的時候,只需要暴露 Nacos 控制枱的 8080 端口,且限制為只允許內網訪問即可。因為 Nacos 只是內部作為維護管理 MCP 工具元數據信息的 MCP Registry 使用,對用户側不感知;且 Higress 和 Nacos 都部署在內網的 K8s 集羣上面,內部通信通過 K8s 的 Service 即可,無需將 Nacos 的 8848 端口暴露給公網。
申請 K8s Service 供 Higress 使用
注意 Higress 拉取/訂閲 Nacos 中的配置會通過 gRPC 的方式調用,這裏的 Service 需要暴露 8848 和 9848 兩個端口給 Higress 使用。
apiVersion: v1
kind: Service
metadata:
name: pre-oss-mcp-nacos-endpoint
namespace: aso-oss-mcp-nacos
labels:
app: mcp-nacos
nacos: mcp-nacos
spec:
type: ClusterIP
ports:
- name: subscribe-port
port: 8848
protocol: TCP
targetPort: 8848
- name: grpc-port
port: 9848
protocol: TCP
targetPort: 9848
selector:
app: nacos
同理,如果想使用企業內部的鏡像,或者是想在原本的基礎上即成一些新的功能,如使用阿里雲的 SLS、雲監控等功能,也可根據 Nacos 的 Dockerfile 進行自定義構建部署。
鑑權
Higress 自身提供了豐富的鑑權 [6 ] 能力,如果你的企業本身就基於 Higress 搭建了自己的網關並使用了 Higress 提供的鑑權能力,這種場景下直接複用原來的方案即可。
另一種場景下,企業中會有多個服務 Provider,每個 Provider 有不同的鑑權方式。如下圖所示,某個服務提供者會通過攔截器對請求中攜帶的用户 Cookie 進行 RAM 鑑權;另一個服務提供者會通過 tengine lua 腳本對請求進行自定義鑑權;以及後續註冊的服務可能有其他的鑑權方式。
一方面,我們並不希望使用 Higress 的鑑權能力去覆蓋全部的鑑權場景,開發維護成本過高,我們優先考慮直接複用服務提供者已有的鑑權能力;另一方面,如果通過網關層鑑權需要將 AK 或者認證信息存放在 Higress 服務上,在安全層面也不是一個合適的做法。
這裏推薦的做法是直接在 MCP 工具調用的時候,將鑑權信息透傳給服務提供者,讓服務提供者完成鑑權。
MCP 驗證
根據文檔 [ 7] 中的操作示例,我們可以簡單做個全鏈路測試驗證。主要分為以下三步:
1)在 Nacos 中註冊服務,並配置 MCP 工具的元數據信息:
在 public 命名空間下,創建服務信息。
在機器上將自己的服務作為永久實例註冊進去。(這裏為了快速驗證黑屏登陸機器操作,線上生產環境還是須要白屏操作)
curl -X POST 'http://127.0.0.1:8848/nacos/v1/ns/instance?namespaceId=[namespace]&serviceName=[service_name]&groupName=[group_name]&ip=[服務域名]&port=[服務端口]&ephemeral=false'
註冊完之後,就能在 Nacos 控制枱上看到註冊的服務配置以及健康狀態。
接着在 Nacos 控制枱上配置 MCP 工具,添加一個簡單工具,可以選擇一個無參數 GET 接口,併發布。
{
"requestTemplate": {
"url": "/xxx/list.json",
"method": "GET",
"headers": [],
"argsToUrlParam": true
},
"responseTemplate": {
"body": "{{.}}"
}
}
2)在 Higress 中配置 MCP Nacos 的服務來源:
這裏為了快速測試關閉了 Nacos 的認證,線上環境建議開啓 Nacos 的認證。
3)在 Cursor/Cherry Studio 中配置對外暴露的 Higress 服務地址和 uri,即可使用 MCP 工具:
設計圖
容災架構
進入瀏覽器查看原圖:https://img.alicdn.com/imgextra/i2/O1CN0138v82b1L7vNY3RQdo_!!6000000001253-2-tps-6507-5451.png
在整個 MCP 網關中,通過 uri 來路由不同的 MCP 工具,實現工具的隔離。
邏輯模塊圖
時序圖
附錄:
[1] 基於 all-in-one 的 docker 鏡像
https://github.com/higress-group/higress-standalone/blob/main/all-in-one/Dockerfile
[2] higress-plugin-server
https://www.cnkirito.moe/higress-plugin-server/
[3] 基於 nacos-server 的鏡像進行部署
https://github.com/nacos-group/nacos-docker/blob/master/build/Dockerfile
[4] 腳本源碼
https://github.com/kmodules/peer-finder/blob/master/peer-finder.go
[5] mysql-schema.sql
https://github.com/alibaba/nacos/blob/develop/distribution/conf/mysql-schema.sql
[6] Higress 提供豐富鑑權能力
https://higress.cn/docs/latest/plugins/authentication/basic-auth/
[7] 基於 Nacos + Higress 的 MCP 開發新範式,手把手教程來了!
[8] Nacos 3.0 正式發佈:MCP Registry、安全零信任、鏈接更多生態
[9] 修改內置插件的鏡像地址
https://higress.cn/docs/latest/ops/how-tos/builtin-plugin-url/
[10] Nacos 集羣模式
https://nacos.io/docs/latest/manual/admin/deployment/deployment-cluster/