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声明式配置文件:
yaml
root@ubuntu:/data/apps/kong# cat kong.yml
_format_version: "3.0"
services:
- name: backend-service
url: http://backend-v1:3000
- name: backend-service-gray
url: http://backend-v2:3000
routes:
- name: backend-route-gray
service: backend-service-gray
paths:
- /admin
headers:
X-App-Version:
- v2
strip_path: false
- name: backend-route
service: backend-service
paths:
- /admin
strip_path: false
plugins:
- name: cors
config:
origins:
- "*"
methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
headers:
- Accept
- Authorization
- Content-Type
- X-Requested-With
- X-App-Version
exposed_headers:
- X-Custom-Header
credentials: true
max_age: 3600
配置参数说明:
好的,以下是你提供的这份 Kong 声明式配置(kong.yml
)的逐项参数详解,特别聚焦于灰度发布与 CORS 支持相关部分:
_format_version: "3.0"
- 说明 :声明配置文件的格式版本,Kong 3.x 需要为
"3.0"
。 - 作用:Kong 用来解析声明式配置,必须写。
services
定义了两个后端服务(一个正式,一个灰度):
backend-service
yaml
- name: backend-service
url: http://backend-v1:3000
- name:服务名称,用于 route 映射。
- url :实际后端地址,这里是正式环境
backend-v1
的 3000 端口。 - 备注 :省略了
host/port/protocol
的写法,直接用url
更简洁。
backend-service-gray
yaml
- name: backend-service-gray
url: http://backend-v2:3000
- 同上,只不过这是灰度环境的服务地址(backend-v2)。
routes
定义了两条路由规则,基于路径和请求头进行分流:
backend-route-gray
yaml
- name: backend-route-gray
service: backend-service-gray
paths:
- /admin
headers:
X-App-Version:
- v2
strip_path: false
- name:路由名称。
- service :绑定到灰度服务
backend-service-gray
。 - paths :匹配
/admin
路径的请求。 - headers :匹配请求头
X-App-Version: v2
的请求。 - strip_path :为
false
表示保留/admin
前缀,不会从请求 URL 中剥离。
backend-route
yaml
- name: backend-route
service: backend-service
paths:
- /admin
strip_path: false
- 匹配
/admin
的默认路由,绑定到正式服务backend-service
。 - 没有 header 限制,所有不匹配前面 route 的请求都会走这个。
⚠️ 匹配顺序重要 :backend-route-gray
必须在前面,Kong 按顺序匹配 route,先匹配成功的就执行,后面的不再判断。
plugins
配置了全局的 CORS(跨域资源共享)插件:
yaml
- name: cors
config:
origins:
- "*"
methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
headers:
- Accept
- Authorization
- Content-Type
- X-Requested-With
- X-App-Version
exposed_headers:
- X-Custom-Header
credentials: true
max_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网关与前后端服务网络
bash
docker network create app_network
创建部署kong网关的docker-compose.yaml
yaml
root@ubuntu:/data/apps/kong# cat docker-compose.yaml
name: 'kong-gateway'
services:
kong:
image: kong:3.9.1
container_name: kong-gateway
restart: always
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /usr/local/kong/declarative/kong.yml
# kong proxy
KONG_PROXY_LISTEN: 0.0.0.0:8000
KONG_PROXY_LISTEN_SSL: 0.0.0.0:8443
# kong admin
KONG_ADMIN_LISTEN: 0.0.0.0:8001, 0.0.0.0:8444 ssl
# kong manager
KONG_ADMIN_GUI_LISTEN: 0.0.0.0:8002, 0.0.0.0:8445 ssl
# kong logs
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ERROR_LOG: /dev/stderr
ports:
- "8000:8000" # Proxy
- "8443:8443" # Proxy SSL
- "8001:8001" # Admin API
- "8444:8444" # Admin API SSL
- "8002:8002" # Kong Manager
- "8445:8445" # Kong Manager SSL
volumes:
- .kong.yml:/usr/local/kong/declarative/kong.yml
networks:
- app-net
healthcheck:
test: ["CMD-SHELL", "kong health"]
interval: 15s
timeout: 10s
retries: 3
networks:
app-net:
external: true
name: app_network
启动kong网关
bash
docker compose up -d
查看服务运行状态
bash
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编排
目录结构
bash
canaryheader/
├── docker-compose.yml
├── backend-v1/
│ ├── Dockerfile
│ └── index.js # v1后端代码
├── backend-v2/
│ ├── Dockerfile
│ └── index.js # v2后端代码
├── frontend/
│ ├── Dockerfile
│ └── index.html
backend-v1/index.js
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
bash
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
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
bash
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
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
bash
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
yaml
root@ubuntu:/data/git/canary-header-app# cat docker-compose.yml
name: 'canary-flow-app'
services:
frontend:
build: ./frontend
image: registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-header-frontend:v1.0
container_name: canary-frontend
ports:
- "8080:80"
networks:
- app-net
backend-v1:
build: ./backend-v1
image: registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-header-backend-v1:v1.0
container_name: canary-backend-v1
ports:
- "3001:3000"
networks:
- app-net
backend-v2:
build: ./backend-v2
image: registry.cn-shenzhen.aliyuncs.com/cnmirror/canary-header-backend-v2:v1.0
container_name: canary-backend-v2
ports:
- "3002:3000"
networks:
- app-net
networks:
app-net:
external: true
name: app_network
启动前后端服务
构建镜像
bash
docker compose build
启动服务
bash
docker compose up -d
查看服务运行状态
bash
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网关多次请求接口进行验证:
json
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验证: