Compare commits

...

133 Commits

Author SHA1 Message Date
澄潭
9e418dafd9 release 2.0.6-rc.3 (#1680) 2025-01-15 20:47:20 +08:00
澄潭
95523a1bc7 Fix istio lds cache (#1679) 2025-01-15 20:44:13 +08:00
澄潭
dcd8466127 Update build-and-test-plugin.yaml 2025-01-15 20:19:58 +08:00
澄潭
cceae6ad2a update cpp wasm plugins (#1675) 2025-01-15 19:15:11 +08:00
zty98751
32f9a5ff32 fix istio commit 2025-01-15 15:29:44 +08:00
澄潭
6f95297b80 Release 2.0.6-rc.2 (#1671) 2025-01-14 20:10:53 +08:00
Kent Dong
95426d5ccf fix: Fix a typo in the README files of ai-statistics plugin (#1670) 2025-01-14 13:39:55 +08:00
澄潭
a05b6b1e9d add ai_log field (#1669) 2025-01-14 10:03:24 +08:00
Jun
d0628344da add higress architecture doc (#1662) 2025-01-14 09:48:32 +08:00
韩贤涛
a1bf315b13 fix: resolve blocking issue with minimax responses in ai-proxy (#1663) 2025-01-14 09:43:19 +08:00
mamba
b3d9123d59 [frontend-gray] 微前端灰度 场景,支持 IncludePathPrefixes字段 (#1666) 2025-01-13 16:24:51 +08:00
rinfx
817061c6cc remove dependency for ai-statistic (#1660) 2025-01-10 13:43:29 +08:00
rinfx
ea0d5e7564 Improve ai plugins (#1657)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2025-01-09 22:04:51 +08:00
澄潭
2a89c3bb70 Optimize wasmplugin proto (#1656) 2025-01-09 13:19:46 +08:00
johnlanni
a570c72504 Update Chart.lock 2025-01-08 17:14:27 +08:00
澄潭
ab1316dfe1 rel: Release 2.0.6-rc.1 (#1653) 2025-01-08 17:08:09 +08:00
澄潭
e97448b71b Update metrics & enable lds cache (#1650) 2025-01-08 16:49:23 +08:00
澄潭
6820a06a99 fix tls version annotation (#1652) 2025-01-08 15:31:39 +08:00
澄潭
4733af849d Update README.md 2025-01-08 11:30:29 +08:00
yunmaoQu
1c2330e33b feat: add TLS version annotation support for per-rule configuration (#1592)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2025-01-06 21:29:09 +08:00
澄潭
61fef0ecf8 Release 2.0.5 (#1646) 2025-01-06 19:42:18 +08:00
澄潭
d29b8d7ca8 fix ai proxy checkStream (#1645) 2025-01-06 15:30:02 +08:00
澄潭
2501895b66 ai-cache update body buffer limit size (#1644) 2025-01-06 14:53:29 +08:00
Kent Dong
187a7b5408 fix: Enlarge the default retry timeout in ai-proxy (#1640) 2025-01-03 11:19:40 +08:00
Jingze
00be491d02 feat: support github provider for oidc wasm plugin (#1639) 2025-01-02 10:01:54 +08:00
ayanami-desu
2d74c48e8a Add cohere embedding for ai-cache (#1572) 2024-12-27 17:48:44 +08:00
澄潭
6dc4d43df5 optimize ai cache (#1626) 2024-12-27 10:10:57 +08:00
rinfx
2a4e55d46f move oidcHandler from global to pluginconfig (#1601) 2024-12-26 19:15:20 +08:00
Se7en
579c986915 feat: retry failed request (#1590) 2024-12-26 18:30:50 +08:00
Kent Dong
380717ae3d fix: Make opa listen to all IPs (#1621) 2024-12-26 17:41:28 +08:00
Kent Dong
8f3723f554 feat: Support setting gateway.unprivilegedPortSupported manually (#1616) 2024-12-23 19:45:47 +08:00
VinciWu557
909cc0f088 feat: AI 代理 Wasm 插件接入 Together AI (#1617) 2024-12-23 15:39:56 +08:00
007gzs
4eaf204737 Enhance the capabilities of the AI Intent plugin (#1605) 2024-12-20 10:25:17 +08:00
澄潭
748bcb083a redis wrapper support lazy init and database options (#1602) 2024-12-19 16:22:56 +08:00
澄潭
39c007d045 optimize ai proxy (#1603) 2024-12-19 16:22:35 +08:00
rinfx
d74d327b68 bugfix: cannot parse content if one streaming body has multi chunks (#1606) 2024-12-19 16:21:57 +08:00
澄潭
be27726721 Update CODEOWNERS 2024-12-19 14:36:11 +08:00
澄潭
34cc1c0632 Update README.md 2024-12-18 17:02:28 +08:00
澄潭
5694475872 Update README.md 2024-12-18 16:59:03 +08:00
rinfx
2f5709a93e qwen bailian compatible bug fix (#1597) 2024-12-17 16:57:31 +08:00
StarryNight
2a200cdd42 AI proxy return unified status in header phase (#1588) 2024-12-16 18:41:38 +08:00
rinfx
ec39d56731 AI observability upgrade (#1587)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-12-16 10:27:49 +08:00
韩贤涛
8544fa604d feat: support choosing chatCompletionV2 or chatCompletionPro API for minimax provider (#1593) 2024-12-15 15:12:00 +08:00
mirror
0ba63e5dd4 fix: default port of static service in ai-cache plugin (#1591) 2024-12-13 19:03:26 +08:00
mirror
441408c593 docs: fix typos in ai-quota document (#1589) 2024-12-13 08:56:43 +08:00
duxin40
be57960c22 Support OpenAI embedding. (#1542) 2024-12-11 11:42:51 +08:00
rinfx
f32020068a bugfix and extend ai log (#1576) 2024-12-09 20:39:13 +08:00
澄潭
1a8fce48f0 Update release-hgctl.yaml 2024-12-06 14:01:18 +08:00
澄潭
85c7b1f501 rel: Release 2.0.4 (#1571) 2024-12-06 13:52:03 +08:00
pepesi
8f660211e3 feat: ai-proxy support custom error handler by cover util.ErrorHandler (#1537) 2024-12-06 11:47:50 +08:00
rinfx
433227323d extension mechanism for custom logs and span attributes (#1451) 2024-12-05 18:39:00 +08:00
pepesi
b36e5ea26b feat: allow cover api-version when use ai-proxy azure provider (#1535) 2024-12-05 13:41:02 +08:00
rinfx
ce66ff68ce solve aliyun lvwang content length limit problem (#1569) 2024-12-05 13:39:20 +08:00
pepesi
d026f0fca5 feat: ai-proxy support dashscope-finance (#1554) 2024-12-05 11:48:09 +08:00
rinfx
22790aa149 fix moonshot usage compatible problem (#1568) 2024-12-05 11:35:25 +08:00
澄潭
7ce6d7aba1 fix xds cache (#1559) 2024-12-04 00:55:29 +08:00
Se7en
e705a0344f fix: qwen stream issue (#1564) 2024-12-03 13:10:47 +08:00
澄潭
d6094974c2 update ai proxy go mod (#1556) 2024-12-02 14:41:55 +08:00
mamba
6187be97e5 fix: 🐛 frontend-grayurl 解析不正确导致路由失败 (#1550) 2024-11-29 13:09:05 +08:00
澄潭
bb64b43f23 set concurrency argument of proxy by cpu limit/request (#1552) 2024-11-28 16:55:57 +08:00
澄潭
ca7458cf1c Optimize the overall log output (#1549) 2024-11-27 20:44:34 +08:00
Se7en
ee2dd76ae1 feat: migrate baidu provider to v2 api (#1527) 2024-11-27 20:12:00 +08:00
pepesi
8154cf95f1 feat: support custom log (#1521) 2024-11-27 20:11:29 +08:00
澄潭
a7593381e1 fix ai fallback (#1541) 2024-11-25 16:48:59 +08:00
澄潭
e68a8ac25f add model-mapper plugin & optimize model-router plugin (#1538) 2024-11-22 22:24:42 +08:00
Kent Dong
96575b982e fix: Refresh go.mod and go.sum file contents (#1525) 2024-11-22 13:34:55 +08:00
EnableAsync
c2d405b2a7 feat: Enhance ai-cache Plugin with Vector Similarity-Based LLM Cache Recall and Multi-DB Support (#1248) 2024-11-21 16:57:41 +08:00
Jingze
6efb3109f2 fix: update oidc plugin go.mod dependencies (#1522) 2024-11-19 17:33:42 +08:00
Se7en
1b1c08afb7 fix: apitoken failover for coze (#1515) 2024-11-18 15:36:26 +08:00
Se7en
d24123a55f feat: implement apiToken failover mechanism (#1256) 2024-11-16 19:03:09 +08:00
澄潭
f2a5df3949 use the body returned by the ext auth server when auth fails (#1510) 2024-11-14 18:50:33 +08:00
澄潭
ebc5b2987e fix compile of wasm cpp plugins (#1511) 2024-11-14 18:49:21 +08:00
007gzs
ca97cbd75a fix workflows build-and-push-wasm-plugin-image (#1508) 2024-11-13 17:39:24 +08:00
hanans426
a787e237ce 增加快速部署到阿里云的部署方案 (#1506) 2024-11-13 16:26:55 +08:00
纪卓志
6a1bf90d42 feat: supports custom prepare build script (#1490) 2024-11-12 13:45:28 +08:00
007gzs
60e476da87 fix example sse build error (#1503) 2024-11-11 17:47:26 +08:00
rinfx
2cb8558cda Optimize AI security guard plugin (#1473)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-11-11 14:49:17 +08:00
littlejian
4d1a037942 feat: Automatically generating markdown documentation for helm charts with helm-docs (#1496) 2024-11-11 11:34:38 +08:00
xingyunyang01
39b6eac9d0 AI Agent plugin adds JSON formatting output feature (#1374) 2024-11-11 11:11:02 +08:00
johnlanni
7697af9d2b update chart.lock 2024-11-08 14:07:12 +08:00
澄潭
3660715506 release 2.0.3 (#1494) 2024-11-08 13:59:12 +08:00
Squidward
7bd438877b add textin embedding for ai-cache (#1493) 2024-11-08 13:48:32 +08:00
Ranjana761
0fbeb39cac docs: Added back to top , contributors section and star history graph (#1440) 2024-11-08 10:18:16 +08:00
Kent Dong
d02c974af4 feat: Ensure all images are loaded to K8s before starting e2e tests (#1389) 2024-11-08 10:15:51 +08:00
澄潭
8ad4970231 update rust makefile (#1491) 2024-11-08 10:14:02 +08:00
007gzs
aee37c5e22 Implement Rust Wasm Plugin Build & Publish Action (#1483) 2024-11-07 20:20:42 +08:00
Kent Dong
73cf32aadd feat: Update istio codebase (#1485) 2024-11-07 11:40:57 +08:00
澄潭
1ab69fcf82 Update README.md 2024-11-07 11:20:25 +08:00
mamba
9b995321bb feat: 🎸 支持多版本能力:根据不同路由映射不同的Version版本。 (#1429) 2024-11-07 09:11:57 +08:00
澄潭
00cac813e3 add clusterrole for gateway api (#1480) 2024-11-06 13:37:18 +08:00
澄潭
548cf2f081 move nottinygc to proxy-wasm-go-sdk (#1477) 2024-11-05 20:59:51 +08:00
007gzs
c1f2504e87 Ai data mask deny word match optimize (#1453) 2024-11-05 15:26:55 +08:00
rinfx
7e8b0445ad modify log-format, then every plugin log is associated with access log (#1454) 2024-11-04 22:04:20 +08:00
littlejian
63d5422da6 feat:Support downstream and upstram, which can be configured through helm parameters (#1399) 2024-11-01 22:39:19 +08:00
韩贤涛
035e81a5ca fix: Control gateway-api Listener with global.enableGatewayAPI in Helm (#1461) 2024-10-31 21:36:40 +08:00
澄潭
9a1edcd4c8 Fix the issue where wasmplugin does not work when kingress exists (#1450) 2024-10-29 20:27:11 +08:00
007gzs
2219a17898 [feat] Support redis call with wasm-rust (#1417) 2024-10-29 19:35:02 +08:00
纪卓志
93c1e5c2bb feat: lazy formatted log (#1441) 2024-10-29 17:06:03 +08:00
澄潭
7c2d2b2855 fix destinationrule merge logic (#1439) 2024-10-29 09:01:06 +08:00
澄潭
b1550e91ab rel: Release v2.0.2 (#1438) 2024-10-28 19:14:57 +08:00
澄潭
0b42836e85 Update CODEOWNERS 2024-10-28 18:56:09 +08:00
tmsnan
7c33ebf6ea bugfix: remove config check and fix memory leak in traffic-tag (#1435) 2024-10-28 16:26:29 +08:00
Yang Beining
acec48ed8b [ai-cache] Implement a WASM plugin for LLM result retrieval based on vector similarity (#1290) 2024-10-27 16:21:04 +08:00
澄潭
d309bf2e25 fix model-router plugin (#1432) 2024-10-25 16:48:37 +08:00
韩贤涛
496d365a95 add PILOT_ENABLE_ALPHA_GATEWAY_API and fix gateway status update (#1421) 2024-10-24 20:57:46 +08:00
rinfx
d952fa562b bugfix: plugin will block GET request (#1428) 2024-10-24 17:34:26 +08:00
纪卓志
e7561c30e5 feat: implements text/event-stream(SSE) MIME parser (#1416)
Co-authored-by: 007gzs <007gzs@gmail.com>
2024-10-24 16:58:45 +08:00
澄潭
cdd71155a9 Update README.md 2024-10-24 11:25:59 +08:00
Ankur Singh
a5ccb90b28 Improve the grammar of the sentence (#1426) 2024-10-24 09:38:22 +08:00
007gzs
d76f574ab3 plugin ai-data-mask add log (#1423) 2024-10-23 13:19:02 +08:00
mamba
bb6c43c767 feat: 【frontend-gray】添加 skipedRoutes以及skipedByHeaders 配置 (#1409)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-10-23 09:34:00 +08:00
fengxsong
b8f5826a32 fix: do not create ingressclass when it's empty (#1419)
Signed-off-by: fengxusong <fengxsong@outlook.com>
2024-10-23 09:03:07 +08:00
Bingkun Zhao
0d79386ce2 fix a bug of ip-restriction plugin (#1422) 2024-10-22 22:17:56 +08:00
007gzs
871ae179c3 Ai data masking fix (#1420) 2024-10-22 21:39:01 +08:00
澄潭
f8d62a8ac3 add model router plugin (#1414) 2024-10-21 16:46:18 +08:00
澄潭
badf4b7101 ai cache plugin support set skip ai cache header (#1380) 2024-10-21 15:43:01 +08:00
Ikko Eltociear Ashimine
fc6902ded2 docs: add Japanese README and CONTRIBUTING files (#1407) 2024-10-21 15:20:45 +08:00
007gzs
d96994767c Change http_content to Rc in HttpWrapper (#1391) 2024-10-21 09:44:01 +08:00
rinfx
32e5a59ae0 fix special charactor handle in ai-security-guard plugin (#1394) 2024-10-18 16:32:48 +08:00
Lisheng Zheng
49bb5ec2b9 fix: add HTTP2 protocol options to skywalking and otel cluster (#1379) 2024-10-18 15:34:34 +08:00
mamba
11ff2d1d31 [frontend-gray] support grayKey from localStorage (#1395) 2024-10-18 13:58:52 +08:00
澄潭
c67f494b49 Update README.md 2024-10-18 09:54:10 +08:00
澄潭
299621476f Update README.md 2024-10-17 14:33:08 +08:00
澄潭
7e6168a644 Update README.md 2024-10-17 14:31:26 +08:00
Smoothengineer
e923cbaecc Update README_EN.md (#1402) 2024-10-17 12:53:04 +08:00
Kent Dong
6f86c31bac feat: Update submodules: envoy/envoy, istio/isitio (#1398) 2024-10-16 19:00:18 +08:00
Kent Dong
51c956f0b3 fix: Fix clean targets in Makefile (#1397) 2024-10-16 18:42:49 +08:00
澄潭
d0693d8c4b Update SECURITY.md 2024-10-16 11:17:44 +08:00
Jun
e298078065 add dns&static registry e2e test (#1393) 2024-10-16 10:21:03 +08:00
澄潭
85f8eb5166 key-auth consumer support set independent key source (#1392) 2024-10-15 20:52:03 +08:00
澄潭
0a112d1a1e fix mcp service port protocol name (#1383) 2024-10-15 11:50:43 +08:00
Kent Dong
04ce776f14 feat: Support route fallback by default (#1381) 2024-10-14 18:50:45 +08:00
rinfx
952c9ec5dc Ai proxy support coze (#1387) 2024-10-14 12:45:53 +08:00
328 changed files with 15519 additions and 5858 deletions

View File

@@ -3,9 +3,16 @@ name: Build and Push Wasm Plugin Image
on:
push:
tags:
- "wasm-go-*-v*.*.*" # 匹配 wasm-go-{pluginName}-vX.Y.Z 格式的标签
- "wasm-*-*-v*.*.*" # 匹配 wasm-{go|rust}-{pluginName}-vX.Y.Z 格式的标签
workflow_dispatch:
inputs:
plugin_type:
description: 'Type of the plugin'
required: true
type: choice
options:
- go
- rust
plugin_name:
description: 'Name of the plugin'
required: true
@@ -23,32 +30,42 @@ jobs:
env:
IMAGE_REGISTRY_SERVICE: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
IMAGE_REPOSITORY: ${{ vars.PLUGIN_IMAGE_REPOSITORY || 'plugins' }}
RUST_VERSION: 1.82
GO_VERSION: 1.19
TINYGO_VERSION: 0.28.1
ORAS_VERSION: 1.0.0
steps:
- name: Set plugin_name and version from inputs or ref_name
- name: Set plugin_type, plugin_name and version from inputs or ref_name
id: set_vars
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
plugin_type="${{ github.event.inputs.plugin_type }}"
plugin_name="${{ github.event.inputs.plugin_name }}"
version="${{ github.event.inputs.version }}"
else
ref_name=${{ github.ref_name }}
plugin_type=${ref_name#*-} # 删除插件类型前面的字段(wasm-)
plugin_type=${plugin_type%%-*} # 删除插件类型后面的字段(-{plugin_name}-vX.Y.Z)
plugin_name=${ref_name#*-*-} # 删除插件名前面的字段(wasm-go-)
plugin_name=${plugin_name%-*} # 删除插件名后面的字段(-vX.Y.Z)
version=$(echo "$ref_name" | awk -F'v' '{print $2}')
fi
if [[ "$plugin_type" == "rust" ]]; then
builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-rust-builder:rust${{ env.RUST_VERSION }}-oras${{ env.ORAS_VERSION }}"
else
builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go${{ env.GO_VERSION }}-tinygo${{ env.TINYGO_VERSION }}-oras${{ env.ORAS_VERSION }}"
fi
echo "PLUGIN_TYPE=$plugin_type" >> $GITHUB_ENV
echo "PLUGIN_NAME=$plugin_name" >> $GITHUB_ENV
echo "VERSION=$version" >> $GITHUB_ENV
echo "BUILDER_IMAGE=$builder_image" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v3
- name: File Check
run: |
workspace=${{ github.workspace }}/plugins/wasm-go/extensions/${PLUGIN_NAME}
workspace=${{ github.workspace }}/plugins/wasm-${PLUGIN_TYPE}/extensions/${PLUGIN_NAME}
push_command="./plugin.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip"
# 查找spec.yaml
@@ -75,10 +92,10 @@ jobs:
echo "PUSH_COMMAND=\"$push_command\"" >> $GITHUB_ENV
- name: Run a wasm-go-builder
- name: Run a wasm-builder
env:
PLUGIN_NAME: ${{ env.PLUGIN_NAME }}
BUILDER_IMAGE: higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go${{ env.GO_VERSION }}-tinygo${{ env.TINYGO_VERSION }}-oras${{ env.ORAS_VERSION }}
BUILDER_IMAGE: ${{ env.BUILDER_IMAGE }}
run: |
docker run -itd --name builder -v ${{ github.workspace }}:/workspace -e PLUGIN_NAME=${{ env.PLUGIN_NAME }} --rm ${{ env.BUILDER_IMAGE }} /bin/bash
@@ -93,7 +110,7 @@ jobs:
echo "TargetImage=${target_image}"
echo "TargetImageLatest=${target_image_latest}"
cd ${{ github.workspace }}/plugins/wasm-go/extensions/${PLUGIN_NAME}
cd ${{ github.workspace }}/plugins/wasm-${PLUGIN_TYPE}/extensions/${PLUGIN_NAME}
if [ -f ./.buildrc ]; then
echo 'Found .buildrc file, sourcing it...'
. ./.buildrc
@@ -101,7 +118,7 @@ jobs:
echo '.buildrc file not found'
fi
echo "EXTRA_TAGS=${EXTRA_TAGS}"
if [ "${PLUGIN_TYPE}" == "go" ]; then
command="
set -e
cd /workspace/plugins/wasm-go/extensions/${PLUGIN_NAME}
@@ -112,4 +129,21 @@ jobs:
oras push ${target_image} ${push_command}
oras push ${target_image_latest} ${push_command}
"
elif [ "${PLUGIN_TYPE}" == "rust" ]; then
command="
set -e
cd /workspace/plugins/wasm-rust/extensions/${PLUGIN_NAME}
cargo build --target wasm32-wasi --release
cp target/wasm32-wasi/release/*.wasm plugin.wasm
tar czvf plugin.tar.gz plugin.wasm
echo ${{ secrets.REGISTRY_PASSWORD }} | oras login -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin ${{ env.IMAGE_REGISTRY_SERVICE }}
oras push ${target_image} ${push_command}
oras push ${target_image_latest} ${push_command}
"
else
command="
echo "unkown type ${PLUGIN_TYPE}"
"
fi
docker exec builder bash -c "$command"

View File

@@ -6,11 +6,15 @@ on:
paths:
- 'plugins/**'
- 'test/**'
- 'helm/**'
- 'Makefile.core.mk'
pull_request:
branches: [ "*" ]
paths:
- 'plugins/**'
- 'test/**'
- 'helm/**'
- 'Makefile.core.mk'
workflow_dispatch: ~
jobs:

35
.github/workflows/helm-docs.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: "Helm Docs"
on:
pull_request:
branches:
- "*"
push:
jobs:
helm:
name: Helm Docs
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22.9'
- name: Run helm-docs
run: |
GOBIN=$PWD GO111MODULE=on go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2
./helm-docs -c ${GITHUB_WORKSPACE}/helm/higress -f ../core/values.yaml
DIFF=$(git diff ${GITHUB_WORKSPACE}/helm/higress/*md)
if [ ! -z "$DIFF" ]; then
echo "Please use helm-docs in your clone, of your fork, of the project, and commit a updated README.md for the chart."
fi
git diff --exit-code
rm -f ./helm-docs

24
.github/workflows/release-crd.yaml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Release CRD to GitHub
on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
release-crd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: generate crds
run: |
cat helm/core/crds/customresourcedefinitions.gen.yaml helm/core/crds/istio-envoyfilter.yaml > crd.yaml
- name: Upload hgctl packages to the GitHub release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
crd.yaml

View File

@@ -58,7 +58,7 @@ jobs:
hgctl_${{ env.HGCTL_VERSION }}_darwin_arm64.tar.gz
release-hgctl-macos-amd64:
runs-on: macos-12
runs-on: macos-14
env:
HGCTL_VERSION: ${{github.ref_name}}
steps:

3
.gitignore vendored
View File

@@ -16,4 +16,5 @@ helm/**/charts/**.tgz
target/
tools/hack/cluster.conf
envoy/1.20
istio/1.12
istio/1.12
Cargo.lock

View File

@@ -12,6 +12,7 @@ header:
- 'LICENSE'
- 'api/**'
- 'samples/**'
- 'docs/**'
- '.github/**'
- '.licenserc.yaml'
- 'helm/**'

View File

@@ -2,8 +2,9 @@
/envoy @gengleilei @johnlanni
/istio @SpecialYang @johnlanni
/pkg @SpecialYang @johnlanni @CH3CHO
/plugins @johnlanni @WeixinX @CH3CHO
/plugins/wasm-rust @007gzs
/plugins @johnlanni @CH3CHO @rinfx
/plugins/wasm-go/extensions/ai-proxy @cr7258 @CH3CHO @rinfx
/plugins/wasm-rust @007gzs @jizhuozhi
/registry @NameHaibinZhang @2456868764 @johnlanni
/test @Xunzhuo @2456868764 @CH3CHO
/tools @johnlanni @Xunzhuo @2456868764

View File

@@ -1,6 +1,6 @@
# Contributing to Higress
It is warmly welcomed if you have interest to hack on Higress. First, we encourage this kind of willing very much. And here is a list of contributing guide for you.
Your interest in contributing to Higress is warmly welcomed. First, we encourage this kind of willing very much. And here is a list of contributing guide for you.
[[中文贡献文档](./CONTRIBUTING_CN.md)]

195
CONTRIBUTING_JP.md Normal file
View File

@@ -0,0 +1,195 @@
# Higress への貢献
Higress のハッキングに興味がある場合は、温かく歓迎します。まず、このような意欲を非常に奨励します。そして、以下は貢献ガイドのリストです。
[[中文](./CONTRIBUTING.md)] | [[English Contributing Document](./CONTRIBUTING_EN.md)]
## トピック
- [Higress への貢献](#higress-への貢献)
- [トピック](#トピック)
- [セキュリティ問題の報告](#セキュリティ問題の報告)
- [一般的な問題の報告](#一般的な問題の報告)
- [コードとドキュメントの貢献](#コードとドキュメントの貢献)
- [ワークスペースの準備](#ワークスペースの準備)
- [ブランチの定義](#ブランチの定義)
- [コミットルール](#コミットルール)
- [コミットメッセージ](#コミットメッセージ)
- [コミット内容](#コミット内容)
- [PR 説明](#pr-説明)
- [テストケースの貢献](#テストケースの貢献)
- [何かを手伝うための参加](#何かを手伝うための参加)
- [コードスタイル](#コードスタイル)
## セキュリティ問題の報告
セキュリティ問題は常に真剣に扱われます。通常の原則として、セキュリティ問題を広めることは推奨しません。Higress のセキュリティ問題を発見した場合は、公開で議論せず、公開の問題を開かないでください。代わりに、[higress@googlegroups.com](mailto:higress@googlegroups.com) にプライベートなメールを送信して報告することをお勧めします。
## 一般的な問題の報告
正直なところ、Higress のすべてのユーザーを非常に親切な貢献者と見なしています。Higress を体験した後、プロジェクトに対するフィードバックがあるかもしれません。その場合は、[NEW ISSUE](https://github.com/alibaba/higress/issues/new/choose) を通じて問題を開くことを自由に行ってください。
Higress プロジェクトを分散型で協力しているため、**よく書かれた**、**詳細な**、**明確な**問題報告を高く評価します。コミュニケーションをより効率的にするために、問題が検索リストに存在するかどうかを検索することを希望します。存在する場合は、新しい問題を開くのではなく、既存の問題のコメントに詳細を追加してください。
問題の詳細をできるだけ標準化するために、問題報告者のために [ISSUE TEMPLATE](./.github/ISSUE_TEMPLATE) を設定しました。テンプレートのフィールドに従って指示に従って記入してください。
問題を開く場合は多くのケースがあります:
* バグ報告
* 機能要求
* パフォーマンス問題
* 機能提案
* 機能設計
* 助けが必要
* ドキュメントが不完全
* テストの改善
* プロジェクトに関する質問
* その他
また、新しい問題を記入する際には、投稿から機密データを削除することを忘れないでください。機密データには、パスワード、秘密鍵、ネットワークの場所、プライベートなビジネスデータなどが含まれる可能性があります。
## コードとドキュメントの貢献
Higress プロジェクトをより良くするためのすべての行動が奨励されます。GitHub では、Higress のすべての改善は PRプルリクエストの略を通じて行うことができます。
* タイプミスを見つけた場合は、修正してみてください!
* バグを見つけた場合は、修正してみてください!
* 冗長なコードを見つけた場合は、削除してみてください!
* 欠落しているテストケースを見つけた場合は、追加してみてください!
* 機能を強化できる場合は、**ためらわないでください**
* コードが不明瞭な場合は、コメントを追加して明確にしてください!
* コードが醜い場合は、リファクタリングしてみてください!
* ドキュメントの改善に役立つ場合は、さらに良いです!
* ドキュメントが不正確な場合は、修正してください!
* ...
実際には、それらを完全にリストすることは不可能です。1つの原則を覚えておいてください
> あなたからの PR を楽しみにしています。
Higress を PR で改善する準備ができたら、ここで PR ルールを確認することをお勧めします。
* [ワークスペースの準備](#ワークスペースの準備)
* [ブランチの定義](#ブランチの定義)
* [コミットルール](#コミットルール)
* [PR 説明](#pr-説明)
### ワークスペースの準備
PR を提出するために、GitHub ID に登録していることを前提とします。その後、以下の手順で準備を完了できます:
1. Higress を自分のリポジトリに **FORK** します。この作業を行うには、[alibaba/higress](https://github.com/alibaba/higress) のメインページの右上にある Fork ボタンをクリックするだけです。その後、`https://github.com/<your-username>/higress` に自分のリポジトリが作成されます。ここで、`your-username` はあなたの GitHub ユーザー名です。
2. 自分のリポジトリをローカルに **CLONE** します。`git clone git@github.com:<your-username>/higress.git` を使用してリポジトリをローカルマシンにクローンします。その後、新しいブランチを作成して、行いたい変更を完了できます。
3. リモートを `git@github.com:alibaba/higress.git` に設定します。以下の2つのコマンドを使用します
```bash
git remote add upstream git@github.com:alibaba/higress.git
git remote set-url --push upstream no-pushing
```
このリモート設定を使用すると、git リモート設定を次のように確認できます:
```shell
$ git remote -v
origin git@github.com:<your-username>/higress.git (fetch)
origin git@github.com:<your-username>/higress.git (push)
upstream git@github.com:alibaba/higress.git (fetch)
upstream no-pushing (push)
```
これを追加すると、ローカルブランチを上流ブランチと簡単に同期できます。
### ブランチの定義
現在、プルリクエストを通じたすべての貢献は Higress の [main ブランチ](https://github.com/alibaba/higress/tree/main) に対するものであると仮定します。貢献する前に、ブランチの定義を理解することは非常に役立ちます。
貢献者として、プルリクエストを通じたすべての貢献は main ブランチに対するものであることを再度覚えておいてください。Higress プロジェクトには、リリースブランチ0.6.0、0.6.1)、機能ブランチ、ホットフィックスブランチなど、いくつかの他のブランチがあります。
正式にバージョンをリリースする際には、リリースブランチが作成され、バージョン番号で命名されます。
リリース後、リリースブランチのコミットを main ブランチにマージします。
特定のバージョンにバグがある場合、後のバージョンで修正するか、特定のホットフィックスバージョンで修正するかを決定します。ホットフィックスバージョンで修正することを決定した場合、対応するリリースブランチに基づいてホットフィックスブランチをチェックアウトし、コード修正と検証を行い、main ブランチにマージします。
大きな機能については、開発と検証のために機能ブランチを引き出します。
### コミットルール
実際には、Higress ではコミット時に2つのルールを真剣に考えています
* [コミットメッセージ](#コミットメッセージ)
* [コミット内容](#コミット内容)
#### コミットメッセージ
コミットメッセージは、提出された PR の目的をレビュアーがよりよく理解するのに役立ちます。また、コードレビューの手続きを加速するのにも役立ちます。貢献者には、曖昧なメッセージではなく、**明確な**コミットメッセージを使用することを奨励します。一般的に、以下のコミットメッセージタイプを推奨します:
* docs: xxxx. 例:"docs: add docs about Higress cluster installation".
* feature: xxxx. 例:"feature: use higress config instead of istio config".
* bugfix: xxxx. 例:"bugfix: fix panic when input nil parameter".
* refactor: xxxx. 例:"refactor: simplify to make codes more readable".
* test: xxx. 例:"test: add unit test case for func InsertIntoArray".
* その他の読みやすく明確な表現方法。
一方で、以下のような方法でのコミットメッセージは推奨しません:
* ~~バグ修正~~
* ~~更新~~
* ~~ドキュメント追加~~
迷った場合は、[Git コミットメッセージの書き方](http://chris.beams.io/posts/git-commit/) を参照してください。
#### コミット内容
コミット内容は、1つのコミットに含まれるすべての内容の変更を表します。1つのコミットに、他のコミットの助けを借りずにレビュアーが完全にレビューできる内容を含めるのが最善です。言い換えれば、1つのコミットの内容は CI を通過でき、コードの混乱を避けることができます。簡単に言えば、次の3つの小さなルールを覚えておく必要があります
* コミットで非常に大きな変更を避ける;
* 各コミットが完全でレビュー可能であること。
* コミット時に git config`user.name``user.email`)を確認して、それが GitHub ID に関連付けられていることを確認します。
```bash
git config --get user.name
git config --get user.email
```
* pr を提出する際には、'changes/' フォルダーの下の XXX.md ファイルに現在の変更の簡単な説明を追加してください。
さらに、コード変更部分では、すべての貢献者が Higress の [コードスタイル](#コードスタイル) を読むことをお勧めします。
コミットメッセージやコミット内容に関係なく、コードレビューに重点を置いています。
### PR 説明
PR は Higress プロジェクトファイルを変更する唯一の方法です。レビュアーが目的をよりよく理解できるようにするために、PR 説明は詳細すぎることはありません。貢献者には、[PR テンプレート](./.github/PULL_REQUEST_TEMPLATE.md) に従ってプルリクエストを完了することを奨励します。
### 開発前の準備
```shell
make prebuild && go mod tidy
```
## テストケースの貢献
テストケースは歓迎されます。現在、Higress の機能テストケースが高優先度です。
* 単体テストの場合、同じモジュールの test ディレクトリに xxxTest.go という名前のテストファイルを作成する必要があります。
* 統合テストの場合、統合テストを test ディレクトリに配置できます。
//TBD
## 何かを手伝うための参加
GitHub を Higress の協力の主要な場所として選択しました。したがって、Higress の最新の更新は常にここにあります。PR を通じた貢献は明確な助けの方法ですが、他の方法も呼びかけています。
* 可能であれば、他の人の質問に返信する;
* 他のユーザーの問題を解決するのを手伝う;
* 他の人の PR 設計をレビューするのを手伝う;
* 他の人の PR のコードをレビューするのを手伝う;
* Higress について議論して、物事を明確にする;
* GitHub 以外で Higress 技術を宣伝する;
* Higress に関するブログを書くなど。
## コードスタイル
//TBD
要するに、**どんな助けも貢献です。**

View File

@@ -72,17 +72,17 @@ go.test.coverage: prebuild
.PHONY: build
build: prebuild $(OUT)
GOPROXY=$(GOPROXY) GOOS=$(GOOS_LOCAL) GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT)/ $(HIGRESS_BINARIES)
GOPROXY="$(GOPROXY)" GOOS=$(GOOS_LOCAL) GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT)/ $(HIGRESS_BINARIES)
.PHONY: build-linux
build-linux: prebuild $(OUT)
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT_LINUX)/ $(HIGRESS_BINARIES)
GOPROXY="$(GOPROXY)" GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT_LINUX)/ $(HIGRESS_BINARIES)
$(AMD64_OUT_LINUX)/higress:
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=amd64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_amd64/ $(HIGRESS_BINARIES)
GOPROXY="$(GOPROXY)" GOOS=linux GOARCH=amd64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_amd64/ $(HIGRESS_BINARIES)
$(ARM64_OUT_LINUX)/higress:
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=arm64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_arm64/ $(HIGRESS_BINARIES)
GOPROXY="$(GOPROXY)" GOOS=linux GOARCH=arm64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_arm64/ $(HIGRESS_BINARIES)
.PHONY: build-hgctl
build-hgctl: prebuild $(OUT)
@@ -144,7 +144,7 @@ docker-buildx-push: clean-env docker.higress-buildx
export PARENT_GIT_TAG:=$(shell cat VERSION)
export PARENT_GIT_REVISION:=$(TAG)
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.0.0/envoy-symbol-ARCH.tar.gz
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.1.0/envoy-symbol-ARCH.tar.gz
build-envoy: prebuild
./tools/hack/build-envoy.sh
@@ -187,8 +187,8 @@ install: pre-install
cd helm/higress; helm dependency build
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
ENVOY_LATEST_IMAGE_TAG ?= 2.0.1
ISTIO_LATEST_IMAGE_TAG ?= 2.0.1
ENVOY_LATEST_IMAGE_TAG ?= 958467a353d411ae3f06e03b096bfd342cddb2c6
ISTIO_LATEST_IMAGE_TAG ?= f5cd4d940185204f375a0dd863246037c183cb76
install-dev: pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'
@@ -221,11 +221,15 @@ clean-higress: ## Cleans all the intermediate files and folders previously gener
rm -rf $(DIRS_TO_CLEAN)
clean-istio:
rm -rf external/api
rm -rf external/client-go
rm -rf external/istio
rm -rf external/pkg
clean-gateway: clean-istio
rm -rf external/envoy
rm -rf external/proxy
rm -rf external/go-control-plane
rm -rf external/package/envoy.tar.gz
clean-env:
@@ -284,6 +288,8 @@ delete-cluster: $(tools/kind) ## Delete kind cluster.
.PHONY: kube-load-image
kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster using the provided $IMAGE and $TAG.
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress $(TAG)
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/pilot $(ISTIO_LATEST_IMAGE_TAG)
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway $(ENVOY_LATEST_IMAGE_TAG)
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/dubbo-provider-demo 0.0.3-x86
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/nacos-standlone-rc3 1.0.0-RC3
tools/hack/docker-pull-image.sh docker.io/hashicorp/consul 1.16.0
@@ -293,7 +299,8 @@ kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster us
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server 1.3.0
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server v1.0
tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-body 1.0.0
tools/hack/docker-pull-image.sh openpolicyagent/opa latest
tools/hack/docker-pull-image.sh openpolicyagent/opa 0.61.0
tools/hack/docker-pull-image.sh curlimages/curl latest
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin 1.0.2
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/dubbo-provider-demo 0.0.3-x86
@@ -305,7 +312,8 @@ kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster us
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server 1.3.0
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server v1.0
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-body 1.0.0
tools/hack/kind-load-image.sh openpolicyagent/opa latest
tools/hack/kind-load-image.sh openpolicyagent/opa 0.61.0
tools/hack/kind-load-image.sh curlimages/curl latest
tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin 1.0.2
tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3

View File

@@ -1,3 +1,4 @@
<a name="readme-top"></a>
<h1 align="center">
<img src="https://img.alicdn.com/imgextra/i2/O1CN01NwxLDd20nxfGBjxmZ_!!6000000006895-2-tps-960-290.png" alt="Higress" width="240" height="72.5">
<br>
@@ -5,30 +6,37 @@
</h1>
<h4 align="center"> AI Native API Gateway </h4>
<div align="center">
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
<a href="https://trendshift.io/repositories/10918" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10918" alt="alibaba%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>
[**官网**](https://higress.cn/) &nbsp; |
&nbsp; [**文档**](https://higress.cn/docs/latest/user/quickstart/) &nbsp; |
&nbsp; [**文档**](https://higress.cn/docs/latest/overview/what-is-higress/) &nbsp; |
&nbsp; [**博客**](https://higress.cn/blog/) &nbsp; |
&nbsp; [**电子书**](https://higress.cn/docs/ebook/wasm14/) &nbsp; |
&nbsp; [**开发指引**](https://higress.cn/docs/latest/dev/architecture/) &nbsp; |
&nbsp; [**AI插件**](https://higress.cn/plugin/) &nbsp;
<p>
<a href="README_EN.md"> English <a/> | 中文
<a href="README_EN.md"> English <a/>| 中文 | <a href="README_JP.md"> 日本語 <a/>
</p>
Higress 是基于阿里内部多年的 Envoy Gateway 实践沉淀,以开源 [Istio](https://github.com/istio/istio) 与 [Envoy](https://github.com/envoyproxy/envoy) 为核心构建的云原生 API 网关。
Higress 是一款云原生 API 网关,内核基于 Istio 和 Envoy可以用 Go/Rust/JS 等编写 Wasm 插件提供了数十个现成的通用插件以及开箱即用的控制台demo 点[这里](http://demo.higress.io/)
Higress 在阿里内部作为 AI 网关,承载了通义千问 APP、百炼大模型 API、机器学习 PAI 平台等 AI 业务的流量
Higress 在阿里内部为解决 Tengine reload 对长连接业务有损,以及 gRPC/Dubbo 负载均衡能力不足而诞生
Higress 能够用统一的协议对接国内外所有 LLM 模型厂商,同时具备丰富的 AI 可观测、多模型负载均衡/fallback、AI token 流控、AI 缓存等能力
阿里云基于 Higress 构建了云原生 API 网关产品,为大量企业客户提供 99.99% 的网关高可用保障服务能力
![](https://img.alicdn.com/imgextra/i1/O1CN01fNnhCp1cV8mYPRFeS_!!6000000003605-0-tps-1080-608.jpg)
Higress 基于 AI 网关能力,支撑了通义千问 APP、百炼大模型 API、机器学习 PAI 平台等 AI 业务。同时服务国内头部的 AIGC 企业(如零一万物),以及 AI 产品(如 FastGPT
![](https://img.alicdn.com/imgextra/i2/O1CN011AbR8023V8R5N0HcA_!!6000000007260-2-tps-1080-606.png)
## Summary
@@ -58,32 +66,45 @@ docker run -d --rm --name higress-ai -v ${PWD}:/data \
- 8080 端口:网关 HTTP 协议入口
- 8443 端口:网关 HTTPS 协议入口
**Higress 的所有 Docker 镜像都一直使用自己独享的仓库,不受 Docker Hub 境内不可访问的影响**
**Higress 的所有 Docker 镜像都一直使用自己独享的仓库,不受 Docker Hub 境内访问受限的影响**
K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start 文档](https://higress.cn/docs/latest/user/quickstart/)。
如果您是在云上部署,生产环境推荐使用[企业版](https://higress.io/cloud/),开发测试可以使用下面一键部署社区版:
[![Deploy on AlibabaCloud ComputeNest](https://service-info-public.oss-cn-hangzhou.aliyuncs.com/computenest.svg)](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Higress社区版)
## 使用场景
- **AI 网关**:
Higress 提供了一站式的 AI 插件集,可以增强依赖 AI 能力业务的稳定性、灵活性、可观测性,使得业务与 AI 的集成更加便捷和高效。
Higress 能够用统一的协议对接国内外所有 LLM 模型厂商,同时具备丰富的 AI 可观测、多模型负载均衡/fallback、AI token 流控、AI 缓存等能力:
![](https://img.alicdn.com/imgextra/i1/O1CN01fNnhCp1cV8mYPRFeS_!!6000000003605-0-tps-1080-608.jpg)
- **Kubernetes Ingress 网关**:
Higress 可以作为 K8s 集群的 Ingress 入口网关, 并且兼容了大量 K8s Nginx Ingress 的注解,可以从 K8s Nginx Ingress 快速平滑迁移到 Higress。
支持 [Gateway API](https://gateway-api.sigs.k8s.io/) 标准,支持用户从 Ingress API 平滑迁移到 Gateway API。
相比 ingress-nginx资源开销大幅下降路由变更生效速度有十倍提升
![](https://img.alicdn.com/imgextra/i1/O1CN01bhEtb229eeMNBWmdP_!!6000000008093-2-tps-750-547.png)
![](https://img.alicdn.com/imgextra/i1/O1CN01bqRets1LsBGyitj4S_!!6000000001354-2-tps-887-489.png)
- **微服务网关**:
Higress 可以作为微服务网关, 能够对接多种类型的注册中心发现服务配置路由,例如 Nacos, ZooKeeper, Consul, Eureka 等。
并且深度集成了 [Dubbo](https://github.com/apache/dubbo), [Nacos](https://github.com/alibaba/nacos), [Sentinel](https://github.com/alibaba/Sentinel) 等微服务技术栈,基于 Envoy C++ 网关内核的出色性能,相比传统 Java 类微服务网关,可以显著降低资源使用率,减少成本。
![](https://img.alicdn.com/imgextra/i4/O1CN01v4ZbCj1dBjePSMZ17_!!6000000003698-0-tps-1613-926.jpg)
- **安全防护网关**:
Higress 可以作为安全防护网关, 提供 WAF 的能力,并且支持多种认证鉴权策略,例如 key-auth, hmac-auth, jwt-auth, basic-auth, oidc 等。
Higress 可以作为安全防护网关, 提供 WAF 的能力,并且支持多种认证鉴权策略,例如 key-auth, hmac-auth, jwt-auth, basic-auth, oidc 等。
## 核心优势
@@ -165,7 +186,7 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start
### 交流群
![image](https://img.alicdn.com/imgextra/i2/O1CN01qPd7Ix1uZPVEsWjWp_!!6000000006051-0-tps-720-405.jpg)
![image](https://img.alicdn.com/imgextra/i2/O1CN01fZefEP1aPWkzG3A19_!!6000000003322-0-tps-720-405.jpg)
### 技术分享
@@ -176,4 +197,20 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start
### 关联仓库
- Higress 控制台https://github.com/higress-group/higress-console
- Higress独立运行版https://github.com/higress-group/higress-standalone
- Higress独立运行版https://github.com/higress-group/higress-standalone
### 贡献者
<a href="https://github.com/alibaba/higress/graphs/contributors">
<img alt="contributors" src="https://contrib.rocks/image?repo=alibaba/higress"/>
</a>
### Star History
[![Star History](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date)
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
↑ 返回顶部 ↑
</a>
</p>

View File

@@ -1,3 +1,4 @@
<a name="readme-top"></a>
<h1 align="center">
<img src="https://img.alicdn.com/imgextra/i2/O1CN01NwxLDd20nxfGBjxmZ_!!6000000006895-2-tps-960-290.png" alt="Higress" width="240" height="72.5">
<br>
@@ -15,7 +16,7 @@
<p>
English | <a href="README.md">中文<a/>
English | <a href="README.md">中文<a/> | <a href="README_JP.md">日本語<a/>
</p>
Higress is a cloud-native api gateway based on Alibaba's internal gateway practices.
@@ -47,7 +48,7 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
Higress can function as a microservice gateway, which can discovery microservices from various service registries, such as Nacos, ZooKeeper, Consul, Eureka, etc.
It deeply integrates of [Dubbo](https://github.com/apache/dubbo), [Nacos](https://github.com/alibaba/nacos), [Sentinel](https://github.com/alibaba/Sentinel) and other microservice technology stacks.
It deeply integrates with [Dubbo](https://github.com/apache/dubbo), [Nacos](https://github.com/alibaba/nacos), [Sentinel](https://github.com/alibaba/Sentinel) and other microservice technology stacks.
- **Security gateway**:
@@ -57,7 +58,7 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
- **Easy to use**
Provide one-stop gateway solutions for traffic scheduling, service management, and security protection, support Console, K8s Ingress, and Gateway API configuration methods, and also support HTTP to Dubbo protocol conversion, and easily complete protocol mapping configuration.
Provides one-stop gateway solutions for traffic scheduling, service management, and security protection, support Console, K8s Ingress, and Gateway API configuration methods, and also support HTTP to Dubbo protocol conversion, and easily complete protocol mapping configuration.
- **Easy to expand**
@@ -73,7 +74,7 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
- **Security**
Provides JWT, OIDC, custom authentication and authentication, deeply integrates open source web application firewall.
Provides JWT, OIDC, custom authentication and authentication, deeply integrates open-source web application firewall.
## Community
@@ -81,9 +82,25 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
### Thanks
Higress would not be possible without the valuable open-source work of projects in the community. We would like to extend a special thank-you to Envoy and Istio.
Higress would not be possible without the valuable open-source work of projects in the community. We would like to extend a special thank you to Envoy and Istio.
### Related Repositories
- Higress Console: https://github.com/higress-group/higress-console
- Higress Standalone: https://github.com/higress-group/higress-standalone
- Higress Standalone: https://github.com/higress-group/higress-standalone
### Contributors
<a href="https://github.com/alibaba/higress/graphs/contributors">
<img alt="contributors" src="https://contrib.rocks/image?repo=alibaba/higress"/>
</a>
### Star History
[![Star History Chart](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date)
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
↑ Back to Top ↑
</a>
</p>

206
README_JP.md Normal file
View File

@@ -0,0 +1,206 @@
<a name="readme-top"></a>
<h1 align="center">
<img src="https://img.alicdn.com/imgextra/i2/O1CN01NwxLDd20nxfGBjxmZ_!!6000000006895-2-tps-960-290.png" alt="Higress" width="240" height="72.5">
<br>
AIゲートウェイ
</h1>
<h4 align="center"> AIネイティブAPIゲートウェイ </h4>
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[**公式サイト**](https://higress.cn/) &nbsp; |
&nbsp; [**ドキュメント**](https://higress.cn/docs/latest/overview/what-is-higress/) &nbsp; |
&nbsp; [**ブログ**](https://higress.cn/blog/) &nbsp; |
&nbsp; [**電子書籍**](https://higress.cn/docs/ebook/wasm14/) &nbsp; |
&nbsp; [**開発ガイド**](https://higress.cn/docs/latest/dev/architecture/) &nbsp; |
&nbsp; [**AIプラグイン**](https://higress.cn/plugin/) &nbsp;
<p>
<a href="README_EN.md"> English <a/> | <a href="README.md">中文<a/> | 日本語
</p>
Higressは、IstioとEnvoyをベースにしたクラウドネイティブAPIゲートウェイで、Go/Rust/JSなどを使用してWasmプラグインを作成できます。数十の既製の汎用プラグインと、すぐに使用できるコンソールを提供していますデモは[こちら](http://demo.higress.io/))。
Higressは、Tengineのリロードが長時間接続のビジネスに影響を与える問題や、gRPC/Dubboの負荷分散能力の不足を解決するために、Alibaba内部で誕生しました。
Alibaba Cloudは、Higressを基盤にクラウドネイティブAPIゲートウェイ製品を構築し、多くの企業顧客に99.99%のゲートウェイ高可用性保証サービスを提供しています。
Higressは、AIゲートウェイ機能を基盤に、Tongyi Qianwen APP、Bailian大規模モデルAPI、機械学習PAIプラットフォームなどのAIビジネスをサポートしています。また、国内の主要なAIGC企業ZeroOneやAI製品FastGPTにもサービスを提供しています。
![](https://img.alicdn.com/imgextra/i2/O1CN011AbR8023V8R5N0HcA_!!6000000007260-2-tps-1080-606.png)
## 目次
- [**クイックスタート**](#クイックスタート)
- [**機能紹介**](#機能紹介)
- [**使用シナリオ**](#使用シナリオ)
- [**主な利点**](#主な利点)
- [**コミュニティ**](#コミュニティ)
## クイックスタート
HigressはDockerだけで起動でき、個人開発者がローカルで学習用にセットアップしたり、簡易サイトを構築するのに便利です。
```bash
# 作業ディレクトリを作成
mkdir higress; cd higress
# Higressを起動し、設定ファイルを作業ディレクトリに書き込みます
docker run -d --rm --name higress-ai -v ${PWD}:/data \
-p 8001:8001 -p 8080:8080 -p 8443:8443 \
higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/all-in-one:latest
```
リスンポートの説明は以下の通りです:
- 8001ポートHigress UIコンソールのエントリーポイント
- 8080ポートゲートウェイのHTTPプロトコルエントリーポイント
- 8443ポートゲートウェイのHTTPSプロトコルエントリーポイント
**HigressのすべてのDockerイメージは専用のリポジトリを使用しており、Docker Hubの国内アクセス不可の影響を受けません**
K8sでのHelmデプロイなどの他のインストール方法については、公式サイトの[クイックスタートドキュメント](https://higress.cn/docs/latest/user/quickstart/)を参照してください。
## 使用シナリオ
- **AIゲートウェイ**:
Higressは、国内外のすべてのLLMモデルプロバイダーと統一されたプロトコルで接続でき、豊富なAI可観測性、多モデル負荷分散/フォールバック、AIトークンフロー制御、AIキャッシュなどの機能を備えています。
![](https://img.alicdn.com/imgextra/i1/O1CN01fNnhCp1cV8mYPRFeS_!!6000000003605-0-tps-1080-608.jpg)
- **Kubernetes Ingressゲートウェイ**:
HigressはK8sクラスターのIngressエントリーポイントゲートウェイとして機能し、多くのK8s Nginx Ingressの注釈に対応しています。K8s Nginx IngressからHigressへのスムーズな移行が可能です。
[Gateway API](https://gateway-api.sigs.k8s.io/)標準をサポートし、ユーザーがIngress APIからGateway APIにスムーズに移行できるようにします。
ingress-nginxと比較して、リソースの消費が大幅に減少し、ルーティングの変更が10倍速く反映されます。
![](https://img.alicdn.com/imgextra/i1/O1CN01bhEtb229eeMNBWmdP_!!6000000008093-2-tps-750-547.png)
![](https://img.alicdn.com/imgextra/i1/O1CN01bqRets1LsBGyitj4S_!!6000000001354-2-tps-887-489.png)
- **マイクロサービスゲートウェイ**:
Higressはマイクロサービスゲートウェイとして機能し、Nacos、ZooKeeper、Consul、Eurekaなどのさまざまなサービスレジストリからサービスを発見し、ルーティングを構成できます。
また、[Dubbo](https://github.com/apache/dubbo)、[Nacos](https://github.com/alibaba/nacos)、[Sentinel](https://github.com/alibaba/Sentinel)などのマイクロサービス技術スタックと深く統合されています。Envoy C++ゲートウェイコアの優れたパフォーマンスに基づいて、従来のJavaベースのマイクロサービスゲートウェイと比較して、リソース使用率を大幅に削減し、コストを削減できます。
![](https://img.alicdn.com/imgextra/i4/O1CN01v4ZbCj1dBjePSMZ17_!!6000000003698-0-tps-1613-926.jpg)
- **セキュリティゲートウェイ**:
Higressはセキュリティゲートウェイとして機能し、WAF機能を提供し、key-auth、hmac-auth、jwt-auth、basic-auth、oidcなどのさまざまな認証戦略をサポートします。
## 主な利点
- **プロダクションレベル**
Alibabaで2年以上のプロダクション検証を経た内部製品から派生し、毎秒数十万のリクエストを処理する大規模なシナリオをサポートします。
Nginxのリロードによるトラフィックの揺れを完全に排除し、構成変更がミリ秒単位で反映され、ビジネスに影響を与えません。AIビジネスなどの長時間接続シナリオに特に適しています。
- **ストリーム処理**
リクエスト/レスポンスボディの完全なストリーム処理をサポートし、Wasmプラグインを使用してSSEServer-Sent Eventsなどのストリームプロトコルのメッセージをカスタマイズして処理できます。
AIビジネスなどの大帯域幅シナリオで、メモリ使用量を大幅に削減できます。
- **拡張性**
AI、トラフィック管理、セキュリティ保護などの一般的な機能をカバーする豊富な公式プラグインライブラリを提供し、90以上のビジネスシナリオのニーズを満たします。
Wasmプラグイン拡張を主力とし、サンドボックス隔離を通じてメモリの安全性を確保し、複数のプログラミング言語をサポートし、プラグインバージョンの独立したアップグレードを許可し、トラフィックに影響を与えずにゲートウェイロジックをホットアップデートできます。
- **安全で使いやすい**
Ingress APIおよびGateway API標準に基づき、すぐに使用できるUIコンソールを提供し、WAF保護プラグイン、IP/Cookie CC保護プラグインをすぐに使用できます。
Let's Encryptの自動証明書発行および更新をサポートし、K8sを使用せずにデプロイでき、1行のDockerコマンドで起動でき、個人開発者にとって便利です。
## 機能紹介
### AIゲートウェイデモ展示
[OpenAIから他の大規模モデルへの移行を30秒で完了
](https://www.bilibili.com/video/BV1dT421a7w7/?spm_id_from=333.788.recommend_more_video.14)
### Higress UIコンソール
- **豊富な可観測性**
すぐに使用できる可観測性を提供し、GrafanaPrometheusは組み込みのものを使用することも、自分で構築したものを接続することもできます。
![](./docs/images/monitor.gif)
- **プラグイン拡張メカニズム**
公式にはさまざまなプラグインが提供されており、ユーザーは[独自のプラグインを開発](./plugins/wasm-go)し、Docker/OCIイメージとして構築し、コンソールで構成して、プラグインロジックをリアルタイムで変更できます。トラフィックに影響を与えずにプラグインロジックをホットアップデートできます。
![](./docs/images/plugin.gif)
- **さまざまなサービス発見**
デフォルトでK8s Serviceサービス発見を提供し、構成を通じてNacos/ZooKeeperなどのレジストリに接続してサービスを発見することも、静的IPまたはDNSに基づいて発見することもできます。
![](./docs/images/service-source.gif)
- **ドメインと証明書**
TLS証明書を作成および管理し、ドメインのHTTP/HTTPS動作を構成できます。ドメインポリシーでは、特定のドメインに対してプラグインを適用することができます。
![](./docs/images/domain.gif)
- **豊富なルーティング機能**
上記で定義されたサービス発見メカニズムを通じて、発見されたサービスはサービスリストに表示されます。ルーティングを作成する際に、ドメインを選択し、ルーティングマッチングメカニズムを定義し、ターゲットサービスを選択してルーティングを行います。ルーティングポリシーでは、特定のルーティングに対してプラグインを適用することができます。
![](./docs/images/route-service.gif)
## コミュニティ
### 感謝
EnvoyとIstioのオープンソースの取り組みがなければ、Higressは実現できませんでした。これらのプロジェクトに最も誠実な敬意を表します。
### 交流グループ
![image](https://img.alicdn.com/imgextra/i2/O1CN01BkopaB22ZsvamFftE_!!6000000007135-0-tps-720-405.jpg)
### 技術共有
WeChat公式アカウント
![](https://img.alicdn.com/imgextra/i1/O1CN01WnQt0q1tcmqVDU73u_!!6000000005923-0-tps-258-258.jpg)
### 関連リポジトリ
- Higressコンソールhttps://github.com/higress-group/higress-console
- Higressスタンドアロン版https://github.com/higress-group/higress-standalone
### 貢献者
<a href="https://github.com/alibaba/higress/graphs/contributors">
<img alt="contributors" src="https://contrib.rocks/image?repo=alibaba/higress"/>
</a>
### スターの歴史
[![スターの歴史チャート](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date)
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
↑ トップに戻る ↑
</a>
</p>

View File

@@ -4,6 +4,7 @@
| Version | Supported |
| ------- | ------------------ |
| 2.x.x | :white_check_mark: |
| 1.x.x | :white_check_mark: |
| < 1.0.0 | :x: |

View File

@@ -1 +1 @@
v2.0.1
v2.0.6-rc.3

View File

@@ -341,7 +341,7 @@ type WasmPlugin struct {
// Extended by Higress, matching rules take effect
MatchRules []*MatchRule `protobuf:"bytes,102,rep,name=match_rules,json=matchRules,proto3" json:"match_rules,omitempty"`
// disable the default config
DefaultConfigDisable bool `protobuf:"varint,103,opt,name=default_config_disable,json=defaultConfigDisable,proto3" json:"default_config_disable,omitempty"`
DefaultConfigDisable *wrappers.BoolValue `protobuf:"bytes,103,opt,name=default_config_disable,json=defaultConfigDisable,proto3" json:"default_config_disable,omitempty"`
}
func (x *WasmPlugin) Reset() {
@@ -467,11 +467,11 @@ func (x *WasmPlugin) GetMatchRules() []*MatchRule {
return nil
}
func (x *WasmPlugin) GetDefaultConfigDisable() bool {
func (x *WasmPlugin) GetDefaultConfigDisable() *wrappers.BoolValue {
if x != nil {
return x.DefaultConfigDisable
}
return false
return nil
}
// Extended by Higress
@@ -480,11 +480,11 @@ type MatchRule struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Ingress []string `protobuf:"bytes,1,rep,name=ingress,proto3" json:"ingress,omitempty"`
Domain []string `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
Config *_struct.Struct `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"`
ConfigDisable bool `protobuf:"varint,4,opt,name=config_disable,json=configDisable,proto3" json:"config_disable,omitempty"`
Service []string `protobuf:"bytes,5,rep,name=service,proto3" json:"service,omitempty"`
Ingress []string `protobuf:"bytes,1,rep,name=ingress,proto3" json:"ingress,omitempty"`
Domain []string `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
Config *_struct.Struct `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"`
ConfigDisable *wrappers.BoolValue `protobuf:"bytes,4,opt,name=config_disable,json=configDisable,proto3" json:"config_disable,omitempty"`
Service []string `protobuf:"bytes,5,rep,name=service,proto3" json:"service,omitempty"`
}
func (x *MatchRule) Reset() {
@@ -540,11 +540,11 @@ func (x *MatchRule) GetConfig() *_struct.Struct {
return nil
}
func (x *MatchRule) GetConfigDisable() bool {
func (x *MatchRule) GetConfigDisable() *wrappers.BoolValue {
if x != nil {
return x.ConfigDisable
}
return false
return nil
}
func (x *MatchRule) GetService() []string {
@@ -686,7 +686,7 @@ var file_extensions_v1alpha1_wasmplugin_proto_rawDesc = []byte{
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x22, 0x8d, 0x06, 0x0a, 0x0a, 0x57, 0x61, 0x73, 0x6d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x6f, 0x22, 0xa9, 0x06, 0x0a, 0x0a, 0x57, 0x61, 0x73, 0x6d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75,
0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x53, 0x0a, 0x11, 0x69, 0x6d,
@@ -731,52 +731,55 @@ var file_extensions_v1alpha1_wasmplugin_proto_rawDesc = []byte{
0x73, 0x18, 0x66, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73,
0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61,
0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x52,
0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x64,
0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x50, 0x0a, 0x16, 0x64,
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x67, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x65, 0x66,
0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c,
0x65, 0x22, 0xaf, 0x01, 0x0a, 0x09, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x12,
0x18, 0x0a, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09,
0x52, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
0x6e, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x73,
0x61, 0x62, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x22, 0x41, 0x0a, 0x08, 0x56, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
0x35, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68,
0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61,
0x72, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x22, 0x7e, 0x0a, 0x06, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x72,
0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65,
0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x72, 0x6f, 0x6d,
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x45, 0x0a, 0x0b, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x48, 0x41, 0x53, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
0x41, 0x55, 0x54, 0x48, 0x4e, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x5a,
0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x54, 0x53, 0x10, 0x03, 0x2a, 0x42, 0x0a,
0x0a, 0x50, 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x55,
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43,
0x59, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x66, 0x4e, 0x6f, 0x74, 0x50, 0x72, 0x65, 0x73,
0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10,
0x02, 0x2a, 0x26, 0x0a, 0x0e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12,
0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x01, 0x2a, 0x2d, 0x0a, 0x0c, 0x46, 0x61, 0x69,
0x6c, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x41, 0x49,
0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x41, 0x49,
0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68,
0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e,
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f,
0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x22, 0xcb, 0x01,
0x0a, 0x09, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x69,
0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e,
0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2f, 0x0a,
0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41,
0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c,
0x75, 0x65, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c,
0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03,
0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x41, 0x0a, 0x08, 0x56,
0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65,
0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x22, 0x7e,
0x0a, 0x06, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x0a,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e,
0x32, 0x2b, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e,
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45,
0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x45,
0x0a, 0x0b, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x15, 0x0a,
0x11, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x48, 0x41,
0x53, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x4e, 0x10, 0x01, 0x12,
0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x5a, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54,
0x41, 0x54, 0x53, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x0a, 0x50, 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c,
0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
0x45, 0x44, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x49,
0x66, 0x4e, 0x6f, 0x74, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a,
0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x02, 0x2a, 0x26, 0x0a, 0x0e, 0x45, 0x6e, 0x76,
0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x49,
0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53, 0x54, 0x10,
0x01, 0x2a, 0x2d, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
0x79, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x41, 0x49, 0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10,
0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x41, 0x49, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x01,
0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61,
0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31,
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -804,6 +807,7 @@ var file_extensions_v1alpha1_wasmplugin_proto_goTypes = []interface{}{
(*EnvVar)(nil), // 7: higress.extensions.v1alpha1.EnvVar
(*_struct.Struct)(nil), // 8: google.protobuf.Struct
(*wrappers.Int32Value)(nil), // 9: google.protobuf.Int32Value
(*wrappers.BoolValue)(nil), // 10: google.protobuf.BoolValue
}
var file_extensions_v1alpha1_wasmplugin_proto_depIdxs = []int32{
1, // 0: higress.extensions.v1alpha1.WasmPlugin.image_pull_policy:type_name -> higress.extensions.v1alpha1.PullPolicy
@@ -814,14 +818,16 @@ var file_extensions_v1alpha1_wasmplugin_proto_depIdxs = []int32{
6, // 5: higress.extensions.v1alpha1.WasmPlugin.vm_config:type_name -> higress.extensions.v1alpha1.VmConfig
8, // 6: higress.extensions.v1alpha1.WasmPlugin.default_config:type_name -> google.protobuf.Struct
5, // 7: higress.extensions.v1alpha1.WasmPlugin.match_rules:type_name -> higress.extensions.v1alpha1.MatchRule
8, // 8: higress.extensions.v1alpha1.MatchRule.config:type_name -> google.protobuf.Struct
7, // 9: higress.extensions.v1alpha1.VmConfig.env:type_name -> higress.extensions.v1alpha1.EnvVar
2, // 10: higress.extensions.v1alpha1.EnvVar.value_from:type_name -> higress.extensions.v1alpha1.EnvValueSource
11, // [11:11] is the sub-list for method output_type
11, // [11:11] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension type_name
11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
10, // 8: higress.extensions.v1alpha1.WasmPlugin.default_config_disable:type_name -> google.protobuf.BoolValue
8, // 9: higress.extensions.v1alpha1.MatchRule.config:type_name -> google.protobuf.Struct
10, // 10: higress.extensions.v1alpha1.MatchRule.config_disable:type_name -> google.protobuf.BoolValue
7, // 11: higress.extensions.v1alpha1.VmConfig.env:type_name -> higress.extensions.v1alpha1.EnvVar
2, // 12: higress.extensions.v1alpha1.EnvVar.value_from:type_name -> higress.extensions.v1alpha1.EnvValueSource
13, // [13:13] is the sub-list for method output_type
13, // [13:13] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
}
func init() { file_extensions_v1alpha1_wasmplugin_proto_init() }

View File

@@ -112,7 +112,7 @@ message WasmPlugin {
// Extended by Higress, matching rules take effect
repeated MatchRule match_rules = 102;
// disable the default config
bool default_config_disable = 103;
google.protobuf.BoolValue default_config_disable = 103;
}
// Extended by Higress
@@ -120,7 +120,7 @@ message MatchRule {
repeated string ingress = 1;
repeated string domain = 2;
google.protobuf.Struct config = 3;
bool config_disable = 4;
google.protobuf.BoolValue config_disable = 4;
repeated string service = 5;
}

143
docs/architecture.md Normal file
View File

@@ -0,0 +1,143 @@
# Higress 核心组件和原理
Higress 是基于 Envoy 和 Istio 进行二次定制化开发构建和功能增强,同时利用 Envoy 和 Istio 一些插件机制,实现了一个轻量级的网关服务。其包括 3 个核心组件Higress Controller控制器、Higress Gateway网关和 Higress Console控制台
下图概况了其核心工作流程:
![img](./images/img_02_01.png)
本章将重点介绍 Higress 的两个核心组件Higress Controller 和 Higress Gateway。
## 1 Higress Console
Higress Console 是 Higress 网关的管理控制台,主要功能是管理 Higress 网关的路由配置、插件配置等。
### 1.1 Higress Admin SDK
Higress Admin SDK 脱胎于 Higress Console。起初它作为 Higress Console 的一部分,为前端界面提供实际的功能支持。后来考虑到对接外部系统等需求,将配置管理的部分剥离出来,形成一个独立的逻辑组件,便于和各个系统进行对接。目前支持服务来源管理、服务管理、路由管理、域名管理、证书管理、插件管理等功能。
Higress Admin SDK 现在只提供 Java 版本,且要求 JDK 版本不低于 17。具体如何集成请参考 Higress 官方 BLOG [如何使用 Higress Admin SDK 进行配置管理](https://higress.io/zh-cn/blog/admin-sdk-intro)。
## 2 Higress Controller
Higress Controller控制器 是 Higress 的核心组件,其功能主要是实现 Higress 网关的服务发现、动态配置管理以及动态下发配置给数据面。Higress Controller 内部包含两个子组件Discovery 和 Higress Core。
### 2.1 Discovery 组件
Discovery 组件Istio Pilot-Discovery是 Istio 的核心组件负责服务发现、配置管理、证书签发、控制面和数据面之间的通讯和配置下发等。Discovery 内部结构比较复杂,本文只介绍 Discovery 配置管理和服务发现的基本原理,其核心功能的详细介绍可以参考赵化冰老师的 BLOG [Istio Pilot 组件介绍](https://www.zhaohuabing.com/post/2019-10-21-pilot-discovery-code-analysis/)。
Discovery 将 Kubernetes Service、Gateway API 配置等转换成 Istio 配置,然后将所有 Istio 配置合并转成符合 xDS 接口规范的数据结构,通过 GRPC 下发到数据面的 Envoy。其工作原理如下图
![img](./images/img_02_02.png)
#### 2.1.1 Config Controller
Discovery 为了更好管理 Istio 配置来源,提供 `Config Controller` 用于管理各种配置来源,目前支持 4 种类型的 `Config Controller`
- Kubernetes使用 Kubernetes 作为配置信息来源,该方式的直接依赖 Kubernetes 强大的 CRD 机制来存储配置信息,简单方便,是 Istio 最开始使用的配置信息存储方案, 其中包括 `Kubernetes Controller``Gateway API Controller` 两个实现。
- MCPMesh Configuration Protocol使用 Kubernetes 存储配置数据导致了 Istio 和 Kubernetes 的耦合,限制了 Istio 在非 Kubernetes 环境下的运用。为了解决该耦合Istio 社区提出了 MCP。
- Memory一个基于内存的 Config Controller 实现,主要用于测试。
- File一个基于文件的 Config Controller 实现,主要用于测试。
1. Istio 配置
Istio 配置包括:`Gateway``VirtualService``DestinationRule``ServiceEntry``EnvoyFilter``WasmPlugin``WorkloadEntry``WorkloadGroup` 等,可以参考 Istio 官方文档[流量管理](https://istio.io/latest/zh/docs/reference/config/networking/)了解更多配置信息。
2. Gateway API 配置
Gateway API 配置包括:`GatewayClass``Gateway``HttpRoute``TCPRoute``GRPCRoute` 等, 可以参考 Gateway API 官方文档 [Gateway API](https://gateway-api.sigs.k8s.io/api-types/gateway/) 了解更多配置信息。
3. MCP over xDS
Discovery 作为 MCP Client任何实现了 MCP 协议的 Server 都可以通过 MCP 协议向 Discovery 下发配置信息,从而消除了 Istio 和 Kubernetes 之间的耦合, 同时使 Istio 的配置信息处理更加灵活和可扩展。
同时 MCP 是一种基于 xDS 协议的配置管理协议Higress Core 通过实现 MCP 协议,使 Higress Core 成为 Discovery 的 Istio 配置来源。
4. Config Controller 来源配置
`higress-system` 命名空间中,名为 `higress-config` 的 Configmap 中,`mesh` 配置项包含一个 `configSources` 属性用于配置来源。其 Configmap 部分配置项如下:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: higress-config
namespace: higress-system
data:
mesh: |-
accessLogEncoding: TEXT
...
configSources:
- address: xds://127.0.0.1:15051
- address: k8s://
...
meshNetworks: "networks: {}"
```
#### 2.1.2 Service Controller
`Service Controller` 用于管理各种 `Service Registry`,提供服务发现数据,目前 Istio 支持的 `Service Registry` 主要包括:
- Kubernetes对接 Kubernetes Registry可以将 Kubernetes 中定义的 Service 和 Endpoint 采集到 Istio 中。
- Memory一个基于内存的 Service Controller 实现,主要用于测试。
### 2.2 Higress Core 组件
Higress Core 核心逻辑如下图:
![img](./images/img_02_03.png)
Higress Core 内部包含两个核心子组件: Ingress Config 和 Cert Server。
#### 2.2.1 Ingress Config
Ingress Config 包含 6 个控制器,各自负责不同的功能:
- Ingress Controller监听 Ingress 资源,将 Ingress 转换为 Istio 的 Gateway、VirtualService、DestinationRule 等资源。
- Gateway Controller监听 Gateway、VirtualService、DestinationRule 等资源。
- McpBridge Controller根据 McpBridge 的配置,将来自 Nacos、Eureka、Consul、Zookeeper 等外部注册中心或 DNS 的服务信息转换成 Istio ServiceEntry 资源。
- Http2Rpc Controller监听 Http2Rpc 资源,实现 HTTP 协议到 RPC 协议的转换。用户可以通过配置协议转换,将 RPC 服务以 HTTP 接口的形式暴露,从而使用 HTTP 请求调用 RPC 接口。
- WasmPlugin Controller监听 WasmPlugin 资源,将 Higress WasmPlugin 转化为 Istio WasmPlugin。Higress WasmPlugin 在 Istio WasmPlugin 的基础上进行了扩展,支持全局、路由、域名、服务级别的配置。
- ConfigmapMgr监听 Higress 的全局配置 `higress-config` ConfigMap可以根据 tracing、gzip 等配置构造 EnvoyFilter。
#### 2.2.2 Cert Server
Cert Server 管理 Secret 资源和证书自动签发。
## 3 Higress Gateway
Higress Gateway 内部包含两个子组件Pilot Agent 和 Envoy。Pilot Agent 主要负责 Envoy 的启动和配置,同时代理 Envoy xDS 请求到 Discovery。 Envoy 作为数据面,负责接收控制面的配置下发,并代理请求到业务服务。 Pilot Agent 和 Envoy 之间通讯协议是使用 xDS 协议, 通过 Unix Domain SocketUDS进行通信。
Envoy 核心架构如下图:
![img](./images/img_02_04.png)
### 1 Envoy 核心组件
- 下游Downstream:
下游是 Envoy 的客户端,它们负责发起请求并接收 Envoy 的响应。下游通常是最终用户的设备或服务,它们通过 Envoy 代理与后端服务进行通信。
- 上游Upstream:
上游是 Envoy 的后端服务器,它们接收 Envoy 代理的连接和请求。上游提供服务或数据,对来自下游客户端的请求进行处理并返回响应。
- 监听器Listener:
监听器是可以接受来自下游客户端连接的网络地址(如 IP 地址和端口Unix Domain Socket 等。Envoy 支持在单个进程中配置任意数量的监听器。监听器可以通过 `Listener Discovery ServiceLDS`来动态发现和更新。
- 路由Router:
路由器是 Envoy 中连接下游和上游的桥梁。它负责决定如何将监听器接收到的请求路由到适当的集群。路由器根据配置的路由规则如路径、HTTP 标头 等,来确定请求的目标集群,从而实现精确的流量控制和路由。路由器可以通过 `Route Discovery ServiceRDS`来动态发现和更新。
- 集群Cluster:
集群是一组逻辑上相似的服务提供者的集合。集群成员的选择由负载均衡策略决定,确保请求能够均匀或按需分配到不同的服务实例。集群可以通过 `Cluster Discovery ServiceCDS`来动态发现和更新。
- 端点Endpoint:
端点是上游集群中的具体服务实例,可以是 IP 地址和端口号的组合。端点可以通过 `Endpoint Discovery ServiceEDS`来动态发现和更新。
- SSL/TLS:
Envoy 可以通过 `Secret Discovery Service (SDS)` 动态获取监听器和集群所需的 TLS 证书、私钥以及信任的根证书和撤销机制等配置信息。
通过这些组件的协同工作Envoy 能够高效地处理网络请求,提供流量管理、负载均衡、服务发现和动态路由等关键功能。
要详细了解 Envoy 的工作原理,可以参考[Envoy 官方文档](https://www.envoyproxy.io/docs/envoy/latest/intro/intro),最佳的方式可以通过一个请求通过 [Envoy 代理的生命周期](https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request)事件的过程来理解 Envoy 的工作原理。
## 参考
- [1] [Istio Pilot 组件介绍](https://www.zhaohuabing.com/post/2019-10-21-pilot-discovery-code-analysis/)
- [2] [Istio 服务注册插件机制代码解析](https://www.zhaohuabing.com/post/2019-02-18-pilot-service-registry-code-analysis/)
- [3] [Istio Pilot代码深度解析](https://www.zhaohuabing.com/post/2019-10-21-pilot-discovery-code-analysis/)
- [4] [Envoy 官方文档](https://www.envoyproxy.io/docs/envoy/latest/intro/intro)

BIN
docs/images/img_02_01.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
docs/images/img_02_02.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
docs/images/img_02_03.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

BIN
docs/images/img_02_04.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.0.1
appVersion: 2.0.6-rc.3
description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -10,4 +10,4 @@ name: higress-core
sources:
- http://github.com/alibaba/higress
type: application
version: 2.0.1
version: 2.0.6-rc.3

View File

@@ -1,3 +1,4 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:

View File

@@ -7,9 +7,6 @@ Rendering the pod template of gateway component.
template:
metadata:
annotations:
{{- if .Values.global.enableHigressIstio }}
"enableHigressIstio": "true"
{{- end }}
{{- if .Values.gateway.podAnnotations }}
{{- toYaml .Values.gateway.podAnnotations | nindent 6 }}
{{- end }}
@@ -268,11 +265,7 @@ template:
{{- end }}
- name: higress-ca-root-cert
configMap:
{{- if .Values.global.enableHigressIstio }}
name: istio-ca-root-cert
{{- else }}
name: higress-ca-root-cert
{{- end }}
- name: config
configMap:
name: higress-config

View File

@@ -9,7 +9,7 @@
accessLogFile: "/dev/stdout"
{{- end }}
ingressControllerMode: "OFF"
accessLogFormat: '{"authority":"%REQ(X-ENVOY-ORIGINAL-HOST?:AUTHORITY)%","bytes_received":"%BYTES_RECEIVED%","bytes_sent":"%BYTES_SENT%","downstream_local_address":"%DOWNSTREAM_LOCAL_ADDRESS%","downstream_remote_address":"%DOWNSTREAM_REMOTE_ADDRESS%","duration":"%DURATION%","istio_policy_status":"%DYNAMIC_METADATA(istio.mixer:status)%","method":"%REQ(:METHOD)%","path":"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%","protocol":"%PROTOCOL%","request_id":"%REQ(X-REQUEST-ID)%","requested_server_name":"%REQUESTED_SERVER_NAME%","response_code":"%RESPONSE_CODE%","response_flags":"%RESPONSE_FLAGS%","route_name":"%ROUTE_NAME%","start_time":"%START_TIME%","trace_id":"%REQ(X-B3-TRACEID)%","upstream_cluster":"%UPSTREAM_CLUSTER%","upstream_host":"%UPSTREAM_HOST%","upstream_local_address":"%UPSTREAM_LOCAL_ADDRESS%","upstream_service_time":"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%","upstream_transport_failure_reason":"%UPSTREAM_TRANSPORT_FAILURE_REASON%","user_agent":"%REQ(USER-AGENT)%","x_forwarded_for":"%REQ(X-FORWARDED-FOR)%","response_code_details":"%RESPONSE_CODE_DETAILS%"}
accessLogFormat: '{"ai_log":"%FILTER_STATE(wasm.ai_log:PLAIN)%","authority":"%REQ(X-ENVOY-ORIGINAL-HOST?:AUTHORITY)%","bytes_received":"%BYTES_RECEIVED%","bytes_sent":"%BYTES_SENT%","downstream_local_address":"%DOWNSTREAM_LOCAL_ADDRESS%","downstream_remote_address":"%DOWNSTREAM_REMOTE_ADDRESS%","duration":"%DURATION%","istio_policy_status":"%DYNAMIC_METADATA(istio.mixer:status)%","method":"%REQ(:METHOD)%","path":"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%","protocol":"%PROTOCOL%","request_id":"%REQ(X-REQUEST-ID)%","requested_server_name":"%REQUESTED_SERVER_NAME%","response_code":"%RESPONSE_CODE%","response_flags":"%RESPONSE_FLAGS%","route_name":"%ROUTE_NAME%","start_time":"%START_TIME%","trace_id":"%REQ(X-B3-TRACEID)%","upstream_cluster":"%UPSTREAM_CLUSTER%","upstream_host":"%UPSTREAM_HOST%","upstream_local_address":"%UPSTREAM_LOCAL_ADDRESS%","upstream_service_time":"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%","upstream_transport_failure_reason":"%UPSTREAM_TRANSPORT_FAILURE_REASON%","user_agent":"%REQ(USER-AGENT)%","x_forwarded_for":"%REQ(X-FORWARDED-FOR)%","response_code_details":"%RESPONSE_CODE_DETAILS%"}
'
dnsRefreshRate: 200s
@@ -20,11 +20,7 @@
# When processing a leaf namespace Istio will search for declarations in that namespace first
# and if none are found it will search in the root namespace. Any matching declaration found in the root namespace
# is processed as if it were declared in the leaf namespace.
{{- if .Values.global.enableHigressIstio }}
rootNamespace: {{ .Values.meshConfig.rootNamespace | default .Values.global.istioNamespace }}
{{- else }}
rootNamespace: {{ .Release.Namespace }}
{{- end }}
configSources:
- address: "xds://127.0.0.1:15051"
@@ -85,12 +81,8 @@
discoveryAddress: {{ printf "istiod.%s.svc" .Release.Namespace }}:15012
{{- end }}
{{- else }}
{{- if .Values.global.enableHigressIstio }}
discoveryAddress: {{ printf "istiod.%s.svc" .Values.global.istioNamespace }}:15012
{{- else }}
discoveryAddress: {{ include "controller.name" . }}.{{.Release.Namespace}}.svc:15012
{{- end }}
{{- end }}
proxyStatsMatcher:
inclusionRegexps:
- ".*"
@@ -116,6 +108,12 @@ data:
{{- $existingData = index $existingConfig.data "higress" | default "{}" | fromYaml }}
{{- end }}
{{- $newData := dict }}
{{- if hasKey .Values "upstream" }}
{{- $_ := set $newData "upstream" .Values.upstream }}
{{- end }}
{{- if hasKey .Values "downstream" }}
{{- $_ := set $newData "downstream" .Values.downstream }}
{{- end }}
{{- if and (hasKey .Values "tracing") .Values.tracing.enable }}
{{- $_ := set $newData "tracing" .Values.tracing }}
{{- end }}

View File

@@ -129,3 +129,10 @@ rules:
- apiGroups: ["networking.internal.knative.dev"]
resources: ["ingresses/status"]
verbs: ["get","patch","update"]
# gateway api need
- apiGroups: ["apps"]
verbs: [ "get", "watch", "list", "update", "patch", "create", "delete" ]
resources: [ "deployments" ]
- apiGroups: [""]
verbs: [ "get", "watch", "list", "update", "patch", "create", "delete" ]
resources: [ "serviceaccounts"]

View File

@@ -69,6 +69,12 @@ spec:
fieldPath: spec.serviceAccountName
- name: DOMAIN_SUFFIX
value: {{ .Values.global.proxy.clusterDomain }}
- name: GATEWAY_NAME
value: {{ include "gateway.name" . }}
- name: PILOT_ENABLE_GATEWAY_API
value: "{{ .Values.global.enableGatewayAPI }}"
- name: PILOT_ENABLE_ALPHA_GATEWAY_API
value: "{{ .Values.global.enableGatewayAPI }}"
{{- if .Values.controller.env }}
{{- range $key, $val := .Values.controller.env }}
- name: {{ $key }}
@@ -90,7 +96,6 @@ spec:
volumeMounts:
- name: log
mountPath: /var/log
{{- if not .Values.global.enableHigressIstio }}
- name: discovery
image: "{{ .Values.pilot.hub | default .Values.global.hub }}/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Chart.AppVersion }}"
{{- if .Values.global.imagePullPolicy }}
@@ -131,6 +136,10 @@ spec:
periodSeconds: 3
timeoutSeconds: 5
env:
- name: ENABLE_PUSH_ALL_MCP_CLUSTERS
value: "{{ .Values.global.enablePushAllMCPClusters }}"
- name: PILOT_ENABLE_LDS_CACHE
value: "{{ .Values.global.enableLDSCache }}"
- name: PILOT_ENABLE_QUIC_LISTENERS
value: "true"
- name: VALIDATION_WEBHOOK_CONFIG_NAME
@@ -215,18 +224,16 @@ spec:
- name: HIGRESS_ENABLE_ISTIO_API
value: "true"
{{- end }}
{{- if .Values.global.enableGatewayAPI }}
- name: PILOT_ENABLE_GATEWAY_API
value: "true"
value: "false"
- name: PILOT_ENABLE_ALPHA_GATEWAY_API
value: "false"
- name: PILOT_ENABLE_GATEWAY_API_STATUS
value: "true"
value: "false"
- name: PILOT_ENABLE_GATEWAY_API_DEPLOYMENT_CONTROLLER
value: "false"
{{- end }}
{{- if not .Values.global.enableHigressIstio }}
- name: CUSTOM_CA_CERT_NAME
value: "higress-ca-root-cert"
{{- end }}
{{- if not (or .Values.global.local .Values.global.kind) }}
resources:
{{- if .Values.pilot.resources }}
@@ -263,7 +270,6 @@ spec:
- name: extracacerts
mountPath: /cacerts
{{- end }}
{{- end }}
{{- with .Values.controller.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
@@ -279,7 +285,6 @@ spec:
volumes:
- name: log
emptyDir: {}
{{- if not .Values.global.enableHigressIstio }}
- name: config
configMap:
name: higress-config
@@ -311,4 +316,3 @@ spec:
configMap:
name: pilot-jwks-extra-cacerts{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}
{{- end }}
{{- end }}

View File

@@ -9,7 +9,6 @@ spec:
type: {{ .Values.controller.service.type }}
ports:
{{- toYaml .Values.controller.ports | nindent 4 }}
{{- if not .Values.global.enableHigressIstio }}
- port: 15010
name: grpc-xds # plaintext
protocol: TCP
@@ -23,6 +22,5 @@ spec:
- port: 15014
name: http-monitoring # prometheus stats
protocol: TCP
{{- end }}
selector:
{{- include "controller.selectorLabels" . | nindent 4 }}

View File

@@ -1,7 +1,8 @@
{{- if eq .Values.gateway.kind "DaemonSet" -}}
{{- $o11y := .Values.global.o11y }}
{{- $unprivilegedPortSupported := true }}
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
{{- if eq .Values.gateway.unprivilegedPortSupported nil -}}
{{- $unprivilegedPortSupported := true }}
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
{{- $kernelVersion := $node.status.nodeInfo.kernelVersion }}
{{- if $kernelVersion }}
{{- $kernelVersion = regexFind "^(\\d+\\.\\d+\\.\\d+)" $kernelVersion }}
@@ -9,8 +10,9 @@
{{- $unprivilegedPortSupported = false }}
{{- end }}
{{- end }}
{{- end -}}
{{- $_ := set .Values.gateway "unprivilegedPortSupported" $unprivilegedPortSupported -}}
{{- end -}}
{{- $_ := set .Values.gateway "unprivilegedPortSupported" $unprivilegedPortSupported -}}
apiVersion: apps/v1
kind: DaemonSet

View File

@@ -1,6 +1,7 @@
{{- if eq .Values.gateway.kind "Deployment" -}}
{{- $unprivilegedPortSupported := true }}
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
{{- if eq .Values.gateway.unprivilegedPortSupported nil -}}
{{- $unprivilegedPortSupported := true }}
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
{{- $kernelVersion := $node.status.nodeInfo.kernelVersion }}
{{- if $kernelVersion }}
{{- $kernelVersion = regexFind "^(\\d+\\.\\d+\\.\\d+)" $kernelVersion }}
@@ -8,8 +9,9 @@
{{- $unprivilegedPortSupported = false }}
{{- end }}
{{- end }}
{{- end -}}
{{- $_ := set .Values.gateway "unprivilegedPortSupported" $unprivilegedPortSupported -}}
{{- end -}}
{{- $_ := set .Values.gateway "unprivilegedPortSupported" $unprivilegedPortSupported -}}
apiVersion: apps/v1
kind: Deployment

View File

@@ -0,0 +1,22 @@
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: {{ include "gateway.name" . }}-global-custom-response
namespace: {{ .Release.Namespace }}
labels:
{{- include "gateway.labels" . | nindent 4}}
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: GATEWAY
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
patch:
operation: INSERT_FIRST
value:
name: envoy.filters.http.custom_response
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.custom_response.v3.CustomResponse

View File

@@ -1,6 +1,8 @@
{{- if .Values.global.ingressClass }}
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: {{ .Values.global.ingressClass }}
spec:
controller: higress.io/higress-controller
controller: higress.io/higress-controller
{{- end }}

View File

@@ -3,14 +3,16 @@ global:
enableH3: false
enableIPv6: false
enableProxyProtocol: false
liteMetrics: true
enableLDSCache: true
enablePushAllMCPClusters: true
liteMetrics: false
xdsMaxRecvMsgSize: "104857600"
defaultUpstreamConcurrencyThreshold: 10000
enableSRDS: true
onDemandRDS: false
hostRDSMergeSubset: false
onlyPushRouteCluster: true
# IngressClass filters which ingress resources the higress controller watches.
# -- IngressClass filters which ingress resources the higress controller watches.
# The default ingress class is higress.
# There are some special cases for special ingress class.
# 1. When the ingress class is set as nginx, the higress controller will watch ingress
@@ -18,28 +20,38 @@ global:
# 2. When the ingress class is set empty, the higress controller will watch all ingress
# resources in the k8s cluster.
ingressClass: "higress"
# -- If not empty, Higress Controller will only watch resources in the specified namespace.
# When isolating different business systems using K8s namespace,
# if each namespace requires a standalone gateway instance,
# this parameter can be used to confine the Ingress watching of Higress within the given namespace.
watchNamespace: ""
# -- Whether to disable HTTP/2 in ALPN
disableAlpnH2: false
# -- If true, Higress Controller will update the status field of Ingress resources.
# When migrating from Nginx Ingress, in order to avoid status field of Ingress objects being overwritten,
# this parameter needs to be set to false,
# so Higress won't write the entry IP to the status field of the corresponding Ingress object.
enableStatus: true
# whether to use autoscaling/v2 template for HPA settings
# -- whether to use autoscaling/v2 template for HPA settings
# for internal usage only, not to be configured by users.
autoscalingv2API: true
local: false # When deploying to a local cluster (e.g.: kind cluster), set this to true.
# -- When deploying to a local cluster (e.g.: kind cluster), set this to true.
local: false
kind: false # Deprecated. Please use "global.local" instead. Will be removed later.
enableIstioAPI: false
# -- If true, Higress Controller will monitor istio resources as well
enableIstioAPI: true
# -- If true, Higress Controller will monitor Gateway API resources as well
enableGatewayAPI: false
# Deprecated
enableHigressIstio: false
# Used to locate istiod.
# -- Used to locate istiod.
istioNamespace: istio-system
# enable pod disruption budget for the control plane, which is used to
# -- enable pod disruption budget for the control plane, which is used to
# ensure Istio control plane components are gradually upgraded or recovered.
defaultPodDisruptionBudget:
enabled: false
# The values aren't mutable due to a current PodDisruptionBudget limitation
# minAvailable: 1
# A minimal set of requested resources to applied to all deployments so that
# -- A minimal set of requested resources to applied to all deployments so that
# Horizontal Pod Autoscaler will be able to function (if set).
# Each component can overwrite these default values by adding its own resources
# block in the relevant section below and setting the desired resources values.
@@ -51,16 +63,16 @@ global:
# cpu: 100m
# memory: 128Mi
# Default hub for Istio images.
# -- Default hub for Istio images.
# Releases are published to docker hub under 'istio' project.
# Dev builds from prow are on gcr.io
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
# Specify image pull policy if default behavior isn't desired.
# -- Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
imagePullPolicy: ""
# ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace
# -- ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace
# to use for pulling any images in pods that reference this ServiceAccount.
# For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing)
# ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects.
@@ -68,14 +80,14 @@ global:
imagePullSecrets: []
# - private-registry-key
# Enabled by default in master for maximising testing.
# -- Enabled by default in master for maximising testing.
istiod:
enableAnalysis: false
# To output all istio components logs in json format by adding --log_as_json argument to each container argument
# -- To output all istio components logs in json format by adding --log_as_json argument to each container argument
logAsJson: false
# Comma-separated minimum per-scope logging level of messages to output, in the form of <scope>:<level>,<scope>:<level>
# -- Comma-separated minimum per-scope logging level of messages to output, in the form of <scope>:<level>,<scope>:<level>
# The control plane has different scopes depending on component, but can configure default log level across all components
# If empty, default scope and level will be used as configured in code
logging:
@@ -83,11 +95,11 @@ global:
omitSidecarInjectorConfigMap: false
# Whether to restrict the applications namespace the controller manages;
# -- Whether to restrict the applications namespace the controller manages;
# If not set, controller watches all namespaces
oneNamespace: false
# Configure whether Operator manages webhook configurations. The current behavior
# -- Configure whether Operator manages webhook configurations. The current behavior
# of Istiod is to manage its own webhook configurations.
# When this option is set as true, Istio Operator, instead of webhooks, manages the
# webhook configurations. When this option is set as false, webhooks manage their
@@ -106,7 +118,7 @@ global:
#- global
#- "{{ valueOrDefault .DeploymentMeta.Namespace \"default\" }}.global"
# Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and
# -- Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and
# system-node-critical, it is better to configure this in order to make sure your Istio pods
# will not be killed because of low priority class.
# Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass
@@ -116,18 +128,18 @@ global:
proxy:
image: proxyv2
# This controls the 'policy' in the sidecar injector.
# -- This controls the 'policy' in the sidecar injector.
autoInject: enabled
# CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value
# -- CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value
# cluster domain. Default value is "cluster.local".
clusterDomain: "cluster.local"
# Per Component log level for proxy, applies to gateways and sidecars. If a component level is
# -- Per Component log level for proxy, applies to gateways and sidecars. If a component level is
# not set, then the global "logLevel" will be used.
componentLogLevel: "misc:error"
# If set, newly injected sidecars will have core dumps enabled.
# -- If set, newly injected sidecars will have core dumps enabled.
enableCoreDump: false
# istio ingress capture allowlist
@@ -136,7 +148,7 @@ global:
excludeInboundPorts: ""
includeInboundPorts: "*"
# istio egress capture allowlist
# -- istio egress capture allowlist
# https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly
# example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16"
# would only capture egress traffic on those two IP Ranges, all other outbound traffic would
@@ -146,29 +158,29 @@ global:
includeOutboundPorts: ""
excludeOutboundPorts: ""
# Log level for proxy, applies to gateways and sidecars.
# -- Log level for proxy, applies to gateways and sidecars.
# Expected values are: trace|debug|info|warning|error|critical|off
logLevel: warning
#If set to true, istio-proxy container will have privileged securityContext
# -- If set to true, istio-proxy container will have privileged securityContext
privileged: false
# The number of successive failed probes before indicating readiness failure.
# -- The number of successive failed probes before indicating readiness failure.
readinessFailureThreshold: 30
# The number of successive successed probes before indicating readiness success.
# -- The number of successive successed probes before indicating readiness success.
readinessSuccessThreshold: 30
# The initial delay for readiness probes in seconds.
# -- The initial delay for readiness probes in seconds.
readinessInitialDelaySeconds: 1
# The period between readiness probes.
# -- The period between readiness probes.
readinessPeriodSeconds: 2
# The readiness timeout seconds
# -- The readiness timeout seconds
readinessTimeoutSeconds: 3
# Resources for the sidecar.
# -- Resources for the sidecar.
resources:
requests:
cpu: 100m
@@ -177,18 +189,18 @@ global:
cpu: 2000m
memory: 1024Mi
# Default port for Pilot agent health checks. A value of 0 will disable health checking.
# -- Default port for Pilot agent health checks. A value of 0 will disable health checking.
statusPort: 15020
# Specify which tracer to use. One of: lightstep, datadog, stackdriver.
# -- Specify which tracer to use. One of: lightstep, datadog, stackdriver.
# If using stackdriver tracer outside GCP, set env GOOGLE_APPLICATION_CREDENTIALS to the GCP credential file.
tracer: ""
# Controls if sidecar is injected at the front of the container list and blocks the start of the other containers until the proxy is ready
# -- Controls if sidecar is injected at the front of the container list and blocks the start of the other containers until the proxy is ready
holdApplicationUntilProxyStarts: false
proxy_init:
# Base name for the proxy_init container, used to configure iptables.
# -- Base name for the proxy_init container, used to configure iptables.
image: proxyv2
resources:
limits:
@@ -198,7 +210,7 @@ global:
cpu: 10m
memory: 10Mi
# configure remote pilot and istiod service and endpoint
# -- configure remote pilot and istiod service and endpoint
remotePilotAddress: ""
##############################################################################################
@@ -206,20 +218,20 @@ global:
# make sure they are consistent across your Istio helm charts #
##############################################################################################
# The customized CA address to retrieve certificates for the pods in the cluster.
# -- The customized CA address to retrieve certificates for the pods in the cluster.
# CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint.
# If not set explicitly, default to the Istio discovery address.
caAddress: ""
# Configure a remote cluster data plane controlled by an external istiod.
# -- Configure a remote cluster data plane controlled by an external istiod.
# When set to true, istiod is not deployed locally and only a subset of the other
# discovery charts are enabled.
externalIstiod: false
# Configure a remote cluster as the config cluster for an external istiod.
# -- Configure a remote cluster as the config cluster for an external istiod.
configCluster: false
# Configure the policy for validating JWT.
# -- Configure the policy for validating JWT.
# Currently, two options are supported: "third-party-jwt" and "first-party-jwt".
jwtPolicy: "third-party-jwt"
@@ -241,7 +253,7 @@ global:
# of migration TBD, and it may be a disruptive operation to change the Mesh
# ID post-install.
#
# If the mesh admin does not specify a value, Istio will use the value of the
# -- If the mesh admin does not specify a value, Istio will use the value of the
# mesh's Trust Domain. The best practice is to select a proper Trust Domain
# value.
meshID: ""
@@ -275,68 +287,69 @@ global:
#
meshNetworks: {}
# Use the user-specified, secret volume mounted key and certs for Pilot and workloads.
# -- Use the user-specified, secret volume mounted key and certs for Pilot and workloads.
mountMtlsCerts: false
multiCluster:
# Set to true to connect two kubernetes clusters via their respective
# -- Set to true to connect two kubernetes clusters via their respective
# ingressgateway services when pods in each cluster cannot directly
# talk to one another. All clusters should be using Istio mTLS and must
# have a shared root CA for this model to work.
enabled: true
# Should be set to the name of the cluster this installation will run in. This is required for sidecar injection
# -- Should be set to the name of the cluster this installation will run in. This is required for sidecar injection
# to properly label proxies
clusterName: ""
# Network defines the network this cluster belong to. This name
# -- Network defines the network this cluster belong to. This name
# corresponds to the networks in the map of mesh networks.
network: ""
# Configure the certificate provider for control plane communication.
# -- Configure the certificate provider for control plane communication.
# Currently, two providers are supported: "kubernetes" and "istiod".
# As some platforms may not have kubernetes signing APIs,
# Istiod is the default
pilotCertProvider: istiod
sds:
# The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3.
# -- The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3.
# When a CSR is sent from Istio Agent to the CA (e.g. Istiod), this aud is to make sure the
# JWT is intended for the CA.
token:
aud: istio-ca
sts:
# The service port used by Security Token Service (STS) server to handle token exchange requests.
# -- The service port used by Security Token Service (STS) server to handle token exchange requests.
# Setting this port to a non-zero value enables STS server.
servicePort: 0
# Configuration for each of the supported tracers
# -- Configuration for each of the supported tracers
tracer:
# Configuration for envoy to send trace data to LightStep.
# -- Configuration for envoy to send trace data to LightStep.
# Disabled by default.
# address: the <host>:<port> of the satellite pool
# accessToken: required for sending data to the pool
#
datadog:
# Host:Port for submitting traces to the Datadog agent.
# -- Host:Port for submitting traces to the Datadog agent.
address: "$(HOST_IP):8126"
lightstep:
address: "" # example: lightstep-satellite:443
accessToken: "" # example: abcdefg1234567
# -- example: lightstep-satellite:443
address: ""
# -- example: abcdefg1234567
accessToken: ""
stackdriver:
# enables trace output to stdout.
# -- enables trace output to stdout.
debug: false
# The global default max number of message events per span.
# -- The global default max number of message events per span.
maxNumberOfMessageEvents: 200
# The global default max number of annotation events per span.
# -- The global default max number of annotation events per span.
maxNumberOfAnnotations: 200
# The global default max number of attributes per span.
# -- The global default max number of attributes per span.
maxNumberOfAttributes: 200
# Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source.
# -- Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source.
useMCP: false
# Observability (o11y) configurations
# -- Observability (o11y) configurations
o11y:
enabled: false
promtail:
@@ -350,7 +363,7 @@ global:
memory: 2Gi
securityContext: {}
# The name of the CA for workload certificates.
# -- The name of the CA for workload certificates.
# For example, when caName=GkeWorkloadCertificate, GKE workload certificates
# will be used as the certificates for workloads.
# The default value is "" and when caName="", the CA will be configured by other
@@ -359,7 +372,7 @@ global:
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
clusterName: ""
# meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior
# -- meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior
# See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options
meshConfig:
enablePrometheusMerge: true
@@ -370,14 +383,13 @@ meshConfig:
# and gradual adoption by setting capture only on specific workloads. It also allows
# VMs to use other DNS options, like dnsmasq or unbound.
# The namespace to treat as the administrative root namespace for Istio configuration.
# -- The namespace to treat as the administrative root namespace for Istio configuration.
# When processing a leaf namespace Istio will search for declarations in that namespace first
# and if none are found it will search in the root namespace. Any matching declaration found in the root namespace
# is processed as if it were declared in the leaf namespace.
rootNamespace:
# The trust domain corresponds to the trust root of a system
# -- The trust domain corresponds to the trust root of a system
# Refer to https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain
trustDomain: "cluster.local"
@@ -391,56 +403,57 @@ meshConfig:
gateway:
name: "higress-gateway"
# -- Number of Higress Gateway pods
replicas: 2
image: gateway
# -- Use a `DaemonSet` or `Deployment`
kind: Deployment
# The number of successive failed probes before indicating readiness failure.
# -- The number of successive failed probes before indicating readiness failure.
readinessFailureThreshold: 30
# The number of successive successed probes before indicating readiness success.
# -- The number of successive successed probes before indicating readiness success.
readinessSuccessThreshold: 1
# The initial delay for readiness probes in seconds.
# -- The initial delay for readiness probes in seconds.
readinessInitialDelaySeconds: 1
# The period between readiness probes.
# -- The period between readiness probes.
readinessPeriodSeconds: 2
# The readiness timeout seconds
# -- The readiness timeout seconds
readinessTimeoutSeconds: 3
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
tag: ""
# revision declares which revision this gateway is a part of
# -- revision declares which revision this gateway is a part of
revision: ""
rbac:
# If enabled, roles will be created to enable accessing certificates from Gateways. This is not needed
# -- If enabled, roles will be created to enable accessing certificates from Gateways. This is not needed
# when using http://gateway-api.org/.
enabled: true
serviceAccount:
# If set, a service account will be created. Otherwise, the default is used
# -- If set, a service account will be created. Otherwise, the default is used
create: true
# Annotations to add to the service account
# -- Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# -- The name of the service account to use.
# If not set, the release name is used
name: ""
# Pod environment variables
# -- Pod environment variables
env: {}
httpPort: 80
httpsPort: 443
hostNetwork: false
# Labels to apply to all resources
# -- Labels to apply to all resources
labels: {}
# Annotations to apply to all resources
# -- Annotations to apply to all resources
annotations: {}
podAnnotations:
@@ -449,14 +462,15 @@ gateway:
prometheus.io/path: "/stats/prometheus"
sidecar.istio.io/inject: "false"
# Define the security context for the pod.
# -- Define the security context for the pod.
# If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443.
# On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl.
securityContext: ~
containerSecurityContext: ~
unprivilegedPortSupported: ~
service:
# Type of service. Set to "None" to disable the service entirely
# -- Type of service. Set to "None" to disable the service entirely
type: LoadBalancer
ports:
- name: http2
@@ -496,28 +510,29 @@ gateway:
affinity: {}
# If specified, the gateway will act as a network gateway for the given network.
# -- If specified, the gateway will act as a network gateway for the given network.
networkGateway: ""
metrics:
# If true, create PodMonitor or VMPodScrape for gateway
# -- If true, create PodMonitor or VMPodScrape for gateway
enabled: false
# provider group name for CustomResourceDefinition, can be monitoring.coreos.com or operator.victoriametrics.com
# -- provider group name for CustomResourceDefinition, can be monitoring.coreos.com or operator.victoriametrics.com
provider: monitoring.coreos.com
interval: ""
scrapeTimeout: ""
honorLabels: false
# for monitoring.coreos.com/v1.PodMonitor
# -- for monitoring.coreos.com/v1.PodMonitor
metricRelabelings: []
relabelings: []
# for operator.victoriametrics.com/v1beta1.VMPodScrape
# -- for operator.victoriametrics.com/v1beta1.VMPodScrape
metricRelabelConfigs: []
relabelConfigs: []
# some more raw podMetricsEndpoints spec
# -- some more raw podMetricsEndpoints spec
rawSpec: {}
controller:
name: "higress-controller"
# -- Number of Higress Controller pods
replicas: 1
image: higress
@@ -541,12 +556,12 @@ controller:
create: true
serviceAccount:
# Specifies whether a service account should be created
# -- Specifies whether a service account should be created
create: true
# Annotations to add to the service account
# -- Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
# -- The name of the service account to use.
# -- If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
@@ -602,7 +617,7 @@ controller:
enabled: true
email: ""
## Discovery Settings
## -- Discovery Settings
pilot:
autoscaleEnabled: false
autoscaleMin: 1
@@ -614,11 +629,11 @@ pilot:
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
tag: ""
# Can be a full hub/image:tag
# -- Can be a full hub/image:tag
image: pilot
traceSampling: 1.0
# Resources for a small pilot install
# -- Resources for a small pilot install
resources:
requests:
cpu: 500m
@@ -633,21 +648,21 @@ pilot:
cpu:
targetAverageUtilization: 80
# if protocol sniffing is enabled for outbound
# -- if protocol sniffing is enabled for outbound
enableProtocolSniffingForOutbound: true
# if protocol sniffing is enabled for inbound
# -- if protocol sniffing is enabled for inbound
enableProtocolSniffingForInbound: true
nodeSelector: {}
podAnnotations: {}
serviceAnnotations: {}
# You can use jwksResolverExtraRootCA to provide a root certificate
# -- You can use jwksResolverExtraRootCA to provide a root certificate
# in PEM format. This will then be trusted by pilot when resolving
# JWKS URIs.
jwksResolverExtraRootCA: ""
# This is used to set the source of configuration for
# -- This is used to set the source of configuration for
# the associated address in configSource, if nothing is specified
# the default MCP is assumed.
configSource:
@@ -655,21 +670,21 @@ pilot:
plugins: []
# The following is used to limit how long a sidecar can be connected
# -- The following is used to limit how long a sidecar can be connected
# to a pilot. It balances out load across pilot instances at the cost of
# increasing system churn.
keepaliveMaxServerConnectionAge: 30m
# Additional labels to apply to the deployment.
# -- Additional labels to apply to the deployment.
deploymentLabels: {}
## Mesh config settings
# Install the mesh config map, generated from values.yaml.
# -- Install the mesh config map, generated from values.yaml.
# If false, pilot wil use default values (by default) or user-supplied values.
configMap: true
# Additional labels to apply on the pod level for monitoring and logging configuration.
# -- Additional labels to apply on the pod level for monitoring and logging configuration.
podLabels: {}
# Tracing config settings
@@ -684,3 +699,19 @@ tracing:
# zipkin:
# service: ""
# port: 9411
# -- Downstream config settings
downstream:
idleTimeout: 180
maxRequestHeadersKb: 60
connectionBufferLimits: 32768
http2:
maxConcurrentStreams: 100
initialStreamWindowSize: 65535
initialConnectionWindowSize: 1048576
routeTimeout: 0
# -- Upstream config settings
upstream:
idleTimeout: 10
connectionBufferLimits: 10485760

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 2.0.1
version: 2.0.6-rc.3
- name: higress-console
repository: https://higress.io/helm-charts/
version: 1.4.4
digest: sha256:6e4d77c31c834a404a728ec5a8379dd5df27a7e9b998a08e6524dc6534b07c1d
generated: "2024-10-09T20:07:21.857942+08:00"
version: 2.0.1
digest: sha256:6821ee9079a795f3e1de2c5126c36d3285f44863938a88f021ee4fbce82c0f15
generated: "2025-01-15T20:46:00.498051+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.0.1
appVersion: 2.0.6-rc.3
description: Helm chart for deploying Higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -12,9 +12,9 @@ sources:
dependencies:
- name: higress-core
repository: "file://../core"
version: 2.0.1
version: 2.0.6-rc.3
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 1.4.4
version: 2.0.1
type: application
version: 2.0.1
version: 2.0.6-rc.3

View File

@@ -1,57 +1,278 @@
# Higress Helm Chart
Installs the cloud-native gateway [Higress](http://higress.io/)
## Get Repo Info
```console
helm repo add higress.io https://higress.io/helm-charts
helm repo update
```
_See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation._
## Installing the Chart
To install the chart with the release name `higress`:
```console
helm install higress -n higress-system higress.io/higress --create-namespace --render-subchart-notes
```
## Uninstalling the Chart
To uninstall/delete the higress deployment:
```console
helm delete higress -n higress-system
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
## Configuration
| **Parameter** | **Description** | **Default** |
|---|---|---|
| **Global Parameters** | | |
| global.local | Set to `true` if installing to a local K8s cluster (e.g.: Kind, Rancher Desktop, etc.) | false |
| global.ingressClass | [IngressClass](https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/#ingress-class) which is used to filter Ingress resources Higress Controller watches.<br />If there are multiple gateway instances deployed in the cluster, this parameter can be used to distinguish the scope of each gateway instance.<br />There are some special cases for special IngressClass values:<br />1. If set to "nginx", Higress Controller will watch Ingress resources with the `nginx` IngressClass or without any Ingress class.<br />2. If set to empty, Higress Controller will watch all Ingress resources in the K8s cluster. | higress |
| global.watchNamespace | If not empty, Higress Controller will only watch resources in the specified namespace. When isolating different business systems using K8s namespace, if each namespace requires a standalone gateway instance, this parameter can be used to confine the Ingress watching of Higress within the given namespace. | "" |
| global.disableAlpnH2 | Whether to disable HTTP/2 in ALPN | true |
| global.enableStatus | If `true`, Higress Controller will update the `status` field of Ingress resources.<br />When migrating from Nginx Ingress, in order to avoid `status` field of Ingress objects being overwritten, this parameter needs to be set to false, so Higress won't write the entry IP to the `status` field of the corresponding Ingress object. | true |
| global.enableIstioAPI | If `true`, Higress Controller will monitor istio resources as well | false |
| global.enableGatewayAPI | If `true`, Higress Controller will monitor Gateway API resources as well | false |
| global.istioNamespace | The namespace istio is installed to | istio-system |
| **Core Paramters** | | |
| higress-core.gateway.replicas | Number of Higress Gateway pods | 2 |
| higress-core.controller.replicas | Number of Higress Controller pods | 1 |
| **Console Paramters** | | |
| higress-console.replicaCount | Number of Higress Console pods | 1 |
| higress-console.service.type | K8s service type used by Higress Console | ClusterIP |
| higress-console.domain | Domain used to access Higress Console | console.higress.io |
| higress-console.tlsSecretName | Name of Secret resource used by TLS connections. | "" |
| higress-console.web.login.prompt | Prompt message to be displayed on the login page | "" |
| higress-console.admin.password.value | If not empty, the admin password will be configured to the specified value. | "" |
| higress-console.admin.password.length | The length of random admin password generated during installation. Only works when `higress-console.admin.password.value` is not set. | 8 |
| higress-console.o11y.enabled | If `true`, o11y suite (Grafana + Promethues) will be installed. | false |
| higress-console.pvc.rwxSupported | Set to `false` when installing to a standard K8s cluster and the target cluster doesn't support the ReadWriteMany access mode of PersistentVolumeClaim. | true |
## Higress for Kubernetes
Higress is a cloud-native api gateway based on Alibaba's internal gateway practices.
Powered by Istio and Envoy, Higress realizes the integration of the triple gateway architecture of traffic gateway, microservice gateway and security gateway, thereby greatly reducing the costs of deployment, operation and maintenance.
## Setup Repo Info
```console
helm repo add higress.io https://higress.io/helm-charts
helm repo update
```
## Install
To install the chart with the release name `higress`:
```console
helm install higress -n higress-system higress.io/higress --create-namespace --render-subchart-notes
```
## Uninstall
To uninstall/delete the higress deployment:
```console
helm delete higress -n higress-system
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
## Parameters
## Values
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| clusterName | string | `""` | |
| controller.affinity | object | `{}` | |
| controller.automaticHttps.email | string | `""` | |
| controller.automaticHttps.enabled | bool | `true` | |
| controller.autoscaling.enabled | bool | `false` | |
| controller.autoscaling.maxReplicas | int | `5` | |
| controller.autoscaling.minReplicas | int | `1` | |
| controller.autoscaling.targetCPUUtilizationPercentage | int | `80` | |
| controller.env | object | `{}` | |
| controller.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | |
| controller.image | string | `"higress"` | |
| controller.imagePullSecrets | list | `[]` | |
| controller.labels | object | `{}` | |
| controller.name | string | `"higress-controller"` | |
| controller.nodeSelector | object | `{}` | |
| controller.podAnnotations | object | `{}` | |
| controller.podSecurityContext | object | `{}` | |
| controller.ports[0].name | string | `"http"` | |
| controller.ports[0].port | int | `8888` | |
| controller.ports[0].protocol | string | `"TCP"` | |
| controller.ports[0].targetPort | int | `8888` | |
| controller.ports[1].name | string | `"http-solver"` | |
| controller.ports[1].port | int | `8889` | |
| controller.ports[1].protocol | string | `"TCP"` | |
| controller.ports[1].targetPort | int | `8889` | |
| controller.ports[2].name | string | `"grpc"` | |
| controller.ports[2].port | int | `15051` | |
| controller.ports[2].protocol | string | `"TCP"` | |
| controller.ports[2].targetPort | int | `15051` | |
| controller.probe.httpGet.path | string | `"/ready"` | |
| controller.probe.httpGet.port | int | `8888` | |
| controller.probe.initialDelaySeconds | int | `1` | |
| controller.probe.periodSeconds | int | `3` | |
| controller.probe.timeoutSeconds | int | `5` | |
| controller.rbac.create | bool | `true` | |
| controller.replicas | int | `1` | Number of Higress Controller pods |
| controller.resources.limits.cpu | string | `"1000m"` | |
| controller.resources.limits.memory | string | `"2048Mi"` | |
| controller.resources.requests.cpu | string | `"500m"` | |
| controller.resources.requests.memory | string | `"2048Mi"` | |
| controller.securityContext | object | `{}` | |
| controller.service.type | string | `"ClusterIP"` | |
| controller.serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
| controller.serviceAccount.create | bool | `true` | Specifies whether a service account should be created |
| controller.serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template |
| controller.tag | string | `""` | |
| controller.tolerations | list | `[]` | |
| downstream | object | `{"connectionBufferLimits":32768,"http2":{"initialConnectionWindowSize":1048576,"initialStreamWindowSize":65535,"maxConcurrentStreams":100},"idleTimeout":180,"maxRequestHeadersKb":60,"routeTimeout":0}` | Downstream config settings |
| gateway.affinity | object | `{}` | |
| gateway.annotations | object | `{}` | Annotations to apply to all resources |
| gateway.autoscaling.enabled | bool | `false` | |
| gateway.autoscaling.maxReplicas | int | `5` | |
| gateway.autoscaling.minReplicas | int | `1` | |
| gateway.autoscaling.targetCPUUtilizationPercentage | int | `80` | |
| gateway.containerSecurityContext | string | `nil` | |
| gateway.env | object | `{}` | Pod environment variables |
| gateway.hostNetwork | bool | `false` | |
| gateway.httpPort | int | `80` | |
| gateway.httpsPort | int | `443` | |
| gateway.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | |
| gateway.image | string | `"gateway"` | |
| gateway.kind | string | `"Deployment"` | Use a `DaemonSet` or `Deployment` |
| gateway.labels | object | `{}` | Labels to apply to all resources |
| gateway.metrics.enabled | bool | `false` | If true, create PodMonitor or VMPodScrape for gateway |
| gateway.metrics.honorLabels | bool | `false` | |
| gateway.metrics.interval | string | `""` | |
| gateway.metrics.metricRelabelConfigs | list | `[]` | for operator.victoriametrics.com/v1beta1.VMPodScrape |
| gateway.metrics.metricRelabelings | list | `[]` | for monitoring.coreos.com/v1.PodMonitor |
| gateway.metrics.provider | string | `"monitoring.coreos.com"` | provider group name for CustomResourceDefinition, can be monitoring.coreos.com or operator.victoriametrics.com |
| gateway.metrics.rawSpec | object | `{}` | some more raw podMetricsEndpoints spec |
| gateway.metrics.relabelConfigs | list | `[]` | |
| gateway.metrics.relabelings | list | `[]` | |
| gateway.metrics.scrapeTimeout | string | `""` | |
| gateway.name | string | `"higress-gateway"` | |
| gateway.networkGateway | string | `""` | If specified, the gateway will act as a network gateway for the given network. |
| gateway.nodeSelector | object | `{}` | |
| gateway.podAnnotations."prometheus.io/path" | string | `"/stats/prometheus"` | |
| gateway.podAnnotations."prometheus.io/port" | string | `"15020"` | |
| gateway.podAnnotations."prometheus.io/scrape" | string | `"true"` | |
| gateway.podAnnotations."sidecar.istio.io/inject" | string | `"false"` | |
| gateway.rbac.enabled | bool | `true` | If enabled, roles will be created to enable accessing certificates from Gateways. This is not needed when using http://gateway-api.org/. |
| gateway.readinessFailureThreshold | int | `30` | The number of successive failed probes before indicating readiness failure. |
| gateway.readinessInitialDelaySeconds | int | `1` | The initial delay for readiness probes in seconds. |
| gateway.readinessPeriodSeconds | int | `2` | The period between readiness probes. |
| gateway.readinessSuccessThreshold | int | `1` | The number of successive successed probes before indicating readiness success. |
| gateway.readinessTimeoutSeconds | int | `3` | The readiness timeout seconds |
| gateway.replicas | int | `2` | Number of Higress Gateway pods |
| gateway.resources.limits.cpu | string | `"2000m"` | |
| gateway.resources.limits.memory | string | `"2048Mi"` | |
| gateway.resources.requests.cpu | string | `"2000m"` | |
| gateway.resources.requests.memory | string | `"2048Mi"` | |
| gateway.revision | string | `""` | revision declares which revision this gateway is a part of |
| gateway.rollingMaxSurge | string | `"100%"` | |
| gateway.rollingMaxUnavailable | string | `"25%"` | |
| gateway.securityContext | string | `nil` | Define the security context for the pod. If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443. On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl. |
| gateway.service.annotations | object | `{}` | |
| gateway.service.externalTrafficPolicy | string | `""` | |
| gateway.service.loadBalancerClass | string | `""` | |
| gateway.service.loadBalancerIP | string | `""` | |
| gateway.service.loadBalancerSourceRanges | list | `[]` | |
| gateway.service.ports[0].name | string | `"http2"` | |
| gateway.service.ports[0].port | int | `80` | |
| gateway.service.ports[0].protocol | string | `"TCP"` | |
| gateway.service.ports[0].targetPort | int | `80` | |
| gateway.service.ports[1].name | string | `"https"` | |
| gateway.service.ports[1].port | int | `443` | |
| gateway.service.ports[1].protocol | string | `"TCP"` | |
| gateway.service.ports[1].targetPort | int | `443` | |
| gateway.service.type | string | `"LoadBalancer"` | Type of service. Set to "None" to disable the service entirely |
| gateway.serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
| gateway.serviceAccount.create | bool | `true` | If set, a service account will be created. Otherwise, the default is used |
| gateway.serviceAccount.name | string | `""` | The name of the service account to use. If not set, the release name is used |
| gateway.tag | string | `""` | |
| gateway.tolerations | list | `[]` | |
| gateway.unprivilegedPortSupported | string | `nil` | |
| global.autoscalingv2API | bool | `true` | whether to use autoscaling/v2 template for HPA settings for internal usage only, not to be configured by users. |
| global.caAddress | string | `""` | The customized CA address to retrieve certificates for the pods in the cluster. CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint. If not set explicitly, default to the Istio discovery address. |
| global.caName | string | `""` | The name of the CA for workload certificates. For example, when caName=GkeWorkloadCertificate, GKE workload certificates will be used as the certificates for workloads. The default value is "" and when caName="", the CA will be configured by other mechanisms (e.g., environmental variable CA_PROVIDER). |
| global.configCluster | bool | `false` | Configure a remote cluster as the config cluster for an external istiod. |
| global.defaultPodDisruptionBudget | object | `{"enabled":false}` | enable pod disruption budget for the control plane, which is used to ensure Istio control plane components are gradually upgraded or recovered. |
| global.defaultResources | object | `{"requests":{"cpu":"10m"}}` | A minimal set of requested resources to applied to all deployments so that Horizontal Pod Autoscaler will be able to function (if set). Each component can overwrite these default values by adding its own resources block in the relevant section below and setting the desired resources values. |
| global.defaultUpstreamConcurrencyThreshold | int | `10000` | |
| global.disableAlpnH2 | bool | `false` | Whether to disable HTTP/2 in ALPN |
| global.enableGatewayAPI | bool | `false` | If true, Higress Controller will monitor Gateway API resources as well |
| global.enableH3 | bool | `false` | |
| global.enableIPv6 | bool | `false` | |
| global.enableIstioAPI | bool | `true` | If true, Higress Controller will monitor istio resources as well |
| global.enableLDSCache | bool | `true` | |
| global.enableProxyProtocol | bool | `false` | |
| global.enablePushAllMCPClusters | bool | `true` | |
| global.enableSRDS | bool | `true` | |
| global.enableStatus | bool | `true` | If true, Higress Controller will update the status field of Ingress resources. When migrating from Nginx Ingress, in order to avoid status field of Ingress objects being overwritten, this parameter needs to be set to false, so Higress won't write the entry IP to the status field of the corresponding Ingress object. |
| global.externalIstiod | bool | `false` | Configure a remote cluster data plane controlled by an external istiod. When set to true, istiod is not deployed locally and only a subset of the other discovery charts are enabled. |
| global.hostRDSMergeSubset | bool | `false` | |
| global.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | Default hub for Istio images. Releases are published to docker hub under 'istio' project. Dev builds from prow are on gcr.io |
| global.imagePullPolicy | string | `""` | Specify image pull policy if default behavior isn't desired. Default behavior: latest images will be Always else IfNotPresent. |
| global.imagePullSecrets | list | `[]` | ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace to use for pulling any images in pods that reference this ServiceAccount. For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing) ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects. Must be set for any cluster configured with private docker registry. |
| global.ingressClass | string | `"higress"` | IngressClass filters which ingress resources the higress controller watches. The default ingress class is higress. There are some special cases for special ingress class. 1. When the ingress class is set as nginx, the higress controller will watch ingress resources with the nginx ingress class or without any ingress class. 2. When the ingress class is set empty, the higress controller will watch all ingress resources in the k8s cluster. |
| global.istioNamespace | string | `"istio-system"` | Used to locate istiod. |
| global.istiod | object | `{"enableAnalysis":false}` | Enabled by default in master for maximising testing. |
| global.jwtPolicy | string | `"third-party-jwt"` | Configure the policy for validating JWT. Currently, two options are supported: "third-party-jwt" and "first-party-jwt". |
| global.kind | bool | `false` | |
| global.liteMetrics | bool | `false` | |
| global.local | bool | `false` | When deploying to a local cluster (e.g.: kind cluster), set this to true. |
| global.logAsJson | bool | `false` | |
| global.logging | object | `{"level":"default:info"}` | Comma-separated minimum per-scope logging level of messages to output, in the form of <scope>:<level>,<scope>:<level> The control plane has different scopes depending on component, but can configure default log level across all components If empty, default scope and level will be used as configured in code |
| global.meshID | string | `""` | If the mesh admin does not specify a value, Istio will use the value of the mesh's Trust Domain. The best practice is to select a proper Trust Domain value. |
| global.meshNetworks | object | `{}` | |
| global.mountMtlsCerts | bool | `false` | Use the user-specified, secret volume mounted key and certs for Pilot and workloads. |
| global.multiCluster.clusterName | string | `""` | Should be set to the name of the cluster this installation will run in. This is required for sidecar injection to properly label proxies |
| global.multiCluster.enabled | bool | `true` | Set to true to connect two kubernetes clusters via their respective ingressgateway services when pods in each cluster cannot directly talk to one another. All clusters should be using Istio mTLS and must have a shared root CA for this model to work. |
| global.network | string | `""` | Network defines the network this cluster belong to. This name corresponds to the networks in the map of mesh networks. |
| global.o11y | object | `{"enabled":false,"promtail":{"image":{"repository":"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/promtail","tag":"2.9.4"},"port":3101,"resources":{"limits":{"cpu":"500m","memory":"2Gi"}},"securityContext":{}}}` | Observability (o11y) configurations |
| global.omitSidecarInjectorConfigMap | bool | `false` | |
| global.onDemandRDS | bool | `false` | |
| global.oneNamespace | bool | `false` | Whether to restrict the applications namespace the controller manages; If not set, controller watches all namespaces |
| global.onlyPushRouteCluster | bool | `true` | |
| global.operatorManageWebhooks | bool | `false` | Configure whether Operator manages webhook configurations. The current behavior of Istiod is to manage its own webhook configurations. When this option is set as true, Istio Operator, instead of webhooks, manages the webhook configurations. When this option is set as false, webhooks manage their own webhook configurations. |
| global.pilotCertProvider | string | `"istiod"` | Configure the certificate provider for control plane communication. Currently, two providers are supported: "kubernetes" and "istiod". As some platforms may not have kubernetes signing APIs, Istiod is the default |
| global.priorityClassName | string | `""` | Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and system-node-critical, it is better to configure this in order to make sure your Istio pods will not be killed because of low priority class. Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass for more detail. |
| global.proxy.autoInject | string | `"enabled"` | This controls the 'policy' in the sidecar injector. |
| global.proxy.clusterDomain | string | `"cluster.local"` | CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value cluster domain. Default value is "cluster.local". |
| global.proxy.componentLogLevel | string | `"misc:error"` | Per Component log level for proxy, applies to gateways and sidecars. If a component level is not set, then the global "logLevel" will be used. |
| global.proxy.enableCoreDump | bool | `false` | If set, newly injected sidecars will have core dumps enabled. |
| global.proxy.excludeIPRanges | string | `""` | |
| global.proxy.excludeInboundPorts | string | `""` | |
| global.proxy.excludeOutboundPorts | string | `""` | |
| global.proxy.holdApplicationUntilProxyStarts | bool | `false` | Controls if sidecar is injected at the front of the container list and blocks the start of the other containers until the proxy is ready |
| global.proxy.image | string | `"proxyv2"` | |
| global.proxy.includeIPRanges | string | `"*"` | istio egress capture allowlist https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16" would only capture egress traffic on those two IP Ranges, all other outbound traffic would be allowed by the sidecar |
| global.proxy.includeInboundPorts | string | `"*"` | |
| global.proxy.includeOutboundPorts | string | `""` | |
| global.proxy.logLevel | string | `"warning"` | Log level for proxy, applies to gateways and sidecars. Expected values are: trace|debug|info|warning|error|critical|off |
| global.proxy.privileged | bool | `false` | If set to true, istio-proxy container will have privileged securityContext |
| global.proxy.readinessFailureThreshold | int | `30` | The number of successive failed probes before indicating readiness failure. |
| global.proxy.readinessInitialDelaySeconds | int | `1` | The initial delay for readiness probes in seconds. |
| global.proxy.readinessPeriodSeconds | int | `2` | The period between readiness probes. |
| global.proxy.readinessSuccessThreshold | int | `30` | The number of successive successed probes before indicating readiness success. |
| global.proxy.readinessTimeoutSeconds | int | `3` | The readiness timeout seconds |
| global.proxy.resources | object | `{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | Resources for the sidecar. |
| global.proxy.statusPort | int | `15020` | Default port for Pilot agent health checks. A value of 0 will disable health checking. |
| global.proxy.tracer | string | `""` | Specify which tracer to use. One of: lightstep, datadog, stackdriver. If using stackdriver tracer outside GCP, set env GOOGLE_APPLICATION_CREDENTIALS to the GCP credential file. |
| global.proxy_init.image | string | `"proxyv2"` | Base name for the proxy_init container, used to configure iptables. |
| global.proxy_init.resources.limits.cpu | string | `"2000m"` | |
| global.proxy_init.resources.limits.memory | string | `"1024Mi"` | |
| global.proxy_init.resources.requests.cpu | string | `"10m"` | |
| global.proxy_init.resources.requests.memory | string | `"10Mi"` | |
| global.remotePilotAddress | string | `""` | configure remote pilot and istiod service and endpoint |
| global.sds.token | object | `{"aud":"istio-ca"}` | The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3. When a CSR is sent from Istio Agent to the CA (e.g. Istiod), this aud is to make sure the JWT is intended for the CA. |
| global.sts.servicePort | int | `0` | The service port used by Security Token Service (STS) server to handle token exchange requests. Setting this port to a non-zero value enables STS server. |
| global.tracer | object | `{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":""},"stackdriver":{"debug":false,"maxNumberOfAnnotations":200,"maxNumberOfAttributes":200,"maxNumberOfMessageEvents":200}}` | Configuration for each of the supported tracers |
| global.tracer.datadog | object | `{"address":"$(HOST_IP):8126"}` | Configuration for envoy to send trace data to LightStep. Disabled by default. address: the <host>:<port> of the satellite pool accessToken: required for sending data to the pool |
| global.tracer.datadog.address | string | `"$(HOST_IP):8126"` | Host:Port for submitting traces to the Datadog agent. |
| global.tracer.lightstep.accessToken | string | `""` | example: abcdefg1234567 |
| global.tracer.lightstep.address | string | `""` | example: lightstep-satellite:443 |
| global.tracer.stackdriver.debug | bool | `false` | enables trace output to stdout. |
| global.tracer.stackdriver.maxNumberOfAnnotations | int | `200` | The global default max number of annotation events per span. |
| global.tracer.stackdriver.maxNumberOfAttributes | int | `200` | The global default max number of attributes per span. |
| global.tracer.stackdriver.maxNumberOfMessageEvents | int | `200` | The global default max number of message events per span. |
| global.useMCP | bool | `false` | Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source. |
| global.watchNamespace | string | `""` | If not empty, Higress Controller will only watch resources in the specified namespace. When isolating different business systems using K8s namespace, if each namespace requires a standalone gateway instance, this parameter can be used to confine the Ingress watching of Higress within the given namespace. |
| global.xdsMaxRecvMsgSize | string | `"104857600"` | |
| hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | |
| meshConfig | object | `{"enablePrometheusMerge":true,"rootNamespace":null,"trustDomain":"cluster.local"}` | meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options |
| meshConfig.rootNamespace | string | `nil` | The namespace to treat as the administrative root namespace for Istio configuration. When processing a leaf namespace Istio will search for declarations in that namespace first and if none are found it will search in the root namespace. Any matching declaration found in the root namespace is processed as if it were declared in the leaf namespace. |
| meshConfig.trustDomain | string | `"cluster.local"` | The trust domain corresponds to the trust root of a system Refer to https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain |
| pilot.autoscaleEnabled | bool | `false` | |
| pilot.autoscaleMax | int | `5` | |
| pilot.autoscaleMin | int | `1` | |
| pilot.configMap | bool | `true` | Install the mesh config map, generated from values.yaml. If false, pilot wil use default values (by default) or user-supplied values. |
| pilot.configSource | object | `{"subscribedResources":[]}` | This is used to set the source of configuration for the associated address in configSource, if nothing is specified the default MCP is assumed. |
| pilot.cpu.targetAverageUtilization | int | `80` | |
| pilot.deploymentLabels | object | `{}` | Additional labels to apply to the deployment. |
| pilot.enableProtocolSniffingForInbound | bool | `true` | if protocol sniffing is enabled for inbound |
| pilot.enableProtocolSniffingForOutbound | bool | `true` | if protocol sniffing is enabled for outbound |
| pilot.env.PILOT_ENABLE_CROSS_CLUSTER_WORKLOAD_ENTRY | string | `"false"` | |
| pilot.env.PILOT_ENABLE_METADATA_EXCHANGE | string | `"false"` | |
| pilot.env.PILOT_SCOPE_GATEWAY_TO_NAMESPACE | string | `"false"` | |
| pilot.env.VALIDATION_ENABLED | string | `"false"` | |
| pilot.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | |
| pilot.image | string | `"pilot"` | Can be a full hub/image:tag |
| pilot.jwksResolverExtraRootCA | string | `""` | You can use jwksResolverExtraRootCA to provide a root certificate in PEM format. This will then be trusted by pilot when resolving JWKS URIs. |
| pilot.keepaliveMaxServerConnectionAge | string | `"30m"` | The following is used to limit how long a sidecar can be connected to a pilot. It balances out load across pilot instances at the cost of increasing system churn. |
| pilot.nodeSelector | object | `{}` | |
| pilot.plugins | list | `[]` | |
| pilot.podAnnotations | object | `{}` | |
| pilot.podLabels | object | `{}` | Additional labels to apply on the pod level for monitoring and logging configuration. |
| pilot.replicaCount | int | `1` | |
| pilot.resources | object | `{"requests":{"cpu":"500m","memory":"2048Mi"}}` | Resources for a small pilot install |
| pilot.rollingMaxSurge | string | `"100%"` | |
| pilot.rollingMaxUnavailable | string | `"25%"` | |
| pilot.serviceAnnotations | object | `{}` | |
| pilot.tag | string | `""` | |
| pilot.traceSampling | float | `1` | |
| revision | string | `""` | |
| tracing.enable | bool | `false` | |
| tracing.sampling | int | `100` | |
| tracing.skywalking.port | int | `11800` | |
| tracing.skywalking.service | string | `""` | |
| tracing.timeout | int | `500` | |
| upstream | object | `{"connectionBufferLimits":10485760,"idleTimeout":10}` | Upstream config settings |

View File

@@ -0,0 +1,34 @@
## Higress for Kubernetes
Higress is a cloud-native api gateway based on Alibaba's internal gateway practices.
Powered by Istio and Envoy, Higress realizes the integration of the triple gateway architecture of traffic gateway, microservice gateway and security gateway, thereby greatly reducing the costs of deployment, operation and maintenance.
## Setup Repo Info
```console
helm repo add higress.io https://higress.io/helm-charts
helm repo update
```
## Install
To install the chart with the release name `higress`:
```console
helm install higress -n higress-system higress.io/higress --create-namespace --render-subchart-notes
```
## Uninstall
To uninstall/delete the higress deployment:
```console
helm delete higress -n higress-system
```
The command removes all the Kubernetes components associated with the chart and deletes the release.
## Parameters
{{ template "chart.valuesSection" . }}

View File

@@ -41,11 +41,11 @@ import (
"istio.io/istio/pkg/config/schema/kind"
"istio.io/istio/pkg/keepalive"
istiokube "istio.io/istio/pkg/kube"
"istio.io/istio/pkg/log"
"istio.io/istio/pkg/security"
"istio.io/istio/security/pkg/server/ca/authenticate"
"istio.io/istio/security/pkg/server/ca/authenticate/kubeauth"
"istio.io/pkg/ledger"
"istio.io/pkg/log"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"

View File

@@ -173,7 +173,7 @@ func (s *CertMgr) Reconcile(ctx context.Context, oldConfig *Config, newConfig *C
s.cache.Start()
// sync domains
s.configMgr.SetConfig(newConfig)
CertLog.Infof("certMgr start to manageSync domains:+v%", newDomains)
CertLog.Infof("certMgr start to manageSync domains: %+v", newDomains)
s.manageSync(context.Background(), newDomains)
CertLog.Infof("certMgr manageSync domains done")
} else {

View File

@@ -14,6 +14,6 @@
package cert
import "istio.io/pkg/log"
import "istio.io/istio/pkg/log"
var CertLog = log.RegisterScope("cert", "Higress Cert process.", 0)
var CertLog = log.RegisterScope("cert", "Higress Cert process.")

View File

@@ -25,7 +25,7 @@ import (
"istio.io/istio/pkg/config/constants"
"istio.io/istio/pkg/env"
"istio.io/istio/pkg/keepalive"
"istio.io/pkg/log"
"istio.io/istio/pkg/log"
)
var (

View File

@@ -19,6 +19,7 @@ import "istio.io/pkg/env"
var (
PodNamespace = env.RegisterStringVar("POD_NAMESPACE", "higress-system", "").Get()
PodName = env.RegisterStringVar("POD_NAME", "", "").Get()
GatewayName = env.RegisterStringVar("GATEWAY_NAME", "higress-gateway", "").Get()
// Revision is the value of the Istio control plane revision, e.g. "canary",
// and is the value used by the "istio.io/rev" label.
Revision = env.Register("REVISION", "", "").Get()

View File

@@ -34,6 +34,7 @@ import (
extensions "istio.io/api/extensions/v1alpha1"
networking "istio.io/api/networking/v1alpha3"
istiotype "istio.io/api/type/v1beta1"
"istio.io/istio/pilot/pkg/features"
istiomodel "istio.io/istio/pilot/pkg/model"
"istio.io/istio/pilot/pkg/util/protoconv"
"istio.io/istio/pkg/cluster"
@@ -235,8 +236,9 @@ func (m *IngressConfig) AddLocalCluster(options common.Options) {
ingressController = ingressv1.NewController(m.localKubeClient, m.localKubeClient, options, secretController)
}
m.remoteIngressControllers[options.ClusterId] = ingressController
m.remoteGatewayControllers[options.ClusterId] = gateway.NewController(m.localKubeClient, options)
if features.EnableGatewayAPI {
m.remoteGatewayControllers[options.ClusterId] = gateway.NewController(m.localKubeClient, options)
}
}
func (m *IngressConfig) List(typ config.GroupVersionKind, namespace string) []config.Config {
@@ -301,21 +303,21 @@ func (m *IngressConfig) listFromIngressControllers(typ config.GroupVersionKind,
common.SortIngressByCreationTime(configs)
wrapperConfigs := m.createWrapperConfigs(configs)
IngressLog.Infof("resource type %s, configs number %d", typ, len(wrapperConfigs))
var result []config.Config
switch typ {
case gvk.Gateway:
return m.convertGateways(wrapperConfigs)
result = m.convertGateways(wrapperConfigs)
case gvk.VirtualService:
return m.convertVirtualService(wrapperConfigs)
result = m.convertVirtualService(wrapperConfigs)
case gvk.DestinationRule:
return m.convertDestinationRule(wrapperConfigs)
result = m.convertDestinationRule(wrapperConfigs)
case gvk.ServiceEntry:
return m.convertServiceEntry(wrapperConfigs)
result = m.convertServiceEntry(wrapperConfigs)
case gvk.WasmPlugin:
return m.convertWasmPlugin(wrapperConfigs)
result = m.convertWasmPlugin(wrapperConfigs)
}
return nil
IngressLog.Infof("resource type %s, ingress number %d, convert configs number %d", typ, len(configs), len(result))
return result
}
func (m *IngressConfig) listFromGatewayControllers(typ config.GroupVersionKind, namespace string) []config.Config {
@@ -710,7 +712,6 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [
if m.RegistryReconciler != nil {
drws := m.RegistryReconciler.GetAllDestinationRuleWrapper()
IngressLog.Infof("Found mcp destinationRules: %v", drws)
for _, destinationRuleWrapper := range drws {
serviceName := destinationRuleWrapper.ServiceKey.ServiceFQDN
dr, exist := destinationRules[serviceName]
@@ -719,9 +720,9 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [
} else if dr.DestinationRule.TrafficPolicy != nil {
portTrafficPolicy := destinationRuleWrapper.DestinationRule.TrafficPolicy.PortLevelSettings[0]
portUpdated := false
for _, portTrafficPolicy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings {
if portTrafficPolicy.Port.Number == portTrafficPolicy.Port.Number {
portTrafficPolicy.Tls = portTrafficPolicy.Tls
for _, policy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings {
if policy.Port.Number == portTrafficPolicy.Port.Number {
policy.Tls = portTrafficPolicy.Tls
portUpdated = true
break
}
@@ -880,7 +881,7 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
if result.PluginConfig != nil {
return result, nil
}
if !obj.DefaultConfigDisable {
if !isBoolValueTrue(obj.DefaultConfigDisable) {
result.PluginConfig = obj.DefaultConfig
}
hasValidRule := false
@@ -892,7 +893,7 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
}
var ruleValues []*_struct.Value
for _, rule := range obj.MatchRules {
if rule.ConfigDisable {
if isBoolValueTrue(rule.ConfigDisable) {
continue
}
if rule.Config == nil {
@@ -904,6 +905,7 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
StructValue: rule.Config,
}
validRule := false
var matchItems []*_struct.Value
// match ingress
for _, ing := range rule.Ingress {
@@ -914,6 +916,7 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
})
}
if len(matchItems) > 0 {
validRule = true
v.StructValue.Fields["_match_route_"] = &_struct.Value{
Kind: &_struct.Value_ListValue{
ListValue: &_struct.ListValue{
@@ -921,12 +924,9 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
},
},
}
ruleValues = append(ruleValues, &_struct.Value{
Kind: v,
})
continue
}
// match service
matchItems = nil
for _, service := range rule.Service {
matchItems = append(matchItems, &_struct.Value{
Kind: &_struct.Value_StringValue{
@@ -935,6 +935,7 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
})
}
if len(matchItems) > 0 {
validRule = true
v.StructValue.Fields["_match_service_"] = &_struct.Value{
Kind: &_struct.Value_ListValue{
ListValue: &_struct.ListValue{
@@ -942,12 +943,9 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
},
},
}
ruleValues = append(ruleValues, &_struct.Value{
Kind: v,
})
continue
}
// match domain
matchItems = nil
for _, domain := range rule.Domain {
matchItems = append(matchItems, &_struct.Value{
Kind: &_struct.Value_StringValue{
@@ -955,19 +953,23 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
},
})
}
if len(matchItems) == 0 {
if len(matchItems) > 0 {
validRule = true
v.StructValue.Fields["_match_domain_"] = &_struct.Value{
Kind: &_struct.Value_ListValue{
ListValue: &_struct.ListValue{
Values: matchItems,
},
},
}
}
if validRule {
ruleValues = append(ruleValues, &_struct.Value{
Kind: v,
})
} else {
return nil, fmt.Errorf("invalid match rule has no match condition, rule:%v", rule)
}
v.StructValue.Fields["_match_domain_"] = &_struct.Value{
Kind: &_struct.Value_ListValue{
ListValue: &_struct.ListValue{
Values: matchItems,
},
},
}
ruleValues = append(ruleValues, &_struct.Value{
Kind: v,
})
}
if len(ruleValues) > 0 {
hasValidRule = true
@@ -980,13 +982,17 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
}
}
}
if !hasValidRule && obj.DefaultConfigDisable {
if !hasValidRule && isBoolValueTrue(obj.DefaultConfigDisable) {
return nil, nil
}
return result, nil
}
func isBoolValueTrue(b *wrappers.BoolValue) bool {
return b != nil && b.Value
}
func (m *IngressConfig) AddOrUpdateWasmPlugin(clusterNamespacedName util.ClusterNamespacedName) {
if clusterNamespacedName.Namespace != m.namespace {
return

View File

@@ -493,7 +493,7 @@ func (m *KIngressConfig) HasSynced() bool {
defer m.mutex.RUnlock()
for _, remoteIngressController := range m.remoteIngressControllers {
IngressLog.Info("In Kingress Synced.", remoteIngressController)
IngressLog.Info("In Kingress Synced.")
if !remoteIngressController.HasSynced() {
return false
}

View File

@@ -15,6 +15,7 @@
package annotations
import (
"fmt"
"strings"
networking "istio.io/api/networking/v1alpha3"
@@ -27,9 +28,11 @@ import (
)
const (
authTLSSecret = "auth-tls-secret"
sslCipher = "ssl-cipher"
gatewaySdsCaSuffix = "-cacert"
authTLSSecret = "auth-tls-secret"
sslCipher = "ssl-cipher"
gatewaySdsCaSuffix = "-cacert"
annotationMinTLSVersion = "tls-min-protocol-version"
annotationMaxTLSVersion = "tls-max-protocol-version"
)
var (
@@ -41,6 +44,8 @@ type DownstreamTLSConfig struct {
CipherSuites []string
Mode networking.ServerTLSSettings_TLSmode
CASecretName types.NamespacedName
MinVersion string
MaxVersion string
}
type downstreamTLS struct{}
@@ -82,6 +87,14 @@ func (d downstreamTLS) Parse(annotations Annotations, config *Ingress, _ *Global
downstreamTLSConfig.CipherSuites = validCipherSuite
}
if minVersion, err := annotations.ParseStringASAP(annotationMinTLSVersion); err == nil {
downstreamTLSConfig.MinVersion = minVersion
}
if maxVersion, err := annotations.ParseStringASAP(annotationMaxTLSVersion); err == nil {
downstreamTLSConfig.MaxVersion = maxVersion
}
return nil
}
@@ -107,11 +120,44 @@ func (d downstreamTLS) ApplyGateway(gateway *networking.Gateway, config *Ingress
if len(downstreamTLSConfig.CipherSuites) != 0 {
server.Tls.CipherSuites = downstreamTLSConfig.CipherSuites
}
if downstreamTLSConfig.MinVersion != "" {
if version, err := convertTLSVersion(downstreamTLSConfig.MinVersion); err != nil {
IngressLog.Errorf("Invalid minimum TLS version: %v", err)
} else {
server.Tls.MinProtocolVersion = version
}
}
if downstreamTLSConfig.MaxVersion != "" {
if version, err := convertTLSVersion(downstreamTLSConfig.MaxVersion); err != nil {
IngressLog.Errorf("Invalid maximum TLS version: %v", err)
} else {
server.Tls.MaxProtocolVersion = version
}
}
}
}
}
func needDownstreamTLS(annotations Annotations) bool {
return annotations.HasASAP(sslCipher) ||
annotations.HasASAP(authTLSSecret)
annotations.HasASAP(authTLSSecret) ||
annotations.HasASAP(annotationMinTLSVersion) ||
annotations.HasASAP(annotationMaxTLSVersion)
}
func convertTLSVersion(version string) (networking.ServerTLSSettings_TLSProtocol, error) {
switch version {
case "TLSv1.0":
return networking.ServerTLSSettings_TLSV1_0, nil
case "TLSv1.1":
return networking.ServerTLSSettings_TLSV1_1, nil
case "TLSv1.2":
return networking.ServerTLSSettings_TLSV1_2, nil
case "TLSv1.3":
return networking.ServerTLSSettings_TLSV1_3, nil
}
return networking.ServerTLSSettings_TLS_AUTO, fmt.Errorf("invalid TLS version: %s. Valid values are: TLSv1.0, TLSv1.1, TLSv1.2, TLSv1.3", version)
}

View File

@@ -26,11 +26,15 @@ var parser = downstreamTLS{}
func TestParse(t *testing.T) {
testCases := []struct {
name string
input map[string]string
expect *DownstreamTLSConfig
}{
{},
{
name: "empty config",
},
{
name: "ssl cipher only",
input: map[string]string{
buildNginxAnnotationKey(sslCipher): "ECDHE-RSA-AES256-GCM-SHA384:AES128-SHA",
},
@@ -40,9 +44,24 @@ func TestParse(t *testing.T) {
},
},
{
name: "with TLS version config",
input: map[string]string{
buildNginxAnnotationKey(authTLSSecret): "test",
buildNginxAnnotationKey(sslCipher): "ECDHE-RSA-AES256-GCM-SHA384:AES128-SHA",
buildNginxAnnotationKey(annotationMinTLSVersion): "TLSv1.2",
buildNginxAnnotationKey(annotationMaxTLSVersion): "TLSv1.3",
},
expect: &DownstreamTLSConfig{
Mode: networking.ServerTLSSettings_SIMPLE,
MinVersion: "TLSv1.2",
MaxVersion: "TLSv1.3",
},
},
{
name: "complete config",
input: map[string]string{
buildNginxAnnotationKey(authTLSSecret): "test",
buildNginxAnnotationKey(sslCipher): "ECDHE-RSA-AES256-GCM-SHA384:AES128-SHA",
buildNginxAnnotationKey(annotationMinTLSVersion): "TLSv1.2",
buildNginxAnnotationKey(annotationMaxTLSVersion): "TLSv1.3",
},
expect: &DownstreamTLSConfig{
CASecretName: types.NamespacedName{
@@ -51,34 +70,79 @@ func TestParse(t *testing.T) {
},
Mode: networking.ServerTLSSettings_MUTUAL,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384", "AES128-SHA"},
},
},
{
input: map[string]string{
buildHigressAnnotationKey(authTLSSecret): "test/foo",
DefaultAnnotationsPrefix + "/" + sslCipher: "ECDHE-RSA-AES256-GCM-SHA384:AES128-SHA",
},
expect: &DownstreamTLSConfig{
CASecretName: types.NamespacedName{
Namespace: "test",
Name: "foo",
},
Mode: networking.ServerTLSSettings_MUTUAL,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384", "AES128-SHA"},
MinVersion: "TLSv1.2",
MaxVersion: "TLSv1.3",
},
},
}
for _, testCase := range testCases {
t.Run("", func(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
config := &Ingress{
Meta: Meta{
Namespace: "foo",
},
}
_ = parser.Parse(testCase.input, config, nil)
if !reflect.DeepEqual(testCase.expect, config.DownstreamTLS) {
t.Fatalf("Should be equal")
err := parser.Parse(tc.input, config, nil)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if !reflect.DeepEqual(tc.expect, config.DownstreamTLS) {
t.Fatalf("Parse result mismatch:\nExpect: %+v\nGot: %+v", tc.expect, config.DownstreamTLS)
}
})
}
}
func TestConvertTLSVersion(t *testing.T) {
testCases := []struct {
name string
version string
expect networking.ServerTLSSettings_TLSProtocol
wantErr bool
}{
{
name: "TLS 1.0",
version: "TLSv1.0",
expect: networking.ServerTLSSettings_TLSV1_0,
},
{
name: "TLS 1.1",
version: "TLSv1.1",
expect: networking.ServerTLSSettings_TLSV1_1,
},
{
name: "TLS 1.2",
version: "TLSv1.2",
expect: networking.ServerTLSSettings_TLSV1_2,
},
{
name: "TLS 1.3",
version: "TLSv1.3",
expect: networking.ServerTLSSettings_TLSV1_3,
},
{
name: "invalid version",
version: "invalid",
expect: networking.ServerTLSSettings_TLS_AUTO,
wantErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := convertTLSVersion(tc.version)
if tc.wantErr {
if err == nil {
t.Error("Expected error but got none")
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if result != tc.expect {
t.Errorf("Expected %v but got %v", tc.expect, result)
}
}
})
}
@@ -86,11 +150,13 @@ func TestParse(t *testing.T) {
func TestApplyGateway(t *testing.T) {
testCases := []struct {
name string
input *networking.Gateway
config *Ingress
expect *networking.Gateway
}{
{
name: "apply TLS version",
input: &networking.Gateway{
Servers: []*networking.Server{
{
@@ -105,7 +171,8 @@ func TestApplyGateway(t *testing.T) {
},
config: &Ingress{
DownstreamTLS: &DownstreamTLSConfig{
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
MinVersion: "TLSv1.2",
MaxVersion: "TLSv1.3",
},
},
expect: &networking.Gateway{
@@ -115,14 +182,16 @@ func TestApplyGateway(t *testing.T) {
Protocol: "HTTPS",
},
Tls: &networking.ServerTLSSettings{
Mode: networking.ServerTLSSettings_SIMPLE,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
Mode: networking.ServerTLSSettings_SIMPLE,
MinProtocolVersion: networking.ServerTLSSettings_TLSV1_2,
MaxProtocolVersion: networking.ServerTLSSettings_TLSV1_3,
},
},
},
},
},
{
name: "complete config",
input: &networking.Gateway{
Servers: []*networking.Server{
{
@@ -144,24 +213,28 @@ func TestApplyGateway(t *testing.T) {
},
Mode: networking.ServerTLSSettings_MUTUAL,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
MinVersion: "TLSv1.2",
MaxVersion: "TLSv1.3",
},
},
expect: &networking.Gateway{
Servers: []*networking.Server{
{
Port: &networking.Port{
Protocol: "HTTPS",
},
{Port: &networking.Port{
Protocol: "HTTPS",
},
Tls: &networking.ServerTLSSettings{
CredentialName: "kubernetes-ingress://cluster/foo/bar",
Mode: networking.ServerTLSSettings_MUTUAL,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
CredentialName: "kubernetes-ingress://cluster/foo/bar",
Mode: networking.ServerTLSSettings_MUTUAL,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
MinProtocolVersion: networking.ServerTLSSettings_TLSV1_2,
MaxProtocolVersion: networking.ServerTLSSettings_TLSV1_3,
},
},
},
},
},
{
name: "invalid TLS version",
input: &networking.Gateway{
Servers: []*networking.Server{
{
@@ -169,20 +242,15 @@ func TestApplyGateway(t *testing.T) {
Protocol: "HTTPS",
},
Tls: &networking.ServerTLSSettings{
Mode: networking.ServerTLSSettings_SIMPLE,
CredentialName: "kubernetes-ingress://cluster/foo/bar",
Mode: networking.ServerTLSSettings_SIMPLE,
},
},
},
},
config: &Ingress{
DownstreamTLS: &DownstreamTLSConfig{
CASecretName: types.NamespacedName{
Namespace: "foo",
Name: "bar-cacert",
},
Mode: networking.ServerTLSSettings_MUTUAL,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
MinVersion: "invalid",
MaxVersion: "invalid",
},
},
expect: &networking.Gateway{
@@ -192,48 +260,10 @@ func TestApplyGateway(t *testing.T) {
Protocol: "HTTPS",
},
Tls: &networking.ServerTLSSettings{
CredentialName: "kubernetes-ingress://cluster/foo/bar",
Mode: networking.ServerTLSSettings_MUTUAL,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
},
},
},
},
},
{
input: &networking.Gateway{
Servers: []*networking.Server{
{
Port: &networking.Port{
Protocol: "HTTPS",
},
Tls: &networking.ServerTLSSettings{
Mode: networking.ServerTLSSettings_SIMPLE,
CredentialName: "kubernetes-ingress://cluster/foo/bar",
},
},
},
},
config: &Ingress{
DownstreamTLS: &DownstreamTLSConfig{
CASecretName: types.NamespacedName{
Namespace: "bar",
Name: "foo",
},
Mode: networking.ServerTLSSettings_MUTUAL,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
},
},
expect: &networking.Gateway{
Servers: []*networking.Server{
{
Port: &networking.Port{
Protocol: "HTTPS",
},
Tls: &networking.ServerTLSSettings{
CredentialName: "kubernetes-ingress://cluster/foo/bar",
Mode: networking.ServerTLSSettings_SIMPLE,
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
Mode: networking.ServerTLSSettings_SIMPLE,
// Invalid versions should default to TLS_AUTO
MinProtocolVersion: networking.ServerTLSSettings_TLS_AUTO,
MaxProtocolVersion: networking.ServerTLSSettings_TLS_AUTO,
},
},
},
@@ -241,11 +271,59 @@ func TestApplyGateway(t *testing.T) {
},
}
for _, testCase := range testCases {
t.Run("", func(t *testing.T) {
parser.ApplyGateway(testCase.input, testCase.config)
if !reflect.DeepEqual(testCase.input, testCase.expect) {
t.Fatalf("Should be equal")
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
parser.ApplyGateway(tc.input, tc.config)
if !reflect.DeepEqual(tc.input, tc.expect) {
t.Fatalf("ApplyGateway result mismatch for %s:\nExpect: %+v\nGot: %+v",
tc.name, tc.expect, tc.input)
}
})
}
}
func TestNeedDownstreamTLS(t *testing.T) {
testCases := []struct {
name string
annotations map[string]string
expect bool
}{
{
name: "empty annotations",
annotations: map[string]string{},
expect: false,
},
{
name: "with ssl cipher",
annotations: map[string]string{
buildNginxAnnotationKey(sslCipher): "ECDHE-RSA-AES256-GCM-SHA384",
},
expect: true,
},
{
name: "with TLS version",
annotations: map[string]string{
buildNginxAnnotationKey(annotationMinTLSVersion): "TLSv1.2",
},
expect: true,
},
{
name: "with multiple TLS configs",
annotations: map[string]string{
buildNginxAnnotationKey(sslCipher): "ECDHE-RSA-AES256-GCM-SHA384",
buildNginxAnnotationKey(annotationMinTLSVersion): "TLSv1.2",
buildNginxAnnotationKey(annotationMaxTLSVersion): "TLSv1.3",
},
expect: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := needDownstreamTLS(tc.annotations)
if result != tc.expect {
t.Errorf("needDownstreamTLS() for %s = %v, want %v",
tc.name, result, tc.expect)
}
})
}

View File

@@ -255,6 +255,59 @@ func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) {
return configs, nil
}
configPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(tracingConfig),
},
},
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.router",
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(`{
"name":"envoy.filters.http.router",
"typed_config":{
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router",
"start_child_span": true
}
}`),
},
},
}
patches := t.constructTracingExtendPatches(tracing)
configPatches = append(configPatches, patches...)
config := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
@@ -262,55 +315,7 @@ func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) {
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(tracingConfig),
},
},
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.router",
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(`{
"name":"envoy.filters.http.router",
"typed_config":{
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router",
"start_child_span": true
}
}`),
},
},
},
ConfigPatches: configPatches,
},
}
@@ -318,6 +323,52 @@ func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) {
return configs, nil
}
func tracingClusterName(port, service string) string {
return fmt.Sprintf("outbound|%s||%s", port, service)
}
func (t *TracingController) constructHTTP2ProtocolOptionsPatch(port, service string) *networking.EnvoyFilter_EnvoyConfigObjectPatch {
http2ProtocolOptions := `{"typed_extension_protocol_options": {
"envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
"@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
"explicit_http_config": {
"http2_protocol_options": {}
}
}
}}`
return &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_CLUSTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
Cluster: &networking.EnvoyFilter_ClusterMatch{
Name: tracingClusterName(port, service),
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(http2ProtocolOptions),
},
}
}
func (t *TracingController) constructTracingExtendPatches(tracing *Tracing) []*networking.EnvoyFilter_EnvoyConfigObjectPatch {
if tracing == nil {
return nil
}
var patches []*networking.EnvoyFilter_EnvoyConfigObjectPatch
if skywalking := tracing.Skywalking; skywalking != nil {
patches = append(patches, t.constructHTTP2ProtocolOptionsPatch(skywalking.Port, skywalking.Service))
}
if otel := tracing.OpenTelemetry; otel != nil {
patches = append(patches, t.constructHTTP2ProtocolOptionsPatch(otel.Port, otel.Service))
}
return patches
}
func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace string) string {
tracingConfig := ""
timeout := float32(tracing.Timeout) / 1000
@@ -338,7 +389,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s
},
"grpc_service": {
"envoy_grpc": {
"cluster_name": "outbound|%s||%s"
"cluster_name": "%s"
},
"timeout": "%.3fs"
}
@@ -349,7 +400,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s
}
}
}
}`, namespace, skywalking.AccessToken, skywalking.Port, skywalking.Service, timeout, tracing.Sampling)
}`, namespace, skywalking.AccessToken, tracingClusterName(skywalking.Port, skywalking.Service), timeout, tracing.Sampling)
}
if tracing.Zipkin != nil {
@@ -363,7 +414,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s
"name": "envoy.tracers.zipkin",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
"collector_cluster": "outbound|%s||%s",
"collector_cluster": "%s",
"collector_endpoint": "/api/v2/spans",
"collector_hostname": "higress-gateway",
"collector_endpoint_version": "HTTP_JSON",
@@ -375,7 +426,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s
}
}
}
}`, zipkin.Port, zipkin.Service, tracing.Sampling)
}`, tracingClusterName(zipkin.Port, zipkin.Service), tracing.Sampling)
}
if tracing.OpenTelemetry != nil {
@@ -392,7 +443,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s
"service_name": "higress-gateway.%s",
"grpc_service": {
"envoy_grpc": {
"cluster_name": "outbound|%s||%s"
"cluster_name": "%s"
},
"timeout": "%.3fs"
}
@@ -403,7 +454,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s
}
}
}
}`, namespace, opentelemetry.Port, opentelemetry.Service, timeout, tracing.Sampling)
}`, namespace, tracingClusterName(opentelemetry.Port, opentelemetry.Service), timeout, tracing.Sampling)
}
return tracingConfig
}

View File

@@ -25,6 +25,7 @@ import (
"strings"
higressconfig "github.com/alibaba/higress/pkg/config"
"github.com/alibaba/higress/pkg/ingress/kube/util"
istio "istio.io/api/networking/v1alpha3"
"istio.io/istio/pilot/pkg/features"
"istio.io/istio/pilot/pkg/model"
@@ -1880,7 +1881,7 @@ func extractGatewayServices(r GatewayResources, kgw *k8s.GatewaySpec, obj config
if len(name) > 0 {
return []string{fmt.Sprintf("%s.%s.svc.%v", name, obj.Namespace, r.Domain)}, false, nil
}
return []string{}, true, nil
return []string{fmt.Sprintf("%s.%s.svc.%s", higressconfig.GatewayName, higressconfig.PodNamespace, util.GetDomainSuffix())}, true, nil
}
gatewayServices := []string{}
skippedAddresses := []string{}

View File

@@ -919,7 +919,7 @@ func TestExtractGatewayServices(t *testing.T) {
Namespace: "default",
},
},
gatewayServices: []string{},
gatewayServices: []string{"higress-gateway.higress-system.svc.cluster.local"},
useDefaultService: true,
},
{
@@ -1039,7 +1039,7 @@ func TestExtractGatewayServices(t *testing.T) {
Namespace: "default",
},
},
gatewayServices: []string{},
gatewayServices: []string{"higress-gateway.higress-system.svc.cluster.local"},
useDefaultService: true,
},
}

View File

@@ -81,8 +81,6 @@ func (s *statusSyncer) runUpdateStatus() error {
return err
}
IngressLog.Debugf("found number %d of svc", len(svcList))
lbStatusList := common.GetLbStatusListV1Beta1(svcList)
if len(lbStatusList) == 0 {
return nil

View File

@@ -162,6 +162,7 @@ func (c *controller) onEvent(namespacedName types.NamespacedName) error {
delete(c.ingresses, namespacedName.String())
c.mutex.Unlock()
} else {
IngressLog.Warnf("ingressLister Get failed, ingress: %s, err: %v", namespacedName, err)
return err
}
}
@@ -171,7 +172,7 @@ func (c *controller) onEvent(namespacedName types.NamespacedName) error {
return nil
}
IngressLog.Debugf("ingress: %s, event: %s", namespacedName, event)
IngressLog.Infof("ingress: %s, event: %s", namespacedName, event)
// we should check need process only when event is not delete,
// if it is delete event, and previously processed, we need to process too.
@@ -181,7 +182,7 @@ func (c *controller) onEvent(namespacedName types.NamespacedName) error {
return err
}
if !shouldProcess {
IngressLog.Infof("no need process, ingress %s", namespacedName)
IngressLog.Infof("no need process, ingress: %s", namespacedName)
return nil
}
}
@@ -279,10 +280,17 @@ func (c *controller) List() []config.Config {
for _, raw := range c.ingressInformer.Informer.GetStore().List() {
ing, ok := raw.(*ingress.Ingress)
if !ok {
IngressLog.Warnf("get ingress from informer failed: %v", raw)
continue
}
if should, err := c.shouldProcessIngress(ing); !should || err != nil {
should, err := c.shouldProcessIngress(ing)
if err != nil {
IngressLog.Warnf("check should process ingress failed: %v", err)
continue
}
if !should {
IngressLog.Debugf("no need process ingress: %s/%s", ing.Namespace, ing.Name)
continue
}

View File

@@ -81,8 +81,6 @@ func (s *statusSyncer) runUpdateStatus() error {
return err
}
IngressLog.Debugf("found number %d of svc", len(svcList))
lbStatusList := common.GetLbStatusListV1(svcList)
if len(lbStatusList) == 0 {
return nil

View File

@@ -77,7 +77,6 @@ func (s *statusSyncer) runUpdateStatus() error {
return err
}
IngressLog.Debugf("found number %d of svc", len(svcList))
lbStatusList := common2.GetLbStatusList(svcList)
return s.updateStatus(lbStatusList)
}

View File

@@ -14,6 +14,6 @@
package log
import "istio.io/pkg/log"
import "istio.io/istio/pkg/log"
var IngressLog = log.RegisterScope("ingress", "Higress Ingress process.", 0)
var IngressLog = log.RegisterScope("ingress", "Higress Ingress process.")

View File

@@ -187,10 +187,9 @@ func (m *IngressTranslation) List(typ config.GroupVersionKind, namespace string)
higressConfig = append(higressConfig, ingressConfig...)
if m.kingressConfig != nil {
kingressConfig := m.kingressConfig.List(typ, namespace)
if kingressConfig == nil {
return nil
if kingressConfig != nil {
higressConfig = append(higressConfig, kingressConfig...)
}
higressConfig = append(higressConfig, kingressConfig...)
}
return higressConfig
}

View File

@@ -1,5 +1,6 @@
## Wasm 插件
目前 Higress 提供了 c++ 和 golang 两种 Wasm 插件开发框架,支持 Wasm 插件路由&域名级匹配生效。
同时提供了多个内置插件,用户可以基于 Higress 提供的官方镜像仓库直接使用这些插件(以 c++ 版本举例):

View File

@@ -16,24 +16,28 @@ load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps")
container_deps()
PROXY_WASM_CPP_SDK_SHA = "fd0be8405db25de0264bdb78fae3a82668c03782"
PROXY_WASM_CPP_SDK_SHA = "eaec483b5b3c7bcb89fd208b5a1fa5d79d626f61"
PROXY_WASM_CPP_SDK_SHA256 = "c57de2425b5c61d7f630c5061e319b4557ae1f1c7526e5a51c33dc1299471b08"
PROXY_WASM_CPP_SDK_SHA256 = "1140bc8114d75db56a6ca6b18423d4df50d988d40b4cec929a1eb246cf5a4a3d"
http_archive(
name = "proxy_wasm_cpp_sdk",
sha256 = PROXY_WASM_CPP_SDK_SHA256,
strip_prefix = "proxy-wasm-cpp-sdk-" + PROXY_WASM_CPP_SDK_SHA,
url = "https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/archive/" + PROXY_WASM_CPP_SDK_SHA + ".tar.gz",
url = "https://github.com/higress-group/proxy-wasm-cpp-sdk/archive/" + PROXY_WASM_CPP_SDK_SHA + ".tar.gz",
)
load("@proxy_wasm_cpp_sdk//bazel/dep:deps.bzl", "wasm_dependencies")
load("@proxy_wasm_cpp_sdk//bazel:repositories.bzl", "proxy_wasm_cpp_sdk_repositories")
wasm_dependencies()
proxy_wasm_cpp_sdk_repositories()
load("@proxy_wasm_cpp_sdk//bazel/dep:deps_extra.bzl", "wasm_dependencies_extra")
load("@proxy_wasm_cpp_sdk//bazel:dependencies.bzl", "proxy_wasm_cpp_sdk_dependencies")
wasm_dependencies_extra()
proxy_wasm_cpp_sdk_dependencies()
load("@proxy_wasm_cpp_sdk//bazel:dependencies_extra.bzl", "proxy_wasm_cpp_sdk_dependencies_extra")
proxy_wasm_cpp_sdk_dependencies_extra()
load("@istio_ecosystem_wasm_extensions//bazel:wasm.bzl", "wasm_libraries")

View File

@@ -2,16 +2,16 @@ diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/interna
index d8cb047..0c5f182 100644
--- a/absl/time/internal/cctz/src/time_zone_format.cc
+++ b/absl/time/internal/cctz/src/time_zone_format.cc
@@ -18,6 +18,8 @@
#endif
#endif
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#define HAS_STRPTIME 0
+
#if defined(HAS_STRPTIME) && HAS_STRPTIME
#if !defined(_XOPEN_SOURCE)
#define _XOPEN_SOURCE // Definedness suffices for strptime.
@@ -58,7 +60,7 @@ namespace {
#if !defined(HAS_STRPTIME)
#if !defined(_MSC_VER) && !defined(__MINGW32__)
#define HAS_STRPTIME 1 // assume everyone has strptime() except windows
@@ -58,7 +60,7 @@
#if !HAS_STRPTIME
// Build a strptime() using C++11's std::get_time().
@@ -20,7 +20,7 @@ index d8cb047..0c5f182 100644
std::istringstream input(s);
input >> std::get_time(tm, fmt);
if (input.fail()) return nullptr;
@@ -648,7 +650,7 @@ const char* ParseSubSeconds(const char* dp, detail::femtoseconds* subseconds) {
@@ -648,7 +650,7 @@
// Parses a string into a std::tm using strptime(3).
const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) {
if (dp != nullptr) {

View File

@@ -9,9 +9,9 @@ load(
def wasm_libraries():
http_archive(
name = "com_google_absl",
sha256 = "ec8ef47335310cc3382bdc0d0cc1097a001e67dc83fcba807845aa5696e7e1e4",
strip_prefix = "abseil-cpp-302b250e1d917ede77b5ff00a6fd9f28430f1563",
url = "https://github.com/abseil/abseil-cpp/archive/302b250e1d917ede77b5ff00a6fd9f28430f1563.tar.gz",
sha256 = "3a0bb3d2e6f53352526a8d1a7e7b5749c68cd07f2401766a404fb00d2853fa49",
strip_prefix = "abseil-cpp-4bbdb026899fea9f882a95cbd7d6a4adaf49b2dd",
url = "https://github.com/abseil/abseil-cpp/archive/4bbdb026899fea9f882a95cbd7d6a4adaf49b2dd.tar.gz",
patch_args = ["-p1"],
patches = ["//bazel:absl.patch"],
)
@@ -33,14 +33,14 @@ def wasm_libraries():
urls = ["https://github.com/google/googletest/archive/release-1.10.0.tar.gz"],
)
PROXY_WASM_CPP_HOST_SHA = "f38347360feaaf5b2a733f219c4d8c9660d626f0"
PROXY_WASM_CPP_HOST_SHA256 = "bf10de946eb5785813895c2bf16504afc0cd590b9655d9ee52fb1074d0825ea3"
PROXY_WASM_CPP_HOST_SHA = "ecf42a27fcf78f42e64037d4eff1a0ca5a61e403"
PROXY_WASM_CPP_HOST_SHA256 = "9748156731e9521837686923321bf12725c32c9fa8355218209831cc3ee87080"
http_archive(
name = "proxy_wasm_cpp_host",
sha256 = PROXY_WASM_CPP_HOST_SHA256,
strip_prefix = "proxy-wasm-cpp-host-" + PROXY_WASM_CPP_HOST_SHA,
url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/" + PROXY_WASM_CPP_HOST_SHA +".tar.gz",
url = "https://github.com/higress-group/proxy-wasm-cpp-host/archive/" + PROXY_WASM_CPP_HOST_SHA +".tar.gz",
)
http_archive(

View File

@@ -19,7 +19,6 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "common/common_util.h"
namespace Wasm::Common::Http {
@@ -190,7 +189,8 @@ std::vector<std::string> getAllOfHeader(std::string_view key) {
std::vector<std::string> result;
auto headers = getRequestHeaderPairs()->pairs();
for (auto& header : headers) {
if (absl::EqualsIgnoreCase(Wasm::Common::stdToAbsl(header.first), Wasm::Common::stdToAbsl(key))) {
if (absl::EqualsIgnoreCase(Wasm::Common::stdToAbsl(header.first),
Wasm::Common::stdToAbsl(key))) {
result.push_back(std::string(header.second));
}
}
@@ -225,7 +225,8 @@ void forEachCookie(
v = v.substr(1, v.size() - 2);
}
if (!cookie_consumer(Wasm::Common::abslToStd(k), Wasm::Common::abslToStd(v))) {
if (!cookie_consumer(Wasm::Common::abslToStd(k),
Wasm::Common::abslToStd(v))) {
return;
}
}
@@ -265,7 +266,63 @@ std::string buildOriginalUri(std::optional<uint32_t> max_path_length) {
auto scheme = scheme_ptr->view();
auto host_ptr = getRequestHeader(Header::Host);
auto host = host_ptr->view();
return absl::StrCat(Wasm::Common::stdToAbsl(scheme), "://", Wasm::Common::stdToAbsl(host), Wasm::Common::stdToAbsl(final_path));
return absl::StrCat(Wasm::Common::stdToAbsl(scheme), "://",
Wasm::Common::stdToAbsl(host),
Wasm::Common::stdToAbsl(final_path));
}
void extractHostPathFromUri(const absl::string_view& uri,
absl::string_view& host, absl::string_view& path) {
/**
* URI RFC: https://www.ietf.org/rfc/rfc2396.txt
*
* Example:
* uri = "https://example.com:8443/certs"
* pos: ^
* host_pos: ^
* path_pos: ^
* host = "example.com:8443"
* path = "/certs"
*/
const auto pos = uri.find("://");
// Start position of the host
const auto host_pos = (pos == std::string::npos) ? 0 : pos + 3;
// Start position of the path
const auto path_pos = uri.find('/', host_pos);
if (path_pos == std::string::npos) {
// If uri doesn't have "/", the whole string is treated as host.
host = uri.substr(host_pos);
path = "/";
} else {
host = uri.substr(host_pos, path_pos - host_pos);
path = uri.substr(path_pos);
}
}
void extractPathWithoutArgsFromUri(const std::string_view& uri,
std::string_view& path_without_args) {
auto params_pos = uri.find('?');
size_t uri_end;
if (params_pos == std::string::npos) {
uri_end = uri.size();
} else {
uri_end = params_pos;
}
path_without_args = uri.substr(0, uri_end);
}
bool hasRequestBody() {
auto contentType = getRequestHeader("content-type")->toString();
auto contentLengthStr = getRequestHeader("content-length")->toString();
auto transferEncoding = getRequestHeader("transfer-encoding")->toString();
if (!contentType.empty()) {
return true;
}
if (!contentLengthStr.empty()) {
return true;
}
return transferEncoding.find("chunked") != std::string::npos;
}
} // namespace Wasm::Common::Http

View File

@@ -42,6 +42,12 @@ namespace Wasm::Common::Http {
using QueryParams = std::map<std::string, std::string>;
using SystemTime = std::chrono::time_point<std::chrono::system_clock>;
namespace Status {
constexpr int OK = 200;
constexpr int InternalServerError = 500;
constexpr int Unauthorized = 401;
} // namespace Status
namespace Header {
constexpr std::string_view Scheme(":scheme");
constexpr std::string_view Method(":method");
@@ -52,14 +58,17 @@ constexpr std::string_view Accept("accept");
constexpr std::string_view ContentMD5("content-md5");
constexpr std::string_view ContentType("content-type");
constexpr std::string_view ContentLength("content-length");
constexpr std::string_view TransferEncoding("transfer-encoding");
constexpr std::string_view UserAgent("user-agent");
constexpr std::string_view Date("date");
constexpr std::string_view Cookie("cookie");
constexpr std::string_view StrictTransportSecurity("strict-transport-security");
} // namespace Header
namespace ContentTypeValues {
constexpr std::string_view Grpc{"application/grpc"};
}
constexpr std::string_view Json{"application/json"};
} // namespace ContentTypeValues
class PercentEncoding {
public:
@@ -142,4 +151,10 @@ std::unordered_map<std::string, std::string> parseCookies(
std::string buildOriginalUri(std::optional<uint32_t> max_path_length);
void extractHostPathFromUri(const absl::string_view& uri,
absl::string_view& host, absl::string_view& path);
void extractPathWithoutArgsFromUri(const std::string_view& uri,
std::string_view& path_without_args);
bool hasRequestBody();
} // namespace Wasm::Common::Http

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
wasm_cc_binary(
proxy_wasm_cc_binary(
name = "bot_detect.wasm",
srcs = [
"plugin.cc",
@@ -28,7 +28,6 @@ wasm_cc_binary(
"//common:http_util",
"//common:regex_util",
"//common:rule_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
wasm_cc_binary(
proxy_wasm_cc_binary(
name = "custom_response.wasm",
srcs = [
"plugin.cc",
@@ -27,7 +27,6 @@ wasm_cc_binary(
"//common:json_util",
"//common:http_util",
"//common:rule_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
wasm_cc_binary(
proxy_wasm_cc_binary(
name = "hmac_auth.wasm",
srcs = [
"plugin.cc",
@@ -30,7 +30,6 @@ wasm_cc_binary(
"//common:crypto_util",
"//common:http_util",
"//common:rule_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)

View File

@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
wasm_cc_binary(
@@ -33,7 +33,6 @@ wasm_cc_binary(
"//common:json_util",
"//common:http_util",
"//common:rule_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
wasm_cc_binary(
proxy_wasm_cc_binary(
name = "key_auth.wasm",
srcs = [
"plugin.cc",
@@ -28,7 +28,6 @@ wasm_cc_binary(
"//common:json_util",
"//common:http_util",
"//common:rule_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)

View File

@@ -42,10 +42,7 @@ static RegisterContextFactory register_KeyAuth(CONTEXT_FACTORY(PluginContext),
namespace {
void deniedNoKeyAuthData(const std::string& realm) {
sendLocalResponse(401, "No API key found in request", "",
{{"WWW-Authenticate", absl::StrCat("Key realm=", realm)}});
}
const std::string OriginalAuthKey("X-HI-ORIGINAL-AUTH");
void deniedInvalidCredentials(const std::string& realm) {
sendLocalResponse(401, "Request denied by Key Auth check. Invalid API key",
@@ -84,6 +81,7 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
}
if (!JsonArrayIterate(
configuration, "consumers", [&](const json& consumer) -> bool {
Consumer c;
auto item = consumer.find("name");
if (item == consumer.end()) {
LOG_WARN("can't find 'name' field in consumer.");
@@ -94,6 +92,7 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
!name.first) {
return false;
}
c.name = name.first.value();
item = consumer.find("credential");
if (item == consumer.end()) {
LOG_WARN("can't find 'credential' field in consumer.");
@@ -104,6 +103,7 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
!credential.first) {
return false;
}
c.credential = credential.first.value();
if (rule.credential_to_name.find(credential.first.value()) !=
rule.credential_to_name.end()) {
LOG_WARN(absl::StrCat("duplicate consumer credential: ",
@@ -113,15 +113,59 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
rule.credentials.insert(credential.first.value());
rule.credential_to_name.emplace(
std::make_pair(credential.first.value(), name.first.value()));
item = consumer.find("keys");
if (item != consumer.end()) {
c.keys = std::vector<std::string>{OriginalAuthKey};
if (!JsonArrayIterate(
consumer, "keys", [&](const json& key_json) -> bool {
auto key = JsonValueAs<std::string>(key_json);
if (key.second !=
Wasm::Common::JsonParserResultDetail::OK) {
return false;
}
c.keys->push_back(key.first.value());
return true;
})) {
LOG_WARN("failed to parse configuration for consumer keys.");
return false;
}
item = consumer.find("in_query");
if (item != consumer.end()) {
auto in_query = JsonValueAs<bool>(item.value());
if (in_query.second !=
Wasm::Common::JsonParserResultDetail::OK ||
!in_query.first) {
LOG_WARN(
"failed to parse 'in_query' field in consumer "
"configuration.");
return false;
}
c.in_query = in_query.first;
}
item = consumer.find("in_header");
if (item != consumer.end()) {
auto in_header = JsonValueAs<bool>(item.value());
if (in_header.second !=
Wasm::Common::JsonParserResultDetail::OK ||
!in_header.first) {
LOG_WARN(
"failed to parse 'in_header' field in consumer "
"configuration.");
return false;
}
c.in_header = in_header.first;
}
}
rule.consumers.push_back(std::move(c));
return true;
})) {
LOG_WARN("failed to parse configuration for credentials.");
return false;
}
if (rule.credentials.empty()) {
LOG_INFO("at least one credential has to be configured for a rule.");
return false;
}
// if (rule.credentials.empty()) {
// LOG_INFO("at least one credential has to be configured for a rule.");
// return false;
// }
if (!JsonArrayIterate(configuration, "keys", [&](const json& item) -> bool {
auto key = JsonValueAs<std::string>(item);
if (key.second != Wasm::Common::JsonParserResultDetail::OK) {
@@ -137,6 +181,7 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
LOG_WARN("at least one key has to be configured for a rule.");
return false;
}
rule.keys.push_back(OriginalAuthKey);
auto it = configuration.find("realm");
if (it != configuration.end()) {
auto realm_string = JsonValueAs<std::string>(it.value());
@@ -175,36 +220,102 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
bool PluginRootContext::checkPlugin(
const KeyAuthConfigRule& rule,
const std::optional<std::unordered_set<std::string>>& allow_set) {
auto credential = extractCredential(rule);
if (credential.empty()) {
LOG_DEBUG("empty credential");
deniedNoKeyAuthData(rule.realm);
return false;
}
auto auth_credential_iter = rule.credentials.find(std::string(credential));
// Check if the credential is part of the credentials
// set from our container to grant or deny access.
if (auth_credential_iter == rule.credentials.end()) {
LOG_DEBUG(absl::StrCat("api key not found: ", credential));
deniedInvalidCredentials(rule.realm);
return false;
}
// Check if this credential has a consumer name. If so, check if this
// consumer is allowed to access. If allow_set is empty, allow all consumers.
auto credential_to_name_iter =
rule.credential_to_name.find(std::string(std::string(credential)));
if (credential_to_name_iter != rule.credential_to_name.end()) {
if (allow_set && !allow_set.value().empty()) {
if (allow_set.value().find(credential_to_name_iter->second) ==
allow_set.value().end()) {
deniedUnauthorizedConsumer(rule.realm);
LOG_DEBUG(credential_to_name_iter->second);
return false;
if (rule.consumers.empty()) {
for (const auto& key : rule.keys) {
auto credential = extractCredential(rule.in_header, rule.in_query, key);
if (credential.empty()) {
LOG_DEBUG("empty credential for key: " + key);
continue;
}
auto auth_credential_iter = rule.credentials.find(credential);
if (auth_credential_iter == rule.credentials.end()) {
LOG_DEBUG("api key not found: " + credential);
continue;
}
auto credential_to_name_iter = rule.credential_to_name.find(credential);
if (credential_to_name_iter != rule.credential_to_name.end()) {
if (allow_set && !allow_set->empty()) {
if (allow_set->find(credential_to_name_iter->second) ==
allow_set->end()) {
deniedUnauthorizedConsumer(rule.realm);
LOG_DEBUG("unauthorized consumer: " +
credential_to_name_iter->second);
return false;
}
}
addRequestHeader("X-Mse-Consumer", credential_to_name_iter->second);
}
return true;
}
} else {
for (const auto& consumer : rule.consumers) {
std::vector<std::string> keys_to_check =
consumer.keys.value_or(rule.keys);
bool in_query = consumer.in_query.value_or(rule.in_query);
bool in_header = consumer.in_header.value_or(rule.in_header);
for (const auto& key : keys_to_check) {
auto credential = extractCredential(in_header, in_query, key);
if (credential.empty()) {
LOG_DEBUG("empty credential for key: " + key);
continue;
}
if (credential != consumer.credential) {
LOG_DEBUG("credential does not match the consumer's credential: " +
credential);
continue;
}
auto auth_credential_iter = rule.credentials.find(credential);
if (auth_credential_iter == rule.credentials.end()) {
LOG_DEBUG("api key not found: " + credential);
continue;
}
auto credential_to_name_iter = rule.credential_to_name.find(credential);
if (credential_to_name_iter != rule.credential_to_name.end()) {
if (allow_set && !allow_set->empty()) {
if (allow_set->find(credential_to_name_iter->second) ==
allow_set->end()) {
deniedUnauthorizedConsumer(rule.realm);
LOG_DEBUG("unauthorized consumer: " +
credential_to_name_iter->second);
return false;
}
}
addRequestHeader("X-Mse-Consumer", credential_to_name_iter->second);
}
return true;
}
}
addRequestHeader("X-Mse-Consumer", credential_to_name_iter->second);
}
return true;
LOG_DEBUG("No valid credentials were found after checking all consumers.");
deniedInvalidCredentials(rule.realm);
return false;
}
std::string PluginRootContext::extractCredential(bool in_header, bool in_query,
const std::string& key) {
if (in_header) {
auto header = getRequestHeader(key);
if (header->size() != 0) {
return header->toString();
}
}
if (in_query) {
auto request_path_header = getRequestHeader(":path");
auto path = request_path_header->view();
auto params = Wasm::Common::Http::parseAndDecodeQueryString(path);
auto it = params.find(key);
if (it != params.end()) {
return it->second;
}
}
return "";
}
bool PluginRootContext::onConfigure(size_t size) {
@@ -234,31 +345,6 @@ bool PluginRootContext::configure(size_t configuration_size) {
return true;
}
std::string PluginRootContext::extractCredential(
const KeyAuthConfigRule& rule) {
auto request_path_header = getRequestHeader(":path");
auto path = request_path_header->view();
LOG_DEBUG(std::string(path));
if (rule.in_query) {
auto params = Wasm::Common::Http::parseAndDecodeQueryString(path);
for (const auto& key : rule.keys) {
auto it = params.find(key);
if (it != params.end()) {
return it->second;
}
}
}
if (rule.in_header) {
for (const auto& key : rule.keys) {
auto header = getRequestHeader(key);
if (header->size() != 0) {
return header->toString();
}
}
}
return "";
}
FilterHeadersStatus PluginContext::onRequestHeaders(uint32_t, bool) {
auto* rootCtx = rootContext();
return rootCtx->checkAuthRule(

View File

@@ -36,7 +36,16 @@ namespace key_auth {
#endif
struct Consumer {
std::string name;
std::string credential;
std::optional<std::vector<std::string>> keys;
std::optional<bool> in_query = std::nullopt;
std::optional<bool> in_header = std::nullopt;
};
struct KeyAuthConfigRule {
std::vector<Consumer> consumers;
std::unordered_set<std::string> credentials;
std::unordered_map<std::string, std::string> credential_to_name;
std::string realm = "MSE Gateway";
@@ -61,7 +70,8 @@ class PluginRootContext : public RootContext,
private:
bool parsePluginConfig(const json&, KeyAuthConfigRule&) override;
std::string extractCredential(const KeyAuthConfigRule&);
std::string extractCredential(bool in_header, bool in_query,
const std::string& key);
};
// Per-stream context.

View File

@@ -121,7 +121,7 @@ TEST_F(KeyAuthTest, InQuery) {
"_rules_": [
{
"_match_route_": ["test"],
"credentials":["abc"],
"credentials":["abc","def"],
"keys": ["apiKey", "x-api-key"]
}
]
@@ -144,6 +144,10 @@ TEST_F(KeyAuthTest, InQuery) {
path_ = "/test?hello=123&apiKey=123";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
path_ = "/test?hello=123&apiKey=123&x-api-key=def";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
}
TEST_F(KeyAuthTest, InQueryWithConsumer) {
@@ -173,6 +177,29 @@ TEST_F(KeyAuthTest, InQueryWithConsumer) {
FilterHeadersStatus::StopIteration);
}
TEST_F(KeyAuthTest, EmptyConsumer) {
std::string configuration = R"(
{
"consumers" : [],
"keys" : [ "apiKey", "x-api-key" ],
"_rules_" : [ {"_match_route_" : ["test"], "allow" : []} ]
})";
BufferBase buffer;
buffer.set(configuration);
EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration))
.WillOnce([&buffer](WasmBufferType) { return &buffer; });
EXPECT_TRUE(root_context_->configure(configuration.size()));
route_name_ = "test";
path_ = "/test?hello=1&apiKey=abc";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
route_name_ = "test2";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
}
TEST_F(KeyAuthTest, InHeader) {
std::string configuration = R"(
{
@@ -240,6 +267,40 @@ TEST_F(KeyAuthTest, InHeaderWithConsumer) {
FilterHeadersStatus::StopIteration);
}
TEST_F(KeyAuthTest, ConsumerDifferentKey) {
std::string configuration = R"(
{
"consumers" : [ {"credential" : "abc", "name" : "consumer1", "keys" : [ "apiKey" ]}, {"credential" : "123", "name" : "consumer2"} ],
"keys" : [ "apiKey2" ],
"_rules_" : [ {"_match_route_" : ["test"], "allow" : ["consumer1"]}, {"_match_route_" : ["test2"], "allow" : ["consumer2"]} ]
})";
BufferBase buffer;
buffer.set(configuration);
EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration))
.WillOnce([&buffer](WasmBufferType) { return &buffer; });
EXPECT_TRUE(root_context_->configure(configuration.size()));
route_name_ = "test";
path_ = "/test?hello=1&apiKey=abc";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
route_name_ = "test";
path_ = "/test?hello=1&apiKey2=abc";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
route_name_ = "test";
path_ = "/test?hello=123&apiKey2=123";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
route_name_ = "test2";
path_ = "/test?hello=123&apiKey2=123";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
}
} // namespace key_auth
} // namespace null_plugin
} // namespace proxy_wasm

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
wasm_cc_binary(
proxy_wasm_cc_binary(
name = "key_rate_limit.wasm",
srcs = [
"plugin.cc",
@@ -29,7 +29,6 @@ wasm_cc_binary(
"//common:json_util",
"//common:http_util",
"//common:rule_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)

View File

@@ -0,0 +1,70 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
proxy_wasm_cc_binary(
name = "model_mapper.wasm",
srcs = [
"plugin.cc",
"plugin.h",
],
deps = [
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_higress",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"//common:json_util",
"//common:http_util",
"//common:rule_util",
],
)
cc_library(
name = "model_mapper_lib",
srcs = [
"plugin.cc",
],
hdrs = [
"plugin.h",
],
copts = ["-DNULL_PLUGIN"],
deps = [
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"//common:json_util",
"@proxy_wasm_cpp_host//:lib",
"//common:http_util_nullvm",
"//common:rule_util_nullvm",
],
)
cc_test(
name = "model_mapper_test",
srcs = [
"plugin_test.cc",
],
copts = ["-DNULL_PLUGIN"],
deps = [
":model_mapper_lib",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
"@proxy_wasm_cpp_host//:lib",
],
)
declare_wasm_image_targets(
name = "model_mapper",
wasm_file = ":model_mapper.wasm",
)

View File

@@ -0,0 +1,61 @@
# 功能说明
`model-mapper`插件实现了基于LLM协议中的model参数路由的功能
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- |
| `modelKey` | string | 选填 | model | 请求body中model参数的位置 |
| `modelMapping` | map of string | 选填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。<br/>1. 支持前缀匹配。例如用 "gpt-3-*" 匹配所有名称以“gpt-3-”开头的模型;<br/>2. 支持使用 "*" 为键来配置通用兜底映射关系;<br/>3. 如果映射的目标名称为空字符串 "",则表示保留原模型名称。 |
| `enableOnPathSuffix` | array of string | 选填 | ["/v1/chat/completions"] | 只对这些特定路径后缀的请求生效 |
## 效果说明
如下配置
```yaml
modelMapping:
'gpt-4-*': "qwen-max"
'gpt-4o': "qwen-vl-plus"
'*': "qwen-turbo"
```
开启后,`gpt-4-` 开头的模型参数会被改写为 `qwen-max`, `gpt-4o` 会被改写为 `qwen-vl-plus`,其他所有模型会被改写为 `qwen-turbo`
例如原本的请求是:
```json
{
"model": "gpt-4o",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "higress项目主仓库的github地址是什么"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```
经过这个插件后,原始的 LLM 请求体将被改成:
```json
{
"model": "qwen-vl-plus",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "higress项目主仓库的github地址是什么"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```

View File

@@ -0,0 +1,65 @@
## Function Description
The `model-mapper` plugin implements the functionality of routing based on the model parameter in the LLM protocol.
## Configuration Fields
| Name | Data Type | Filling Requirement | Default Value | Description |
| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- |
| `modelKey` | string | Optional | model | The location of the model parameter in the request body. |
| `modelMapping` | map of string | Optional | - | AI model mapping table, used to map the model names in the request to the model names supported by the service provider.<br/>1. Supports prefix matching. For example, use "gpt-3-*" to match all models whose names start with “gpt-3-”;<br/>2. Supports using "*" as the key to configure a generic fallback mapping relationship;<br/>3. If the target name in the mapping is an empty string "", it means to keep the original model name. |
| `enableOnPathSuffix` | array of string | Optional | ["/v1/chat/completions"] | Only applies to requests with these specific path suffixes. |
## Runtime Properties
Plugin execution phase: Authentication phase
Plugin execution priority: 800
## Effect Description
With the following configuration:
```yaml
modelMapping:
'gpt-4-*': "qwen-max"
'gpt-4o': "qwen-vl-plus"
'*': "qwen-turbo"
```
After enabling, model parameters starting with `gpt-4-` will be rewritten to `qwen-max`, `gpt-4o` will be rewritten to `qwen-vl-plus`, and all other models will be rewritten to `qwen-turbo`.
For example, if the original request was:
```json
{
"model": "gpt-4o",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "What is the GitHub address of the main repository for the higress project?"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```
After processing by this plugin, the original LLM request body will be modified to:
```json
{
"model": "qwen-vl-plus",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "What is the GitHub address of the main repository for the higress project?"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```

View File

@@ -0,0 +1,243 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "extensions/model_mapper/plugin.h"
#include <array>
#include <limits>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "common/http_util.h"
#include "common/json_util.h"
using ::nlohmann::json;
using ::Wasm::Common::JsonArrayIterate;
using ::Wasm::Common::JsonGetField;
using ::Wasm::Common::JsonObjectIterate;
using ::Wasm::Common::JsonValueAs;
#ifdef NULL_PLUGIN
namespace proxy_wasm {
namespace null_plugin {
namespace model_mapper {
PROXY_WASM_NULL_PLUGIN_REGISTRY
#endif
static RegisterContextFactory register_ModelMapper(
CONTEXT_FACTORY(PluginContext), ROOT_FACTORY(PluginRootContext));
namespace {
constexpr std::string_view SetDecoderBufferLimitKey =
"SetRequestBodyBufferLimit";
constexpr std::string_view DefaultMaxBodyBytes = "10485760";
} // namespace
bool PluginRootContext::parsePluginConfig(const json& configuration,
ModelMapperConfigRule& rule) {
if (auto it = configuration.find("modelKey"); it != configuration.end()) {
if (it->is_string()) {
rule.model_key_ = it->get<std::string>();
} else {
LOG_ERROR("Invalid type for modelKey. Expected string.");
return false;
}
}
if (auto it = configuration.find("modelMapping"); it != configuration.end()) {
if (!it->is_object()) {
LOG_ERROR("Invalid type for modelMapping. Expected object.");
return false;
}
auto model_mapping = it->get<Wasm::Common::JsonObject>();
if (!JsonObjectIterate(model_mapping, [&](std::string key) -> bool {
auto model_json = model_mapping.find(key);
if (!model_json->is_string()) {
LOG_ERROR(
"Invalid type for item in modelMapping. Expected string.");
return false;
}
if (key == "*") {
rule.default_model_mapping_ = model_json->get<std::string>();
return true;
}
if (absl::EndsWith(key, "*")) {
rule.prefix_model_mapping_.emplace_back(
absl::StripSuffix(key, "*"), model_json->get<std::string>());
return true;
}
auto ret = rule.exact_model_mapping_.emplace(
key, model_json->get<std::string>());
if (!ret.second) {
LOG_ERROR("Duplicate key in modelMapping: " + key);
return false;
}
return true;
})) {
return false;
}
}
if (!JsonArrayIterate(
configuration, "enableOnPathSuffix", [&](const json& item) -> bool {
if (item.is_string()) {
rule.enable_on_path_suffix_.emplace_back(item.get<std::string>());
return true;
}
return false;
})) {
LOG_WARN("Invalid type for item in enableOnPathSuffix. Expected string.");
return false;
}
return true;
}
bool PluginRootContext::onConfigure(size_t size) {
// Parse configuration JSON string.
if (size > 0 && !configure(size)) {
LOG_WARN("configuration has errors initialization will not continue.");
return false;
}
return true;
}
bool PluginRootContext::configure(size_t configuration_size) {
auto configuration_data = getBufferBytes(WasmBufferType::PluginConfiguration,
0, configuration_size);
// Parse configuration JSON string.
auto result = ::Wasm::Common::JsonParse(configuration_data->view());
if (!result) {
LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ",
configuration_data->view()));
return false;
}
if (!parseRuleConfig(result.value())) {
LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ",
configuration_data->view()));
return false;
}
return true;
}
FilterHeadersStatus PluginRootContext::onHeader(
const ModelMapperConfigRule& rule) {
if (!Wasm::Common::Http::hasRequestBody()) {
return FilterHeadersStatus::Continue;
}
auto path = getRequestHeader(Wasm::Common::Http::Header::Path)->toString();
auto params_pos = path.find('?');
size_t uri_end;
if (params_pos == std::string::npos) {
uri_end = path.size();
} else {
uri_end = params_pos;
}
bool enable = false;
for (const auto& enable_suffix : rule.enable_on_path_suffix_) {
if (absl::EndsWith({path.c_str(), uri_end}, enable_suffix)) {
enable = true;
break;
}
}
if (!enable) {
return FilterHeadersStatus::Continue;
}
auto content_type_value =
getRequestHeader(Wasm::Common::Http::Header::ContentType);
if (!absl::StrContains(content_type_value->view(),
Wasm::Common::Http::ContentTypeValues::Json)) {
return FilterHeadersStatus::Continue;
}
removeRequestHeader(Wasm::Common::Http::Header::ContentLength);
setFilterState(SetDecoderBufferLimitKey, DefaultMaxBodyBytes);
return FilterHeadersStatus::StopIteration;
}
FilterDataStatus PluginRootContext::onBody(const ModelMapperConfigRule& rule,
std::string_view body) {
const auto& exact_model_mapping = rule.exact_model_mapping_;
const auto& prefix_model_mapping = rule.prefix_model_mapping_;
const auto& default_model_mapping = rule.default_model_mapping_;
const auto& model_key = rule.model_key_;
auto body_json_opt = ::Wasm::Common::JsonParse(body);
if (!body_json_opt) {
LOG_WARN(absl::StrCat("cannot parse body to JSON string: ", body));
return FilterDataStatus::Continue;
}
auto body_json = body_json_opt.value();
std::string old_model;
if (body_json.contains(model_key)) {
old_model = body_json[model_key];
}
std::string model =
default_model_mapping.empty() ? old_model : default_model_mapping;
if (auto it = exact_model_mapping.find(old_model);
it != exact_model_mapping.end()) {
model = it->second;
} else {
for (auto& prefix_model_pair : prefix_model_mapping) {
if (absl::StartsWith(old_model, prefix_model_pair.first)) {
model = prefix_model_pair.second;
break;
}
}
}
if (!model.empty() && model != old_model) {
body_json[model_key] = model;
setBuffer(WasmBufferType::HttpRequestBody, 0,
std::numeric_limits<size_t>::max(), body_json.dump());
LOG_DEBUG(
absl::StrCat("model mapped, before:", old_model, ", after:", model));
}
return FilterDataStatus::Continue;
}
FilterHeadersStatus PluginContext::onRequestHeaders(uint32_t, bool) {
auto* rootCtx = rootContext();
return rootCtx->onHeaders([rootCtx, this](const auto& config) {
auto ret = rootCtx->onHeader(config);
if (ret == FilterHeadersStatus::StopIteration) {
this->config_ = &config;
}
return ret;
});
}
FilterDataStatus PluginContext::onRequestBody(size_t body_size,
bool end_stream) {
if (config_ == nullptr) {
return FilterDataStatus::Continue;
}
body_total_size_ += body_size;
if (!end_stream) {
return FilterDataStatus::StopIterationAndBuffer;
}
auto body =
getBufferBytes(WasmBufferType::HttpRequestBody, 0, body_total_size_);
auto* rootCtx = rootContext();
return rootCtx->onBody(*config_, body->view());
}
#ifdef NULL_PLUGIN
} // namespace model_mapper
} // namespace null_plugin
} // namespace proxy_wasm
#endif

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <string>
#include <unordered_set>
#include "common/route_rule_matcher.h"
#define ASSERT(_X) assert(_X)
#ifndef NULL_PLUGIN
#include "proxy_wasm_intrinsics.h"
#else
#include "include/proxy-wasm/null_plugin.h"
namespace proxy_wasm {
namespace null_plugin {
namespace model_mapper {
#endif
struct ModelMapperConfigRule {
std::string model_key_ = "model";
std::map<std::string, std::string> exact_model_mapping_;
std::vector<std::pair<std::string, std::string>> prefix_model_mapping_;
std::string default_model_mapping_;
std::vector<std::string> enable_on_path_suffix_ = {"/v1/chat/completions"};
};
// PluginRootContext is the root context for all streams processed by the
// thread. It has the same lifetime as the worker thread and acts as target for
// interactions that outlives individual stream, e.g. timer, async calls.
class PluginRootContext : public RootContext,
public RouteRuleMatcher<ModelMapperConfigRule> {
public:
PluginRootContext(uint32_t id, std::string_view root_id)
: RootContext(id, root_id) {}
~PluginRootContext() {}
bool onConfigure(size_t) override;
FilterHeadersStatus onHeader(const ModelMapperConfigRule&);
FilterDataStatus onBody(const ModelMapperConfigRule&, std::string_view);
bool configure(size_t);
private:
bool parsePluginConfig(const json&, ModelMapperConfigRule&) override;
};
// Per-stream context.
class PluginContext : public Context {
public:
explicit PluginContext(uint32_t id, RootContext* root) : Context(id, root) {}
FilterHeadersStatus onRequestHeaders(uint32_t, bool) override;
FilterDataStatus onRequestBody(size_t, bool) override;
private:
inline PluginRootContext* rootContext() {
return dynamic_cast<PluginRootContext*>(this->root());
}
size_t body_total_size_ = 0;
const ModelMapperConfigRule* config_ = nullptr;
};
#ifdef NULL_PLUGIN
} // namespace model_mapper
} // namespace null_plugin
} // namespace proxy_wasm
#endif

View File

@@ -0,0 +1,301 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "extensions/model_mapper/plugin.h"
#include <cstddef>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "include/proxy-wasm/context.h"
#include "include/proxy-wasm/null.h"
namespace proxy_wasm {
namespace null_plugin {
namespace model_mapper {
NullPluginRegistry* context_registry_;
RegisterNullVmPluginFactory register_model_mapper_plugin("model_mapper", []() {
return std::make_unique<NullPlugin>(model_mapper::context_registry_);
});
class MockContext : public proxy_wasm::ContextBase {
public:
MockContext(WasmBase* wasm) : ContextBase(wasm) {}
MOCK_METHOD(BufferInterface*, getBuffer, (WasmBufferType));
MOCK_METHOD(WasmResult, log, (uint32_t, std::string_view));
MOCK_METHOD(WasmResult, setBuffer,
(WasmBufferType, size_t, size_t, std::string_view));
MOCK_METHOD(WasmResult, getHeaderMapValue,
(WasmHeaderMapType /* type */, std::string_view /* key */,
std::string_view* /*result */));
MOCK_METHOD(WasmResult, replaceHeaderMapValue,
(WasmHeaderMapType /* type */, std::string_view /* key */,
std::string_view /* value */));
MOCK_METHOD(WasmResult, removeHeaderMapValue,
(WasmHeaderMapType /* type */, std::string_view /* key */));
MOCK_METHOD(WasmResult, addHeaderMapValue,
(WasmHeaderMapType, std::string_view, std::string_view));
MOCK_METHOD(WasmResult, getProperty, (std::string_view, std::string*));
MOCK_METHOD(WasmResult, setProperty, (std::string_view, std::string_view));
};
class ModelMapperTest : public ::testing::Test {
protected:
ModelMapperTest() {
// Initialize test VM
test_vm_ = createNullVm();
wasm_base_ = std::make_unique<WasmBase>(
std::move(test_vm_), "test-vm", "", "",
std::unordered_map<std::string, std::string>{},
AllowedCapabilitiesMap{});
wasm_base_->load("model_mapper");
wasm_base_->initialize();
// Initialize host side context
mock_context_ = std::make_unique<MockContext>(wasm_base_.get());
current_context_ = mock_context_.get();
// Initialize Wasm sandbox context
root_context_ = std::make_unique<PluginRootContext>(0, "");
context_ = std::make_unique<PluginContext>(1, root_context_.get());
ON_CALL(*mock_context_, log(testing::_, testing::_))
.WillByDefault([](uint32_t, std::string_view m) {
std::cerr << m << "\n";
return WasmResult::Ok;
});
ON_CALL(*mock_context_, getBuffer(testing::_))
.WillByDefault([&](WasmBufferType type) {
if (type == WasmBufferType::HttpRequestBody) {
return &body_;
}
return &config_;
});
ON_CALL(*mock_context_, getHeaderMapValue(WasmHeaderMapType::RequestHeaders,
testing::_, testing::_))
.WillByDefault([&](WasmHeaderMapType, std::string_view header,
std::string_view* result) {
if (header == "content-type") {
*result = "application/json";
} else if (header == "content-length") {
*result = "1024";
} else if (header == ":path") {
*result = path_;
}
return WasmResult::Ok;
});
ON_CALL(*mock_context_,
replaceHeaderMapValue(WasmHeaderMapType::RequestHeaders, testing::_,
testing::_))
.WillByDefault([&](WasmHeaderMapType, std::string_view key,
std::string_view value) { return WasmResult::Ok; });
ON_CALL(*mock_context_,
removeHeaderMapValue(WasmHeaderMapType::RequestHeaders, testing::_))
.WillByDefault([&](WasmHeaderMapType, std::string_view key) {
return WasmResult::Ok;
});
ON_CALL(*mock_context_, addHeaderMapValue(WasmHeaderMapType::RequestHeaders,
testing::_, testing::_))
.WillByDefault([&](WasmHeaderMapType, std::string_view header,
std::string_view value) { return WasmResult::Ok; });
ON_CALL(*mock_context_, getProperty(testing::_, testing::_))
.WillByDefault([&](std::string_view path, std::string* result) {
if (absl::StartsWith(path, "route_name")) {
*result = route_name_;
} else if (absl::StartsWith(path, "cluster_name")) {
*result = service_name_;
}
return WasmResult::Ok;
});
ON_CALL(*mock_context_, setProperty(testing::_, testing::_))
.WillByDefault(
[&](std::string_view, std::string_view) { return WasmResult::Ok; });
}
~ModelMapperTest() override {}
std::unique_ptr<WasmBase> wasm_base_;
std::unique_ptr<WasmVm> test_vm_;
std::unique_ptr<MockContext> mock_context_;
std::unique_ptr<PluginRootContext> root_context_;
std::unique_ptr<PluginContext> context_;
std::string route_name_;
std::string service_name_;
std::string path_;
BufferBase body_;
BufferBase config_;
};
TEST_F(ModelMapperTest, ModelMappingTest) {
std::string configuration = R"(
{
"modelMapping": {
"*": "qwen-long",
"gpt-4*": "qwen-max",
"gpt-4o": "qwen-turbo",
"gpt-4o-mini": "qwen-plus",
"text-embedding-v1": ""
}
})";
config_.set(configuration);
EXPECT_TRUE(root_context_->configure(configuration.size()));
path_ = "/v1/chat/completions";
std::string request_json = R"({"model": "gpt-3.5"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-long"})");
return WasmResult::Ok;
});
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
request_json = R"({"model": "gpt-4"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-max"})");
return WasmResult::Ok;
});
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
request_json = R"({"model": "gpt-4o"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-turbo"})");
return WasmResult::Ok;
});
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
request_json = R"({"model": "gpt-4o-mini"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-plus"})");
return WasmResult::Ok;
});
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
request_json = R"({"model": "text-embedding-v1"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.Times(0);
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
request_json = R"({})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-long"})");
return WasmResult::Ok;
});
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
}
TEST_F(ModelMapperTest, RouteLevelModelMappingTest) {
std::string configuration = R"(
{
"_rules_": [
{
"_match_route_": ["route-a"],
"_match_service_": ["service-1"],
"modelMapping": {
"*": "qwen-long"
}
},
{
"_match_route_": ["route-b"],
"_match_service_": ["service-2"],
"modelMapping": {
"*": "qwen-max"
}
},
{
"_match_route_": ["route-b"],
"_match_service_": ["service-3"],
"modelMapping": {
"*": "qwen-turbo"
}
}
]})";
config_.set(configuration);
EXPECT_TRUE(root_context_->configure(configuration.size()));
path_ = "/api/v1/chat/completions";
std::string request_json = R"({"model": "gpt-4"})";
body_.set(request_json);
route_name_ = "route-a";
service_name_ = "outbound|80||service-1";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-long"})");
return WasmResult::Ok;
});
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
route_name_ = "route-b";
service_name_ = "outbound|80||service-2";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-max"})");
return WasmResult::Ok;
});
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
route_name_ = "route-b";
service_name_ = "outbound|80||service-3";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-turbo"})");
return WasmResult::Ok;
});
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(20, true), FilterDataStatus::Continue);
}
} // namespace model_mapper
} // namespace null_plugin
} // namespace proxy_wasm

View File

@@ -0,0 +1,70 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
proxy_wasm_cc_binary(
name = "model_router.wasm",
srcs = [
"plugin.cc",
"plugin.h",
],
deps = [
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_higress",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"//common:json_util",
"//common:http_util",
"//common:rule_util",
],
)
cc_library(
name = "model_router_lib",
srcs = [
"plugin.cc",
],
hdrs = [
"plugin.h",
],
copts = ["-DNULL_PLUGIN"],
deps = [
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"//common:json_util",
"@proxy_wasm_cpp_host//:lib",
"//common:http_util_nullvm",
"//common:rule_util_nullvm",
],
)
cc_test(
name = "model_router_test",
srcs = [
"plugin_test.cc",
],
copts = ["-DNULL_PLUGIN"],
deps = [
":model_router_lib",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
"@proxy_wasm_cpp_host//:lib",
],
)
declare_wasm_image_targets(
name = "model_router",
wasm_file = ":model_router.wasm",
)

View File

@@ -0,0 +1,98 @@
# 功能说明
`model-router`插件实现了基于LLM协议中的model参数路由的功能
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- |
| `modelKey` | string | 选填 | model | 请求body中model参数的位置 |
| `addProviderHeader` | string | 选填 | - | 从model参数中解析出的provider名字放到哪个请求header中 |
| `modelToHeader` | string | 选填 | - | 直接将model参数放到哪个请求header中 |
| `enableOnPathSuffix` | array of string | 选填 | ["/v1/chat/completions"] | 只对这些特定路径后缀的请求生效 |
## 运行属性
插件执行阶段:认证阶段
插件执行优先级900
## 效果说明
### 基于 model 参数进行路由
需要做如下配置:
```yaml
modelToHeader: x-higress-llm-model
```
插件会将请求中 model 参数提取出来,设置到 x-higress-llm-model 这个请求 header 中,用于后续路由,举例来说,原生的 LLM 请求体是:
```json
{
"model": "qwen-long",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "higress项目主仓库的github地址是什么"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```
经过这个插件后,将添加下面这个请求头(可以用于路由匹配)
x-higress-llm-model: qwen-long
### 提取 model 参数中的 provider 字段用于路由
> 注意这种模式需要客户端在 model 参数中通过`/`分隔的方式,来指定 provider
需要做如下配置:
```yaml
addProviderHeader: x-higress-llm-provider
```
插件会将请求中 model 参数的 provider 部分(如果有)提取出来,设置到 x-higress-llm-provider 这个请求 header 中,用于后续路由,并将 model 参数重写为模型名称部分。举例来说,原生的 LLM 请求体是:
```json
{
"model": "dashscope/qwen-long",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "higress项目主仓库的github地址是什么"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```
经过这个插件后,将添加下面这个请求头(可以用于路由匹配)
x-higress-llm-provider: dashscope
原始的 LLM 请求体将被改成:
```json
{
"model": "qwen-long",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "higress项目主仓库的github地址是什么"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```

View File

@@ -0,0 +1,97 @@
## Function Description
The `model-router` plugin implements the function of routing based on the model parameter in the LLM protocol.
## Configuration Fields
| Name | Data Type | Filling Requirement | Default Value | Description |
| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- |
| `modelKey` | string | Optional | model | The location of the model parameter in the request body |
| `addProviderHeader` | string | Optional | - | Which request header to place the provider name parsed from the model parameter |
| `modelToHeader` | string | Optional | - | Which request header to directly place the model parameter |
| `enableOnPathSuffix` | array of string | Optional | ["/v1/chat/completions"] | Only effective for requests with these specific path suffixes |
## Runtime Attributes
Plugin execution phase: Authentication phase
Plugin execution priority: 900
## Effect Description
### Routing Based on the model Parameter
The following configuration is required:
```yaml
modelToHeader: x-higress-llm-model
```
The plugin will extract the model parameter from the request and set it in the x-higress-llm-model request header, which can be used for subsequent routing. For example, the original LLM request body:
```json
{
"model": "qwen-long",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "What is the GitHub address of the main repository for the higress project"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```
After processing by this plugin, the following request header (which can be used for route matching) will be added:
x-higress-llm-model: qwen-long
### Extracting the provider Field from the model Parameter for Routing
> Note that this mode requires the client to specify the provider using a `/` separator in the model parameter.
The following configuration is required:
```yaml
addProviderHeader: x-higress-llm-provider
```
The plugin will extract the provider part (if present) from the model parameter in the request and set it in the x-higress-llm-provider request header, which can be used for subsequent routing, and rewrite the model parameter to the model name part. For example, the original LLM request body:
```json
{
"model": "dashscope/qwen-long",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "What is the GitHub address of the main repository for the higress project"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}
```
After processing by this plugin, the following request header (which can be used for route matching) will be added:
x-higress-llm-provider: dashscope
The original LLM request body will be changed to:
```json
{
"model": "qwen-long",
"frequency_penalty": 0,
"max_tokens": 800,
"stream": false,
"messages": [{
"role": "user",
"content": "What is the GitHub address of the main repository for the higress project"
}],
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 0.95
}

View File

@@ -0,0 +1,227 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "extensions/model_router/plugin.h"
#include <array>
#include <limits>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "common/http_util.h"
#include "common/json_util.h"
using ::nlohmann::json;
using ::Wasm::Common::JsonArrayIterate;
using ::Wasm::Common::JsonGetField;
using ::Wasm::Common::JsonObjectIterate;
using ::Wasm::Common::JsonValueAs;
#ifdef NULL_PLUGIN
namespace proxy_wasm {
namespace null_plugin {
namespace model_router {
PROXY_WASM_NULL_PLUGIN_REGISTRY
#endif
static RegisterContextFactory register_ModelRouter(
CONTEXT_FACTORY(PluginContext), ROOT_FACTORY(PluginRootContext));
namespace {
constexpr std::string_view SetDecoderBufferLimitKey =
"SetRequestBodyBufferLimit";
constexpr std::string_view DefaultMaxBodyBytes = "10485760";
} // namespace
bool PluginRootContext::parsePluginConfig(const json& configuration,
ModelRouterConfigRule& rule) {
if (auto it = configuration.find("modelKey"); it != configuration.end()) {
if (it->is_string()) {
rule.model_key_ = it->get<std::string>();
} else {
LOG_ERROR("Invalid type for modelKey. Expected string.");
return false;
}
}
if (auto it = configuration.find("addProviderHeader");
it != configuration.end()) {
if (it->is_string()) {
rule.add_provider_header_ = it->get<std::string>();
} else {
LOG_ERROR("Invalid type for addProviderHeader. Expected string.");
return false;
}
}
if (auto it = configuration.find("modelToHeader");
it != configuration.end()) {
if (it->is_string()) {
rule.model_to_header_ = it->get<std::string>();
} else {
LOG_ERROR("Invalid type for modelToHeader. Expected string.");
return false;
}
}
if (!JsonArrayIterate(
configuration, "enableOnPathSuffix", [&](const json& item) -> bool {
if (item.is_string()) {
rule.enable_on_path_suffix_.emplace_back(item.get<std::string>());
return true;
}
return false;
})) {
LOG_ERROR("Invalid type for item in enableOnPathSuffix. Expected string.");
return false;
}
return true;
}
bool PluginRootContext::onConfigure(size_t size) {
// Parse configuration JSON string.
if (size > 0 && !configure(size)) {
LOG_ERROR("configuration has errors initialization will not continue.");
return false;
}
return true;
}
bool PluginRootContext::configure(size_t configuration_size) {
auto configuration_data = getBufferBytes(WasmBufferType::PluginConfiguration,
0, configuration_size);
// Parse configuration JSON string.
auto result = ::Wasm::Common::JsonParse(configuration_data->view());
if (!result) {
LOG_ERROR(absl::StrCat("cannot parse plugin configuration JSON string: ",
configuration_data->view()));
return false;
}
if (!parseRuleConfig(result.value())) {
LOG_ERROR(absl::StrCat("cannot parse plugin configuration JSON string: ",
configuration_data->view()));
return false;
}
return true;
}
FilterHeadersStatus PluginRootContext::onHeader(
const ModelRouterConfigRule& rule) {
if (!Wasm::Common::Http::hasRequestBody()) {
return FilterHeadersStatus::Continue;
}
auto path = getRequestHeader(Wasm::Common::Http::Header::Path)->toString();
auto params_pos = path.find('?');
size_t uri_end;
if (params_pos == std::string::npos) {
uri_end = path.size();
} else {
uri_end = params_pos;
}
bool enable = false;
for (const auto& enable_suffix : rule.enable_on_path_suffix_) {
if (absl::EndsWith({path.c_str(), uri_end}, enable_suffix)) {
enable = true;
break;
}
}
if (!enable) {
return FilterHeadersStatus::Continue;
}
auto content_type_value =
getRequestHeader(Wasm::Common::Http::Header::ContentType);
if (!absl::StrContains(content_type_value->view(),
Wasm::Common::Http::ContentTypeValues::Json)) {
return FilterHeadersStatus::Continue;
}
removeRequestHeader(Wasm::Common::Http::Header::ContentLength);
setFilterState(SetDecoderBufferLimitKey, DefaultMaxBodyBytes);
return FilterHeadersStatus::StopIteration;
}
FilterDataStatus PluginRootContext::onBody(const ModelRouterConfigRule& rule,
std::string_view body) {
const auto& model_key = rule.model_key_;
const auto& add_provider_header = rule.add_provider_header_;
const auto& model_to_header = rule.model_to_header_;
auto body_json_opt = ::Wasm::Common::JsonParse(body);
if (!body_json_opt) {
LOG_WARN(absl::StrCat("cannot parse body to JSON string: ", body));
return FilterDataStatus::Continue;
}
auto body_json = body_json_opt.value();
if (body_json.contains(model_key)) {
std::string model_value = body_json[model_key];
if (!model_to_header.empty()) {
replaceRequestHeader(model_to_header, model_value);
}
if (!add_provider_header.empty()) {
auto pos = model_value.find('/');
if (pos != std::string::npos) {
const auto& provider = model_value.substr(0, pos);
const auto& model = model_value.substr(pos + 1);
replaceRequestHeader(add_provider_header, provider);
body_json[model_key] = model;
setBuffer(WasmBufferType::HttpRequestBody, 0,
std::numeric_limits<size_t>::max(), body_json.dump());
LOG_DEBUG(absl::StrCat("model route to provider:", provider,
", model:", model));
} else {
LOG_DEBUG(absl::StrCat("model route to provider not work, model:",
model_value));
}
}
}
return FilterDataStatus::Continue;
}
FilterHeadersStatus PluginContext::onRequestHeaders(uint32_t, bool) {
auto* rootCtx = rootContext();
return rootCtx->onHeaders([rootCtx, this](const auto& config) {
auto ret = rootCtx->onHeader(config);
if (ret == FilterHeadersStatus::StopIteration) {
this->config_ = &config;
}
return ret;
});
}
FilterDataStatus PluginContext::onRequestBody(size_t body_size,
bool end_stream) {
if (config_ == nullptr) {
return FilterDataStatus::Continue;
}
body_total_size_ += body_size;
if (!end_stream) {
return FilterDataStatus::StopIterationAndBuffer;
}
auto body =
getBufferBytes(WasmBufferType::HttpRequestBody, 0, body_total_size_);
auto* rootCtx = rootContext();
return rootCtx->onBody(*config_, body->view());
}
#ifdef NULL_PLUGIN
} // namespace model_router
} // namespace null_plugin
} // namespace proxy_wasm
#endif

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2022 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <string>
#include <unordered_set>
#include "common/route_rule_matcher.h"
#define ASSERT(_X) assert(_X)
#ifndef NULL_PLUGIN
#include "proxy_wasm_intrinsics.h"
#else
#include "include/proxy-wasm/null_plugin.h"
namespace proxy_wasm {
namespace null_plugin {
namespace model_router {
#endif
struct ModelRouterConfigRule {
std::string model_key_ = "model";
std::string add_provider_header_;
std::string model_to_header_;
std::vector<std::string> enable_on_path_suffix_ = {"/v1/chat/completions"};
};
// PluginRootContext is the root context for all streams processed by the
// thread. It has the same lifetime as the worker thread and acts as target for
// interactions that outlives individual stream, e.g. timer, async calls.
class PluginRootContext : public RootContext,
public RouteRuleMatcher<ModelRouterConfigRule> {
public:
PluginRootContext(uint32_t id, std::string_view root_id)
: RootContext(id, root_id) {}
~PluginRootContext() {}
bool onConfigure(size_t) override;
FilterHeadersStatus onHeader(const ModelRouterConfigRule&);
FilterDataStatus onBody(const ModelRouterConfigRule&, std::string_view);
bool configure(size_t);
private:
bool parsePluginConfig(const json&, ModelRouterConfigRule&) override;
};
// Per-stream context.
class PluginContext : public Context {
public:
explicit PluginContext(uint32_t id, RootContext* root) : Context(id, root) {}
FilterHeadersStatus onRequestHeaders(uint32_t, bool) override;
FilterDataStatus onRequestBody(size_t, bool) override;
private:
inline PluginRootContext* rootContext() {
return dynamic_cast<PluginRootContext*>(this->root());
}
size_t body_total_size_ = 0;
const ModelRouterConfigRule* config_ = nullptr;
};
#ifdef NULL_PLUGIN
} // namespace model_router
} // namespace null_plugin
} // namespace proxy_wasm
#endif

View File

@@ -0,0 +1,250 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "extensions/model_router/plugin.h"
#include <cstddef>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "include/proxy-wasm/context.h"
#include "include/proxy-wasm/null.h"
namespace proxy_wasm {
namespace null_plugin {
namespace model_router {
NullPluginRegistry* context_registry_;
RegisterNullVmPluginFactory register_model_router_plugin("model_router", []() {
return std::make_unique<NullPlugin>(model_router::context_registry_);
});
class MockContext : public proxy_wasm::ContextBase {
public:
MockContext(WasmBase* wasm) : ContextBase(wasm) {}
MOCK_METHOD(BufferInterface*, getBuffer, (WasmBufferType));
MOCK_METHOD(WasmResult, log, (uint32_t, std::string_view));
MOCK_METHOD(WasmResult, setBuffer,
(WasmBufferType, size_t, size_t, std::string_view));
MOCK_METHOD(WasmResult, getHeaderMapValue,
(WasmHeaderMapType /* type */, std::string_view /* key */,
std::string_view* /*result */));
MOCK_METHOD(WasmResult, replaceHeaderMapValue,
(WasmHeaderMapType /* type */, std::string_view /* key */,
std::string_view /* value */));
MOCK_METHOD(WasmResult, removeHeaderMapValue,
(WasmHeaderMapType /* type */, std::string_view /* key */));
MOCK_METHOD(WasmResult, addHeaderMapValue,
(WasmHeaderMapType, std::string_view, std::string_view));
MOCK_METHOD(WasmResult, getProperty, (std::string_view, std::string*));
MOCK_METHOD(WasmResult, setProperty, (std::string_view, std::string_view));
};
class ModelRouterTest : public ::testing::Test {
protected:
ModelRouterTest() {
// Initialize test VM
test_vm_ = createNullVm();
wasm_base_ = std::make_unique<WasmBase>(
std::move(test_vm_), "test-vm", "", "",
std::unordered_map<std::string, std::string>{},
AllowedCapabilitiesMap{});
wasm_base_->load("model_router");
wasm_base_->initialize();
// Initialize host side context
mock_context_ = std::make_unique<MockContext>(wasm_base_.get());
current_context_ = mock_context_.get();
// Initialize Wasm sandbox context
root_context_ = std::make_unique<PluginRootContext>(0, "");
context_ = std::make_unique<PluginContext>(1, root_context_.get());
ON_CALL(*mock_context_, log(testing::_, testing::_))
.WillByDefault([](uint32_t, std::string_view m) {
std::cerr << m << "\n";
return WasmResult::Ok;
});
ON_CALL(*mock_context_, getBuffer(testing::_))
.WillByDefault([&](WasmBufferType type) {
if (type == WasmBufferType::HttpRequestBody) {
return &body_;
}
return &config_;
});
ON_CALL(*mock_context_, getHeaderMapValue(WasmHeaderMapType::RequestHeaders,
testing::_, testing::_))
.WillByDefault([&](WasmHeaderMapType, std::string_view header,
std::string_view* result) {
if (header == "content-type") {
*result = "application/json";
} else if (header == "content-length") {
*result = "1024";
} else if (header == ":path") {
*result = path_;
}
return WasmResult::Ok;
});
ON_CALL(*mock_context_,
replaceHeaderMapValue(WasmHeaderMapType::RequestHeaders, testing::_,
testing::_))
.WillByDefault([&](WasmHeaderMapType, std::string_view key,
std::string_view value) { return WasmResult::Ok; });
ON_CALL(*mock_context_,
removeHeaderMapValue(WasmHeaderMapType::RequestHeaders, testing::_))
.WillByDefault([&](WasmHeaderMapType, std::string_view key) {
return WasmResult::Ok;
});
ON_CALL(*mock_context_, addHeaderMapValue(WasmHeaderMapType::RequestHeaders,
testing::_, testing::_))
.WillByDefault([&](WasmHeaderMapType, std::string_view header,
std::string_view value) { return WasmResult::Ok; });
ON_CALL(*mock_context_, getProperty(testing::_, testing::_))
.WillByDefault([&](std::string_view path, std::string* result) {
*result = route_name_;
return WasmResult::Ok;
});
ON_CALL(*mock_context_, setProperty(testing::_, testing::_))
.WillByDefault(
[&](std::string_view, std::string_view) { return WasmResult::Ok; });
}
~ModelRouterTest() override {}
std::unique_ptr<WasmBase> wasm_base_;
std::unique_ptr<WasmVm> test_vm_;
std::unique_ptr<MockContext> mock_context_;
std::unique_ptr<PluginRootContext> root_context_;
std::unique_ptr<PluginContext> context_;
std::string route_name_;
std::string path_;
BufferBase body_;
BufferBase config_;
};
TEST_F(ModelRouterTest, RewriteModelAndHeader) {
std::string configuration = R"(
{
"addProviderHeader": "x-higress-llm-provider"
})";
config_.set(configuration);
EXPECT_TRUE(root_context_->configure(configuration.size()));
path_ = "/v1/chat/completions";
std::string request_json = R"({"model": "qwen/qwen-long"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-long"})");
return WasmResult::Ok;
});
EXPECT_CALL(*mock_context_,
replaceHeaderMapValue(testing::_,
std::string_view("x-higress-llm-provider"),
std::string_view("qwen")));
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(28, true), FilterDataStatus::Continue);
}
TEST_F(ModelRouterTest, ModelToHeader) {
std::string configuration = R"(
{
"modelToHeader": "x-higress-llm-model"
})";
config_.set(configuration);
EXPECT_TRUE(root_context_->configure(configuration.size()));
path_ = "/v1/chat/completions";
std::string request_json = R"({"model": "qwen-long"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.Times(0);
EXPECT_CALL(
*mock_context_,
replaceHeaderMapValue(testing::_, std::string_view("x-higress-llm-model"),
std::string_view("qwen-long")));
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(28, true), FilterDataStatus::Continue);
}
TEST_F(ModelRouterTest, IgnorePath) {
std::string configuration = R"(
{
"addProviderHeader": "x-higress-llm-provider"
})";
config_.set(configuration);
EXPECT_TRUE(root_context_->configure(configuration.size()));
path_ = "/v1/chat/xxxx";
std::string request_json = R"({"model": "qwen/qwen-long"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.Times(0);
EXPECT_CALL(*mock_context_,
replaceHeaderMapValue(testing::_,
std::string_view("x-higress-llm-provider"),
std::string_view("qwen")))
.Times(0);
body_.set(request_json);
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
EXPECT_EQ(context_->onRequestBody(28, true), FilterDataStatus::Continue);
}
TEST_F(ModelRouterTest, RouteLevelRewriteModelAndHeader) {
std::string configuration = R"(
{
"_rules_": [
{
"_match_route_": ["route-a"],
"addProviderHeader": "x-higress-llm-provider"
}
]})";
config_.set(configuration);
EXPECT_TRUE(root_context_->configure(configuration.size()));
path_ = "/api/v1/chat/completions";
std::string request_json = R"({"model": "qwen/qwen-long"})";
EXPECT_CALL(*mock_context_,
setBuffer(testing::_, testing::_, testing::_, testing::_))
.WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) {
EXPECT_EQ(body, R"({"model":"qwen-long"})");
return WasmResult::Ok;
});
EXPECT_CALL(*mock_context_,
replaceHeaderMapValue(testing::_,
std::string_view("x-higress-llm-provider"),
std::string_view("qwen")));
body_.set(request_json);
route_name_ = "route-a";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
EXPECT_EQ(context_->onRequestBody(28, true), FilterDataStatus::Continue);
}
} // namespace model_router
} // namespace null_plugin
} // namespace proxy_wasm

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
wasm_cc_binary(
proxy_wasm_cc_binary(
name = "request_block.wasm",
srcs = [
"plugin.cc",
@@ -26,8 +26,8 @@ wasm_cc_binary(
"@com_google_absl//absl/time",
"//common:json_util",
"//common:http_util",
"//common:regex_util",
"//common:rule_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)
@@ -45,6 +45,7 @@ cc_library(
"//common:json_util",
"@proxy_wasm_cpp_host//:lib",
"//common:http_util_nullvm",
"//common:regex_util",
"//common:rule_util_nullvm",
],
)

View File

@@ -1,31 +1,22 @@
---
title: 请求屏蔽
keywords: [higress,request block]
description: 请求屏蔽插件配置参考
---
## 功能说明
# 功能说明
`request-block`插件实现了基于 URL、请求头等特征屏蔽 HTTP 请求,可以用于防护部分站点资源不对外部暴露
## 运行属性
# 配置字段
插件执行阶段:`鉴权阶段`
插件执行优先级:`320`
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| block_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
| block_exact_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要精确屏蔽 URL 的字符串 |
| block_regexp_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的正则表达式 |
| block_headers | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
| block_bodies | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
## 配置字段
# 配置示例
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| block_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
| block_headers | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
| block_bodies | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
## 配置示例
### 屏蔽请求 url 路径
## 屏蔽请求 url 路径
```yaml
block_urls:
- swagger.html
@@ -40,7 +31,36 @@ curl http://example.com?foo=Bar
curl http://exmaple.com/Swagger.html
```
### 屏蔽请求 header
## 屏蔽精确匹配的请求 url 路径
```yaml
block_exact_urls:
- /swagger.html?foo=bar
case_sensitive: false
```
根据该配置,下列请求将被禁止访问:
```bash
curl http://exmaple.com/Swagger.html?foo=Bar
```
## 屏蔽正则匹配的请求 url 路径
```yaml
block_exact_urls:
- .*swagger.*
case_sensitive: false
```
根据该配置,下列请求将被禁止访问:
```bash
curl http://exmaple.com/Swagger.html?foo=Bar
```
## 屏蔽请求 header
```yaml
block_headers:
- example-key
@@ -54,9 +74,9 @@ curl http://example.com -H 'example-key: 123'
curl http://exmaple.com -H 'my-header: example-value'
```
### 屏蔽请求 body
## 屏蔽请求 body
```yaml
block_bodies:
block_bodys:
- "hello world"
case_sensitive: false
```
@@ -68,8 +88,30 @@ curl http://example.com -d 'Hello World'
curl http://exmaple.com -d 'hello world'
```
## 对特定路由或域名开启
```yaml
# 使用 _rules_ 字段进行细粒度规则配置
_rules_:
# 规则一:按路由名称匹配生效
- _match_route_:
- route-a
- route-b
block_bodys:
- "hello world"
# 规则二:按域名匹配生效
- _match_domain_:
- "*.example.com"
- test.com
block_urls:
- "swagger.html"
block_bodys:
- "hello world"
```
此例 `_match_route_` 中指定的 `route-a``route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
## 请求 Body 大小限制
# 请求 Body 大小限制
当配置了 `block_bodies` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls``block_headers` 项时,不会对该请求执行屏蔽操作
当配置了 `block_bodies` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits将返回 `413 Payload Too Large`
当配置了 `block_bodys` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls``block_headers` 项时,不会对该请求执行屏蔽操作
当配置了 `block_bodys` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits将返回 `413 Payload Too Large`, 请在参数配置页调高此项。注意调高此参数配置后,网关内存使用将有显著增加。

View File

@@ -15,6 +15,7 @@
#include "extensions/request_block/plugin.h"
#include <array>
#include <memory>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
@@ -89,6 +90,48 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
LOG_WARN("failed to parse configuration for block_urls.");
return false;
}
if (!JsonArrayIterate(
configuration, "block_exact_urls", [&](const json& item) -> bool {
auto url = JsonValueAs<std::string>(item);
if (url.second != Wasm::Common::JsonParserResultDetail::OK) {
LOG_WARN("cannot parse block_exact_urls");
return false;
}
if (rule.case_sensitive) {
rule.block_exact_urls.push_back(std::move(url.first.value()));
} else {
rule.block_exact_urls.push_back(
absl::AsciiStrToLower(url.first.value()));
}
return true;
})) {
LOG_WARN("failed to parse configuration for block_exact_urls.");
return false;
}
if (!JsonArrayIterate(
configuration, "block_regexp_urls", [&](const json& item) -> bool {
auto url = JsonValueAs<std::string>(item);
if (url.second != Wasm::Common::JsonParserResultDetail::OK) {
LOG_WARN("cannot parse block_regexp_urls");
return false;
}
std::string regex;
if (rule.case_sensitive) {
regex = url.first.value();
} else {
regex = absl::AsciiStrToLower(url.first.value());
}
auto re = std::make_unique<ReMatcher>(regex);
if (!re->error().empty()) {
LOG_WARN(re->error());
return false;
}
rule.block_regexp_urls.push_back(std::move(re));
return true;
})) {
LOG_WARN("failed to parse configuration for block_regexp_urls.");
return false;
}
if (!JsonArrayIterate(
configuration, "block_headers", [&](const json& item) -> bool {
auto header = JsonValueAs<std::string>(item);
@@ -125,8 +168,28 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
LOG_WARN("failed to parse configuration for block_bodys.");
return false;
}
// compatiable
if (!JsonArrayIterate(
configuration, "block_bodies", [&](const json& item) -> bool {
auto body = JsonValueAs<std::string>(item);
if (body.second != Wasm::Common::JsonParserResultDetail::OK) {
LOG_WARN("cannot parse block_bodies");
return false;
}
if (rule.case_sensitive) {
rule.block_bodys.push_back(std::move(body.first.value()));
} else {
rule.block_bodys.push_back(
absl::AsciiStrToLower(body.first.value()));
}
return true;
})) {
LOG_WARN("failed to parse configuration for block_bodies.");
return false;
}
if (rule.block_bodys.empty() && rule.block_headers.empty() &&
rule.block_urls.empty()) {
rule.block_urls.empty() && rule.block_exact_urls.empty() &&
rule.block_regexp_urls.empty()) {
LOG_WARN("there is no block rules");
return false;
}
@@ -172,6 +235,18 @@ bool PluginRootContext::checkHeader(const RequestBlockConfigRule& rule,
urlstr = absl::AsciiStrToLower(request_url);
url = urlstr;
}
for (const auto& block_url : rule.block_exact_urls) {
if (url == block_url) {
sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {});
return false;
}
}
for (const auto& block_url : rule.block_regexp_urls) {
if (block_url->match(url)) {
sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {});
return false;
}
}
for (const auto& block_url : rule.block_urls) {
if (absl::StrContains(url, block_url)) {
sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {});

View File

@@ -22,6 +22,7 @@
#include <unordered_map>
#include "common/http_util.h"
#include "common/regex.h"
#include "common/route_rule_matcher.h"
#define ASSERT(_X) assert(_X)
@@ -39,11 +40,16 @@ namespace request_block {
#endif
using ReMatcher = Wasm::Common::Regex::CompiledGoogleReMatcher;
using ReMatcherPtr = std::unique_ptr<ReMatcher>;
struct RequestBlockConfigRule {
int blocked_code = 403;
std::string blocked_message;
bool case_sensitive = true;
std::vector<std::string> block_urls;
std::vector<std::string> block_exact_urls;
std::vector<ReMatcherPtr> block_regexp_urls;
std::vector<std::string> block_headers;
std::vector<std::string> block_bodys;
};

View File

@@ -127,6 +127,8 @@ TEST_F(RequestBlockTest, CaseSensitive) {
std::string configuration = R"(
{
"block_urls": ["?foo=bar", "swagger.html"],
"block_exact_urls": ["/hello.html?abc=123"],
"block_regexp_urls": [".*monkey.*"],
"block_headers": ["headerKey", "headerValue"],
"block_bodys": ["Hello World"]
})";
@@ -150,6 +152,22 @@ TEST_F(RequestBlockTest, CaseSensitive) {
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
path_ = "/hello.html?abc=123";
EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
path_ = "/black/Monkey";
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
path_ = "/black/monkey";
EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
path_ = "";
headers_ = {{"headerKey", "123"}};
EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_,
@@ -188,6 +206,8 @@ TEST_F(RequestBlockTest, CaseInsensitive) {
"blocked_code": 404,
"block_urls": ["?foo=bar", "swagger.html"],
"block_headers": ["headerKey", "headerValue"],
"block_exact_urls": ["/hello.html?abc=123"],
"block_regexp_urls": [".*monkey.*"],
"block_bodys": ["Hello World"]
})";
@@ -206,6 +226,24 @@ TEST_F(RequestBlockTest, CaseInsensitive) {
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
path_ = "/Hello.html?abc=123";
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
path_ = "/black/Monkey";
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
path_ = "/black/monkey";
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
path_ = "";
headers_ = {{"headerkey", "123"}};
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
@@ -232,6 +270,26 @@ TEST_F(RequestBlockTest, CaseInsensitive) {
FilterDataStatus::StopIterationNoBuffer);
}
TEST_F(RequestBlockTest, Bodies) {
std::string configuration = R"(
{
"case_sensitive": false,
"blocked_code": 404,
"block_bodies": ["Hello World"]
})";
config_.set({configuration.data(), configuration.size()});
EXPECT_TRUE(root_context_->configure(configuration.size()));
body_.set("hello world");
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestBody(11, true),
FilterDataStatus::StopIterationNoBuffer);
}
} // namespace request_block
} // namespace null_plugin
} // namespace proxy_wasm

View File

@@ -12,10 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
wasm_cc_binary(
proxy_wasm_cc_binary(
name = "sni_misdirect.wasm",
srcs = [
"plugin.cc",
@@ -25,7 +25,6 @@ wasm_cc_binary(
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/strings",
"//common:http_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)

View File

@@ -55,6 +55,9 @@ output wasm file: extensions/request-block/plugin.wasm
tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' ./extensions/request-block/main.go
```
详细的编译说明,包括要使用更复杂的 Header 状态管理机制,请参考[ Go 开发插件的最佳实践](https://higress.io/docs/latest/user/wasm-go/#3-%E7%BC%96%E8%AF%91%E7%94%9F%E6%88%90-wasm-%E6%96%87%E4%BB%B6)。
### step2. 构建并推送插件的 docker 镜像
使用这份简单的 Dockerfile
@@ -201,4 +204,4 @@ cSuite.Setup(t)
```bash
PLUGIN_NAME=request-block make higress-wasmplugin-test
```
```

View File

@@ -0,0 +1,68 @@
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
log_format:
text_format_source:
inline_string: "{\"custom_log\":\"%FILTER_STATE(wasm.custom_log:PLAIN)%\",\"ai_log\":\"%FILTER_STATE(wasm.ai_log:PLAIN)%\"}
"
path: /dev/stdout
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- name: get
match:
prefix: "/get"
route:
cluster: httpbin
http_filters:
- name: test
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: test
vm_config:
runtime: envoy.wasm.runtime.v8
code:
local:
filename: main.wasm
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: {}
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: httpbin
connect_timeout: 600s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: httpbin
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: httpbin.org
port_value: 80

View File

@@ -0,0 +1,20 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/custom-logs
go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0
github.com/higress-group/proxy-wasm-go-sdk v1.0.0
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/tidwall/gjson v1.17.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/resp v0.1.1 // indirect
)

View File

@@ -0,0 +1,20 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v1.0.0 h1:BZRNf4R7jr9hwRivg/E29nkVaKEak5MWjBDhWjuHijU=
github.com/higress-group/proxy-wasm-go-sdk v1.0.0/go.mod h1:iiSyFbo+rAtbtGt/bsefv8GU57h9CCLYGJA74/tF5/0=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -0,0 +1,67 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"math/rand"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
)
func main() {
wrapper.SetCtx(
"custom-log",
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type CustomLogConfig struct {
}
// Method 1: write custom log
func writeLog(ctx wrapper.HttpContext) {
ctx.SetUserAttribute("question", "当然可以。在Python中你可以创建一个函数来计算一系列数字的和。下面是一个简单的例子该函数接受一个数字列表作为输入并返回它们的总和。\n\n```python\ndef sum_of_numbers(numbers):\n \"\"\"\n 计算列表中所有数字的和。\n \n 参数:\n numbers (list of int or float): 一个包含数字的列表。\n \n 返回:\n int or float: 列表中所有数字的总和。\n \"\"\"\n total_sum = sum(numbers) # 使用Python内置的sum函数计算总和\n return total_sum\n\n# 示例使用\nnumbers_list = [1, 2, 3, 4, 5]\nprint(\"The sum is:\", sum_of_numbers(numbers_list)) # 输出The sum is: 15\n```\n\n在这段代码中我们定义了一个名为 `sum_of_numbers` 的函数,它接收一个参数 `numbers`这是一个包含整数或浮点数的列表。函数内部使用了Python的内置函数 `sum()` 来计算这些数字的总和,并将结果返回。\n\n你也可以手动实现求和逻辑而不是使用内置的 `sum()` 函数,如下所示:\n\n```python\ndef sum_of_numbers_manual(numbers):\n \"\"\"\n 手动计算列表中所有数字的和。\n \n 参数:\n numbers (list of int or float): 一个包含数字的列表。\n \n 返回:\n int or float: 列表中所有数字的总和。\n \"\"\"\n total_sum = 0\n for number in numbers:\n total_sum += number\n return total_sum\n\n# 示例使用\nnumbers_list = [1, 2, 3, 4, 5]\nprint(\"The sum is:\", sum_of_numbers_manual(numbers_list)) # 输出The sum is: 15\n```\n\n在这个版本中我们初始化 `total_sum` 为0然后遍历列表中的每个元素并将其加到 `total_sum` 上。最后返回这个累加的结果。这两种方法都可以达到相同的目的,但是使用内置函数通常更简洁且效率更高。")
ctx.SetUserAttribute("k2", 2213.22)
ctx.WriteUserAttributeToLog()
}
// Methods 2: write custom log with specific key
func writeLogWithKey(ctx wrapper.HttpContext, key string) {
ctx.SetUserAttribute("k2", 2213.22)
_ = ctx.WriteUserAttributeToLogWithKey(key)
ctx.SetUserAttribute("k2", 212939.22)
ctx.SetUserAttribute("k3", 123)
_ = ctx.WriteUserAttributeToLogWithKey(key)
}
// Methods 2: write custom log with specific key
func writeTraceAttribute(ctx wrapper.HttpContext) {
ctx.SetUserAttribute("question", "当然可以。在Python中你可以创建一个函数来计算一系列数字的和。下面是一个简单的例子该函数接受一个数字列表作为输入并返回它们的总和。\n\n```python\ndef sum_of_numbers(numbers):\n \"\"\"\n 计算列表中所有数字的和。\n \n 参数:\n numbers (list of int or float): 一个包含数字的列表。\n \n 返回:\n int or float: 列表中所有数字的总和。\n \"\"\"\n total_sum = sum(numbers) # 使用Python内置的sum函数计算总和\n return total_sum\n\n# 示例使用\nnumbers_list = [1, 2, 3, 4, 5]\nprint(\"The sum is:\", sum_of_numbers(numbers_list)) # 输出The sum is: 15\n```\n\n在这段代码中我们定义了一个名为 `sum_of_numbers` 的函数,它接收一个参数 `numbers`这是一个包含整数或浮点数的列表。函数内部使用了Python的内置函数 `sum()` 来计算这些数字的总和,并将结果返回。\n\n你也可以手动实现求和逻辑而不是使用内置的 `sum()` 函数,如下所示:\n\n```python\ndef sum_of_numbers_manual(numbers):\n \"\"\"\n 手动计算列表中所有数字的和。\n \n 参数:\n numbers (list of int or float): 一个包含数字的列表。\n \n 返回:\n int or float: 列表中所有数字的总和。\n \"\"\"\n total_sum = 0\n for number in numbers:\n total_sum += number\n return total_sum\n\n# 示例使用\nnumbers_list = [1, 2, 3, 4, 5]\nprint(\"The sum is:\", sum_of_numbers_manual(numbers_list)) # 输出The sum is: 15\n```\n\n在这个版本中我们初始化 `total_sum` 为0然后遍历列表中的每个元素并将其加到 `total_sum` 上。最后返回这个累加的结果。这两种方法都可以达到相同的目的,但是使用内置函数通常更简洁且效率更高。")
ctx.SetUserAttribute("k2", 2213.22)
ctx.WriteUserAttributeToTrace()
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config CustomLogConfig, log wrapper.Log) types.Action {
if rand.Intn(10)%3 == 1 {
writeLog(ctx)
} else if rand.Intn(10)%3 == 2 {
writeLogWithKey(ctx, "ai_log")
} else {
writeTraceAttribute(ctx)
}
return types.ActionContinue
}

View File

@@ -5,7 +5,7 @@ description: AI Agent插件配置参考
---
## 功能说明
一个可定制化的 API AI Agent支持配置 http method 类型为 GET 与 POST 的 API支持多轮对话支持流式与非流式模式。
一个可定制化的 API AI Agent支持配置 http method 类型为 GET 与 POST 的 API支持多轮对话支持流式与非流式模式,支持将结果格式化为自定义的 json
agent流程图如下
![ai-agent](https://img.alicdn.com/imgextra/i1/O1CN01PGSDW31WQfEPm173u_!!6000000002783-0-tps-2733-1473.jpg)
@@ -21,6 +21,7 @@ agent流程图如下
| `llm` | object | 必填 | - | 配置 AI 服务提供商的信息 |
| `apis` | object | 必填 | - | 配置外部 API 服务提供商的信息 |
| `promptTemplate` | object | 非必填 | - | 配置 Agent ReAct 模板的信息 |
| `jsonResp` | object | 非必填 | - | 配置 json 格式化的相关信息 |
`llm`的配置字段说明如下:
@@ -78,7 +79,14 @@ agent流程图如下
| `observation` | string | 非必填 | - | Agent ReAct 模板的 observation 部分 |
| `thought2` | string | 非必填 | - | Agent ReAct 模板的 thought2 部分 |
## 用法示例
`jsonResp`的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|--------------------|-----------|---------|--------|-----------------------------------|
| `enable` | bool | 非必填 | false | 是否开启 json 格式化。 |
| `jsonSchema` | string | 非必填 | - | 自定义 json schema |
## 用法示例-不开启 json 格式化
**配置信息**
@@ -293,7 +301,7 @@ deepl提供了一个工具用于翻译给定的句子支持多语言。。
**请求示例**
```shell
curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"我想在济南市鑫盛大厦附近喝咖啡,给我推荐几个"}],"presence_penalty":0,"temperature":0,"top_p":0}'
@@ -308,7 +316,7 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
**请求示例**
```shell
curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"济南市现在的天气情况如何?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
@@ -323,7 +331,7 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
**请求示例**
```shell
curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role": "user","content": "济南的天气如何?"},{ "role": "assistant","content": "目前济南市的天气为多云气温为24℃数据更新时间为2024年9月12日21时50分14秒。"},{"role": "user","content": "北京呢?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
@@ -338,7 +346,7 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
**请求示例**
```shell
curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"济南市现在的天气情况如何?用华氏度表示,用日语回答"}],"presence_penalty":0,"temperature":0,"top_p":0}'
@@ -353,7 +361,7 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
**请求示例**
```shell
curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"帮我用德语翻译以下句子:九头蛇万岁!"}],"presence_penalty":0,"temperature":0,"top_p":0}'
@@ -364,3 +372,71 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \
```json
{"id":"65dcf12c-61ff-9e68-bffa-44fc9e6070d5","choices":[{"index":0,"message":{"role":"assistant","content":" “九头蛇万岁!”的德语翻译为“Hoch lebe Hydra!”。"},"finish_reason":"stop"}],"created":1724043865,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":908,"completion_tokens":52,"total_tokens":960}}
```
## 用法示例-开启 json 格式化
**配置信息**
在上述配置的基础上增加 jsonResp 配置
```yaml
jsonResp:
enable: true
```
**请求示例**
```shell
curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"北京市现在的天气情况如何?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
```
**响应示例**
```json
{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content": "{\"city\": \"北京市\", \"weather_condition\": \"多云\", \"temperature\": \"19℃\", \"data_update_time\": \"2024年10月9日16时37分53秒\"}"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}}
```
如果不自定义 json schema大模型会自动生成一个 json 格式
**配置信息**
增加自定义 json schema 配置
```yaml
jsonResp:
enable: true
jsonSchema: |
title: WeatherSchema
type: object
properties:
location:
type: string
description: 城市名称.
weather:
type: string
description: 天气情况.
temperature:
type: string
description: 温度.
update_time:
type: string
description: 数据更新时间.
required:
- location
- weather
- temperature
additionalProperties: false
```
**请求示例**
```shell
curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"北京市现在的天气情况如何?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
```
**响应示例**
```json
{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content": "{\"location\": \"北京市\", \"weather\": \"多云\", \"temperature\": \"19℃\", \"update_time\": \"2024年10月9日16时37分53秒\"}"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}}
```

View File

@@ -4,7 +4,7 @@ keywords: [ AI Gateway, AI Agent ]
description: AI Agent plugin configuration reference
---
## Functional Description
A customizable API AI Agent that supports configuring HTTP method types as GET and POST APIs. Supports multiple dialogue rounds, streaming and non-streaming modes.
A customizable API AI Agent that supports configuring HTTP method types as GET and POST APIs. Supports multiple dialogue rounds, streaming and non-streaming modes, support for formatting results as custom json.
The agent flow chart is as follows:
![ai-agent](https://github.com/user-attachments/assets/b0761a0c-1afa-496c-a98e-bb9f38b340f8)
@@ -20,6 +20,7 @@ Plugin execution priority: `200`
| `llm` | object | Required | - | Configuration information for AI service provider |
| `apis` | object | Required | - | Configuration information for external API service provider |
| `promptTemplate` | object | Optional | - | Configuration information for Agent ReAct template |
| `jsonResp` | object | Optional | - | Configuring json formatting information |
The configuration fields for `llm` are as follows:
| Name | Data Type | Requirement | Default Value | Description |
@@ -71,7 +72,13 @@ The configuration fields for `chTemplate` and `enTemplate` are as follows:
| `observation` | string | Optional | - | The observation part of the Agent ReAct template |
| `thought2` | string | Optional | - | The thought2 part of the Agent ReAct template |
## Usage Example
The configuration fields for `jsonResp` are as follows:
| Name | Data Type | Requirement | Default Value | Description |
|--------------------|-----------|-------------|---------------|------------------------------------|
| `enable` | bool | Optional | - | Whether to enable json formatting. |
| `jsonSchema` | string | Optional | - | Custom json schema |
## Usage Example-disable json formatting
**Configuration Information**
```yaml
llm:
@@ -335,3 +342,68 @@ curl 'http://<replace with gateway public IP>/api/openai/v1/chat/completions' \
{"id":"65dcf12c-61ff-9e68-bffa-44fc9e6070d5","choices":[{"index":0,"message":{"role":"assistant","content":" The German translation of \"Hail Hydra!\" is \"Hoch lebe Hydra!\"."},"finish_reason":"stop"}],"created":1724043865,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":908,"completion_tokens":52,"total_tokens":960}}
```
## Usage Example-enable json formatting
**Configuration Information**
Add jsonResp configuration to the above configuration
```yaml
jsonResp:
enable: true
```
**Request Example**
```shell
curl 'http://<replace with gateway public IP>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"What is the current weather in Beijing ?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
```
**Response Example**
```json
{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content": "{\"city\": \"BeiJing\", \"weather_condition\": \"cloudy\", \"temperature\": \"19℃\", \"data_update_time\": \"Oct 9, 2024, at 16:37\"}"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}}
```
If you don't customise the json schema, the big model will automatically generate a json format
**Configuration Information**
Add custom json schema configuration
```yaml
jsonResp:
enable: true
jsonSchema:
title: WeatherSchema
type: object
properties:
location:
type: string
description: city name.
weather:
type: string
description: weather conditions.
temperature:
type: string
description: temperature.
update_time:
type: string
description: the update time of data.
required:
- location
- weather
- temperature
additionalProperties: false
```
**Request Example**
```shell
curl 'http://<replace with gateway public IP>/api/openai/v1/chat/completions' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"What is the current weather in Beijing ?"}],"presence_penalty":0,"temperature":0,"top_p":0}'
```
**Response Example**
```json
{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content": "{\"location\": \"Beijing\", \"weather\": \"cloudy\", \"temperature\": \"19℃\", \"update_time\": \"Oct 9, 2024, at 16:37\"}"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}}
```

Some files were not shown because too many files have changed in this diff Show More