kong網關基于header分流灰度發布
在現代微服務架構中,灰度發布(Canary Release)已經成為一種常用且安全的上線策略。它允許我們將新版本的功能僅暴露給一小部分用戶,從而在保證系統穩定性的同時收集反饋、驗證效果、規避風險。而作為一款輕量高性能的 API 網關,Kong 提供了靈活的路由規則,使得我們可以通過配置請求頭、Cookie、路徑等條件,實現基于規則的流量分流。
本文將聚焦于一種實用的灰度發布方案:基于請求頭(Header)進行流量分發。我們將以 X-App-Version 等請求頭為判斷依據,將部分請求引導至灰度環境(如 backend-v2),其余流量仍然指向正式環境(backend-v1)。通過 Kong 的聲明式配置方式,可以輕松實現無數據庫、無插件依賴的灰度發布策略,特別適合前后端聯調、測試環境驗證以及輕量化場景。
本篇文章將從實際業務需求出發,介紹如何使用 Kong 網關進行權重分流的灰度發布,包括:
-
Kong 中實現header分流的配置;
-
如何基于 Docker Compose 部署一個 DB-less 模式的灰度發布環境;
-
配置示例講解:如何將v2流量導向新版本服務 backend-v2,v1保持在舊版本 backend-v1;
前置條件
節點規劃如下:
主機名 | 節點IP | 監聽端口 | 操作系統 |
---|---|---|---|
frontend | 192.168.73.11 | 8080 | Ubuntu 24.04 |
backend-v1 | 192.168.73.11 | 3001 | Ubuntu 24.04 |
backend-v2 | 192.168.73.11 | 3002 | Ubuntu 24.04 |
kong-gateway | 192.168.73.11 | 8000 | Ubuntu 24.04 |
已安裝 Docker 和 docker-compose工具。
無DB模式部署kong網關
創建kong.yml聲明式配置文件:
root@ubuntu:/data/apps/kong# cat kong.yml
_format_version: "3.0"services:- name: backend-serviceurl: http://backend-v1:3000- name: backend-service-grayurl: http://backend-v2:3000routes:- name: backend-route-grayservice: backend-service-graypaths:- /adminheaders:X-App-Version:- v2strip_path: false- name: backend-routeservice: backend-servicepaths:- /adminstrip_path: falseplugins:- name: corsconfig:origins:- "*"methods:- GET- POST- PUT- DELETE- OPTIONSheaders:- Accept- Authorization- Content-Type- X-Requested-With- X-App-Versionexposed_headers:- X-Custom-Headercredentials: truemax_age: 3600
配置參數說明:
好的,以下是你提供的這份 Kong 聲明式配置(kong.yml
)的逐項參數詳解,特別聚焦于灰度發布與 CORS 支持相關部分:
_format_version: "3.0"
- 說明:聲明配置文件的格式版本,Kong 3.x 需要為
"3.0"
。 - 作用:Kong 用來解析聲明式配置,必須寫。
services
定義了兩個后端服務(一個正式,一個灰度):
backend-service
- name: backend-serviceurl: http://backend-v1:3000
- name:服務名稱,用于 route 映射。
- url:實際后端地址,這里是正式環境
backend-v1
的 3000 端口。 - 備注:省略了
host/port/protocol
的寫法,直接用url
更簡潔。
backend-service-gray
- name: backend-service-grayurl: http://backend-v2:3000
- 同上,只不過這是灰度環境的服務地址(backend-v2)。
routes
定義了兩條路由規則,基于路徑和請求頭進行分流:
backend-route-gray
- name: backend-route-grayservice: backend-service-graypaths:- /adminheaders:X-App-Version:- v2strip_path: false
- name:路由名稱。
- service:綁定到灰度服務
backend-service-gray
。 - paths:匹配
/admin
路徑的請求。 - headers:匹配請求頭
X-App-Version: v2
的請求。 - strip_path:為
false
表示保留/admin
前綴,不會從請求 URL 中剝離。
backend-route
- name: backend-routeservice: backend-servicepaths:- /adminstrip_path: false
- 匹配
/admin
的默認路由,綁定到正式服務backend-service
。 - 沒有 header 限制,所有不匹配前面 route 的請求都會走這個。
?? 匹配順序重要:backend-route-gray
必須在前面,Kong 按順序匹配 route,先匹配成功的就執行,后面的不再判斷。
plugins
配置了全局的 CORS(跨域資源共享)插件:
- name: corsconfig:origins:- "*"methods:- GET- POST- PUT- DELETE- OPTIONSheaders:- Accept- Authorization- Content-Type- X-Requested-With- X-App-Versionexposed_headers:- X-Custom-Headercredentials: truemax_age: 3600
參數說明:
參數 | 說明 |
---|---|
origins | 允許哪些源(Origin)跨域訪問。* 表示全部允許 |
methods | 允許的 HTTP 方法 |
headers | 允許客戶端請求時攜帶的請求頭(包括自定義的如 X-App-Version ) |
exposed_headers | 響應中暴露給前端的響應頭 |
credentials | 是否允許攜帶 Cookie、認證信息等 |
max_age | 預檢請求(OPTIONS)結果緩存時間(秒) |
最終請求 http://kong網關/admin
:
- 如果帶
X-App-Version: v2
請求頭 → 進入 灰度服務 - 如果不帶或值不為
v2
→ 進入 正式服務 - 啟用了全局的 CORS 支持,適合 Web 前端跨域請求調用 API
創建docker外部網絡,打通kong網關與前后端服務網絡
docker network create app_network
創建部署kong網關的docker-compose.yaml
root@ubuntu:/data/apps/kong# cat docker-compose.yaml
name: 'kong-gateway'services:kong:image: kong:3.9.1container_name: kong-gatewayrestart: alwaysenvironment:KONG_DATABASE: "off"KONG_DECLARATIVE_CONFIG: /usr/local/kong/declarative/kong.yml# kong proxyKONG_PROXY_LISTEN: 0.0.0.0:8000KONG_PROXY_LISTEN_SSL: 0.0.0.0:8443# kong adminKONG_ADMIN_LISTEN: 0.0.0.0:8001, 0.0.0.0:8444 ssl# kong managerKONG_ADMIN_GUI_LISTEN: 0.0.0.0:8002, 0.0.0.0:8445 ssl# kong logsKONG_PROXY_ACCESS_LOG: /dev/stdoutKONG_PROXY_ERROR_LOG: /dev/stderrKONG_ADMIN_ACCESS_LOG: /dev/stdoutKONG_ADMIN_ERROR_LOG: /dev/stderrports:- "8000:8000" # Proxy- "8443:8443" # Proxy SSL- "8001:8001" # Admin API- "8444:8444" # Admin API SSL- "8002:8002" # Kong Manager- "8445:8445" # Kong Manager SSLvolumes:- .kong.yml:/usr/local/kong/declarative/kong.ymlnetworks:- app-nethealthcheck:test: ["CMD-SHELL", "kong health"]interval: 15stimeout: 10sretries: 3networks:app-net:external: truename: app_network
啟動kong網關
docker compose up -d
查看服務運行狀態
root@ubuntu:/data/apps/kong# docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
kong-gateway kong:3.9.1 "/docker-entrypoint.…" kong 31 seconds ago Up 31 seconds (healthy) 0.0.0.0:8000-8002->8000-8002/tcp, [::]:8000-8002->8000-8002/tcp, 0.0.0.0:8443-8445->8443-8445/tcp, [::]:8443-8445->8443-8445/tcp
root@ubuntu:/data/apps/kong#
訪問kong manager查看創建的轉發規則
Gateway Services
Routes
Plugins
部署前后端服務
示例 Canaryheader 是一個基于Kong網關和docker-compose的Web端灰度發布演示項目,支持基于百分比的流量灰度策略。
架構如下:
主要特性
- 前端:html/js,展示后端返回的版本信息
- 后端:Node.js (Express),分別為v1和v2版本
- Kong網關:流量分流、服務注冊、路由配置,支持基于header灰度
- 容器化:所有服務均為Docker容器
- 一鍵部署:docker-compose編排
目錄結構
canaryheader/
├── docker-compose.yml
├── backend-v1/
│ ├── Dockerfile
│ └── index.js # v1后端代碼
├── backend-v2/
│ ├── Dockerfile
│ └── index.js # v2后端代碼
├── frontend/
│ ├── Dockerfile
│ └── index.html
backend-v1/index.js
root@ubuntu:/data/git/canary-header-app# cat backend-v1/index.js
const express = require('express');
const app = express();
app.get('/admin', (req, res) => {res.json({ version: 'v1', message: 'Hello from backend v1!' });
});
app.listen(3000, () => console.log('Backend v1 running on 3000'));
backend-v1/Dockerfile
root@ubuntu:/data/git/canary-header-app# cat backend-v1/Dockerfile
FROM node:24-alpine
WORKDIR /app
COPY index.js ./
RUN npm config set registry https://registry.npmmirror.com && \npm init -y && \npm install express
EXPOSE 3000
CMD ["node", "index.js"]
backend-v2/index.js
root@ubuntu:/data/git/canary-header-app# cat backend-v2/index.js
const express = require('express');
const app = express();
app.get('/admin', (req, res) => {res.json({ version: 'v2', message: 'Hello from backend v2!' });
});
app.listen(3000, () => console.log('Backend v2 running on 3000'));
backend-v2/Dockerfile
root@ubuntu:/data/git/canary-header-app# cat backend-v2/Dockerfile
FROM node:24-alpine
WORKDIR /app
COPY index.js ./
RUN npm config set registry https://registry.npmmirror.com && \npm init -y && \npm install express
EXPOSE 3000
CMD ["node", "index.js"]
frontend/index.html
root@ubuntu:/data/git/canary-header-app# cat frontend/index.html
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /><title>Canary Flow</title><style>body {font-family: Arial, sans-serif;max-width: 600px;margin: 0 auto;padding: 20px;}.test-section {margin: 20px 0;padding: 15px;border: 1px solid #ddd;border-radius: 5px;}button {margin: 5px;padding: 10px 15px;background-color: #007bff;color: white;border: none;border-radius: 3px;cursor: pointer;}button:hover {background-color: #0056b3;}pre {background-color: #f8f9fa;padding: 10px;border-radius: 3px;white-space: pre-wrap;}.version-v1 { color: #28a745; font-weight: bold; }.version-v2 { color: #dc3545; font-weight: bold; }</style>
</head>
<body><h1>Canary Flow 灰度發布演示</h1><div class="test-section"><h3>灰度規則測試</h3><p>基于請求頭 <code>X-App-Version</code> 的簡單灰度規則:</p><ul><li><strong>X-App-Version: v2</strong> → 路由到 Backend v2</li><li><strong>無請求頭或其他值</strong> → 路由到 Backend v1</li></ul></div><div class="test-section"><h3>測試按鈕</h3><button onclick="fetchData()">默認請求 (v1)</button><button onclick="fetchDataWithVersion('v2')">使用 v2 版本</button><button onclick="fetchDataWithVersion('v1')">強制使用 v1</button></div><pre id="output">點擊按鈕開始測試...</pre><script>async function fetchData() {try {const res = await fetch("http://192.168.73.11:8000/admin");const data = await res.json();displayResult(data, "默認請求(無請求頭)");} catch (error) {document.getElementById("output").innerText = "錯誤: " + error.message;}}async function fetchDataWithVersion(version) {try {const res = await fetch("http://192.168.73.11:8000/admin", {headers: {'X-App-Version': version}});const data = await res.json();displayResult(data, `X-App-Version: ${version}`);} catch (error) {document.getElementById("output").innerText = "錯誤: " + error.message;}}function displayResult(data, requestInfo = "") {const output = document.getElementById("output");const versionClass = data.version === 'v1' ? 'version-v1' : 'version-v2';const timestamp = new Date().toLocaleTimeString();output.innerHTML = `
請求信息: ${requestInfo}
時間: ${timestamp}
版本: <span class="${versionClass}">${data.version}</span>
消息: ${data.message}`;}</script>
</body>
</html>
說明:前端直接通過kong網關接口http://192.168.73.11:8000/admin
調用后端服務。
frontend/Dockerfile
root@ubuntu:/data/git/canary-header-app# cat frontend/Dockerfile
FROM nginx:alpine
RUN rm -rf /usr/share/nginx/html/*
COPY index.html /usr/share/nginx/html/
docker-compose.yaml
root@ubuntu:/data/git/canary-header-app# cat docker-compose.yml
name: 'canary-flow-app'services:frontend:build: ./frontendimage: registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-header-frontend:v1.0container_name: canary-frontendports:- "8080:80"networks:- app-netbackend-v1:build: ./backend-v1image: registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-header-backend-v1:v1.0container_name: canary-backend-v1ports:- "3001:3000"networks:- app-netbackend-v2:build: ./backend-v2image: registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-header-backend-v2:v1.0container_name: canary-backend-v2ports:- "3002:3000"networks:- app-netnetworks:app-net:external: truename: app_network
啟動前后端服務
構建鏡像
docker compose build
啟動服務
docker compose up -d
查看服務運行狀態
root@ubuntu:/data/git/canary-header-app# docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
canary-backend-v1 registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-flow-backend-v1:v1.0 "docker-entrypoint.s…" backend-v1 10 minutes ago Up 10 minutes 0.0.0.0:3001->3000/tcp, [::]:3001->3000/tcp
canary-backend-v2 registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-header-backend-v2:v1.0 "docker-entrypoint.s…" backend-v2 10 minutes ago Up 10 minutes 0.0.0.0:3002->3000/tcp, [::]:3002->3000/tcp
canary-frontend registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-header-frontend:v1.0 "/docker-entrypoint.…" frontend 10 minutes ago Up 10 minutes 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
root@ubuntu:/data/git/canary-flow-app#
灰度流量分流
Kong通過routes--headers
將流量按header分配到v1和v2后端,實現灰度發布。通過kong網關多次請求接口進行驗證:
root@ubuntu:/data/apps/kong# curl -s -H "X-App-Version: v1" http://192.168.73.11:8000/admin | jq
{"version": "v1","message": "Hello from backend v1!"
}
root@ubuntu:/data/apps/kong#
root@ubuntu:/data/apps/kong# curl -s -H "X-App-Version: v2" http://192.168.73.11:8000/admin | jq
{"version": "v2","message": "Hello from backend v2!"
}
root@ubuntu:/data/apps/kong# curl -s http://192.168.73.11:8000/admin | jq
{"version": "v1","message": "Hello from backend v1!"
}
root@ubuntu:/data/apps/kong#
瀏覽器訪問frontend驗證: