kong网关基于header分流灰度发布

发布于:2025-07-08 ⋅ 阅读:(13) ⋅ 点赞:(0)

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-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

定义了两个后端服务(一个正式,一个灰度):

  1. backend-service
- name: backend-service
  url: http://backend-v1:3000
  • name:服务名称,用于 route 映射。
  • url:实际后端地址,这里是正式环境 backend-v1 的 3000 端口。
  • 备注:省略了 host/port/protocol 的写法,直接用 url 更简洁。
  1. backend-service-gray
- name: backend-service-gray
  url: http://backend-v2:3000
  • 同上,只不过这是灰度环境的服务地址(backend-v2)。

routes

定义了两条路由规则,基于路径和请求头进行分流:

  1. backend-route-gray
- 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 中剥离。
  1. backend-route
- name: backend-route
  service: backend-service
  paths:
    - /admin
  strip_path: false
  • 匹配 /admin 的默认路由,绑定到正式服务 backend-service
  • 没有 header 限制,所有不匹配前面 route 的请求都会走这个。

⚠️ 匹配顺序重要backend-route-gray 必须在前面,Kong 按顺序匹配 route,先匹配成功的就执行,后面的不再判断。


plugins

配置了全局的 CORS(跨域资源共享)插件:

- 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网关与前后端服务网络

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.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网关

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: ./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

启动前后端服务

构建镜像

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验证:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到