【 Kubernetes 风云录 】- Istio 实现流量染色及透传
文章目录
- 了解Istio中不同的流量处理
- 定义
- 对比
- Rust
- 安装
- 新建项目并构建wasm文件
- 构建镜像
- WasmPlugin
- 染色验证
- 流量透传
- 问题
了解Istio中不同的流量处理
定义
- VirtualService — 流量转发的“路由规则”
- EnvoyFilter — 修改 Envoy 的底层行为
- WasmPlugin — Istio 官方提供的 WebAssembly 插件接入方式
👉 VirtualService 在上层负责流量路由控制
👉 WasmPlugin 作为中间层可插拔的逻辑处理模块
👉 EnvoyFilter 位于底层,负责对 Envoy 内部行为的精准调整
对比
方案 | 灵活性 | 性能开销 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|---|
WasmPlugin | 高 | 灵活、高性能、支持复杂逻辑 | 需要编写和编译 WASM 模块 | 需要复杂逻辑或动态决策 | |
EnvoyFilter Lua | 中 | 无需编译,快速修改 | 性能较低,语法受限 | 简单 Header 操作 | |
VirtualService | 低 | 无 | 配置简单 | 无法动态过滤 Header | 静态路由规则 |
Rust
Rust/C++ 编译出来的 Wasm 插件相对较小、启动快、运行效率高 ,所以官方推荐主使用这两种语言构建 wasm
文件。
安装
国内环境配置 rustup镜像
[root@ycloud-1 ~]# export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup
[root@ycloud-1 ~]# export RUSTUP_DIST_ROOT=https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup
[root@ycloud-1 ~]# curl https://sh.rustup.rs -sSf | sh
⚡️: 安装成功, Cargo是Rust编程语言的构建系统和包管理器。
新建项目并构建wasm文件
[root@TCN1206717-1 Downloads]# cargo new wasm-demo --lib
Creating binary (application) `wasm-demo` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[root@TCN1206717-1 wasm-demo]# cd wasm-demo
[root@TCN1206717-1 wasm-demo]# tree
.
├── Cargo.toml
└── src
└── lib.rs
下面直接给出配置及代码文件
## Cargo.toml
## 项目所需要的依赖包
[package]
name = "wasm-demo"
version = "0.1.0"
edition = "2024"
[dependencies]
proxy-wasm = "0.2"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[profile.release]
lto = true
opt-level = 3
codegen-units = 1
panic = "abort"
strip = "debuginfo"
// src/lib.rs
// 主要针对流量染色透传逻辑
use log;
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
use serde::Deserialize;
use serde::Serialize;
use serde_json;
proxy_wasm::main! {{
proxy_wasm::set_log_level(LogLevel::Trace);
proxy_wasm::set_root_context(|_| Box::new(ColorRootContext::default()));
}}
#[derive(Default)]
struct ColorRootContext {
config: Option<ColorConfig>,
}
impl Context for ColorRootContext {}
impl RootContext for ColorRootContext {
fn get_type(&self) -> Option<ContextType> {
Some(ContextType::HttpContext)
}
fn on_configure(&mut self, _: usize) -> bool {
if let Some(config_bytes) = self.get_plugin_configuration() {
match serde_json::from_slice::<ColorConfig>(&config_bytes) {
Ok(parsed) => {
self.config = Some(parsed.clone());
let config_str = serde_json::to_string(&parsed).unwrap();
self.set_shared_data("color_config", Some(config_str.as_bytes()), None)
.unwrap();
log::info!("Color config loaded.");
true
}
Err(e) => {
log::error!("Failed to parse config: {}", e);
false
}
}
} else {
log::warn!("No plugin config provided.");
false
}
}
fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> {
Some(Box::new(ColorHttpContext { context_id }))
}
}
struct ColorHttpContext {
context_id: u32,
}
impl Context for ColorHttpContext {}
impl HttpContext for ColorHttpContext {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
let (config_bytes_opt, _) = self.get_shared_data("color_config");
let config: ColorConfig = match config_bytes_opt {
Some(bytes) => serde_json::from_slice(&bytes).unwrap_or_default(),
None => return Action::Continue,
};
// 染色逻辑
if self.get_http_request_header(&config.color_header).is_none() {
self.set_http_request_header(&config.color_header, Some(&config.default_color));
}
// 透传逻辑
for header in &config.propagate_headers {
if let Some(value) = self.get_http_request_header(header) {
self.set_http_request_header(header, Some(&value));
let new_key = format!("x-original-{}", header);
self.set_http_request_header(&new_key, Some(&value));
}
}
// 删除敏感 header
for header in &config.remove_headers {
// self.remove_http_request_header(header);
self.set_http_request_header(header,None);
}
// 检查是否修改了请求头
for (name, value) in &self.get_http_request_headers() {
log::debug!("#{} {} -> {}", self.context_id, name, value);
}
Action::Continue
}
}
#[derive(Deserialize, Clone, Serialize)]
struct ColorConfig {
color_header: String,
default_color: String,
propagate_headers: Vec<String>,
remove_headers: Vec<String>,
}
impl Default for ColorConfig {
fn default() -> Self {
ColorConfig {
color_header: "x-color".to_string(),
default_color: "gray".to_string(),
propagate_headers: vec!["x-trace-id".to_string()],
remove_headers: vec![],
}
}
}
⚡️: cargo build --target wasm32-unknown-unknown --release
构建镜像
根据我们打包好的wasm文件来构建镜像
FROM scratch
COPY target/wasm32-unknown-unknown/release/wasm-demo.wasm ./plugin.wasm
⚡️: Scratch镜像不包含任何文件系统,构建极致精简
WasmPlugin
WasmPlugin
是 Istio 提供的一个 标准接口,
用来在 Istio 的 Envoy Sidecar 中加载 WebAssembly 插件。
通俗来说就是:
👉 “告诉 Istio:这个
.wasm
插件在哪,怎么加载,什么时候生效,作用在哪些服务上。”
## wasmplugin.yaml
## 通过Label来确认作用到哪些服务
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: color-header-plugin
spec:
selector:
matchLabels:
#istio: ingressgateway
app.kubernetes.io/name: test
url: oci://hub.17usoft.com/middleware/istiowasm:1.2
pluginConfig:
color_header: "x-color"
default_color: "gray"
propagate_headers:
- "x-color"
- "x-trace-id"
remove_headers:
- "x-internal-secret"
👉 通过Label来确认作用到哪些服务。
⚡️ 针对请求新增 header 并且可以 remove 掉无用的header。
染色验证
自定义一个服务请求时打印出 header 信息,可以看到请求该服务时默认带上 X-Color: gray
这个header
流量透传
⚡️: 老板不想对业务代码有侵入
所以做流量透传时,并不是去“拦截”整条链路上的所有流量进行操作,而是通过 Istio 的 WasmPlugin 机制,让特定的插件仅作用于带有特定 Label
的服务。
问题
如何可以将链路上的所有服务在不调整控制器的情况下,去添加特定的 label。
现考虑的是否可以通过 MutatingAdmissionWebhook 自动注入 Pod Label。