nginx将流量转发到envoy,需要修改nginx的配置文件
upstream backend_ups {
server 127.0.0.1:10000; # 原配置 backend_service:10000}
server {
listen 80;
listen [::]:80;
server_name localhost;
location /test {
proxy_pass http://backend_ups;
}
}
从两个方面来说:
基于此,本文就来讨论一下,如何在不修改业务层nginx配置的情况下,envoy怎么劫持业务流量
原nginx配置文件:
upstream backend_ups {
server backend-service:10000;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
location /test {
proxy_pass http://backend_ups;
}在 Controller 中使用本地化
using CodeSpirit.Localization.Resources;using Microsoft.Extensions.Localization;public class MyController : ApiControllerBase{ private readonly IStringLocalizer<SharedResources> _localizer;
public MyController m.judati.com(IStringLocalizer<SharedResources> localizer)
{
_localizer = localizer;
}
[HttpPost] public IActionResult Create()
{ return Ok(new ApiResponse
{
Status = 1,
Msg = _localizer["Common.Save"].Value
});
}
}
抛出本地化异常
// 使用资源键throw new BusinessException("Errors.InvalidStartTime");// 带参数throw new ValidationException("Errors.NotFound", resourceId);
3. DTO 验证特性多语言
using CodeSpirit.Localization.Resources;public class CreateQuestionDto{
[Display(Name = "Content", ResourceType = typeof(DisplayResources))]
[Required(ErrorMessageResourceType = typeof(ValidationResources),
ErrorMessageResourceName judati.com= "Required")]
[StringLength(2000,
ErrorMessageResourceType = typeof(ValidationResources),
ErrorMessageResourceName = "StringLengthMax")] public string Content { get; set; } = string.Empty;
}
}
如果之前一直跟着操练的同学,可以重置一下测试环境: envoy测试环境
使用hostAliases,将本来应该指向后端服务的
backend_service换成指向本地
127.0.0.1的envoy即可
修改nginx的pod编排文件
... hostAliases: - hostnames: - backend-service ip: 127.0.0.1 # 注意和containers一个级别 containers: ......
这种做法简单有效,但是有2个问题:
backend-service,envoy的转发配置是
backend-headless-service,恰好不同,可以使用由此可知,该方法是最简单的流量劫持,通过host映射,将流量转发至envoy,但是限制太大,一旦上述2个条件不满足,是没法使用该方法的
到这里,又要请出iptables了,作为老演员,iptables在前面(利用iptables记录后端ip)就出场过,现在又需要了
目标端口为10000的端口,并且目标ip非127.0.0.1的流量,都要转发到本地的10000端口
iptables -t nat -A OUTPUT -p tcp --dport 10000 ! -d 127.0.0.1/32 -j REDIRECT --to-ports 10000
由于端口都是10000,所以需要拦截nginx发送的,而放行envoy发出的,否则就要打环了。通过uid来确认envoy发出的流量
▶ kubectl exec -it nginx-test-557df7457b-dr7sf -c envoy -- id envoy uid=101(envoy) gid=101(envoy) groups=101(envoy)
iptables -t nat -A OUTPUT -m owner --uid-owner 101 -j RETURN
整理一下最终的版本(注意顺序):
iptables -t nat -A OUTPUT -m owner --uid-owner 101 -j RETURN iptables -t nat -A OUTPUT -p tcp --dport 10000 ! -d 127.0.0.1/32 -j REDIRECT --to-ports 10000
root@wilson:/home/wilson/workspace# iptables -L -n -t natChain PREROUTING (policy ACCEPT)target prot opt source destination Chain INPUT (policy ACCEPT)target rt.52haike.com prot opt source destination Chain OUTPUT (policy ACCEPT)target prot opt source destinationRETURN all -- 0.0.0.0/0 0.0.0.0/0 owner UID match 101REDIRECT tcp -- 0.0.0.0/0 !127.0.0.1 tcp dpt:10000 redir ports 10000Chain POSTROUTING (policy ACCEPT)target prot opt source destination
▶ sudo crictl ps | grep nginx-test091f9a85ba53e 49b0af0078643 30 minutes ago Running envoy 0 ac7556e89b5b3 nginx-test-557df7457b-dr7sf default6b143efde5b0f 7f553e8bbc897 30 minutes ago Running nginx-test 0 ac7556e89b5b3 nginx-test-557df7457b-dr7sf default
▶ sudo crictl inspect 6b143efde5b0f | grep -i pid "pid": 1, "pid": 1569896, "type": "pid"
▶ sudo nsenter -n --target 1569896
执行
curl 10.22.12.178:30785/test,并且查看nginx日志
10.244.0.1 - - [26/Dec/2025:02:15:43 +0000] i5.52haike.com"GET /test HTTP/1.1" 200 10.105.148.194:10000 40 "-" "curl/7.81.0" "-"
确实已经有日志进入nginx,但是再查看envoy的时候,空空如也,说明流量并没有被转发至envoy
由于环境特殊,envoy和backend都是10000端口,所以规则里面新加了一条,只要是uid为101,全部都放行,不再匹配后面的规则
RETURN all -- 0.0.0.0/0 0.0.0.0/0 owner UID match 101
从目前的现象来看,所有的出流量都命中了这条规则,全部被放行了,赶紧去检查一下nginx启动用户的uid
▶ kubectl exec -it nginx-test-557df7457b-dr7sf -c nginx-test -- id nginx uid=101(nginx) gid=101(nginx) groups=101(nginx)
问题找到!由于envoy用户与nginx用户都用101作为uid,导致iptables规则全部放过了,那要解决这个问题,有几种方法:
name: envoy # 和container name一个级别 ... securityContext: runAsUser: 1234
由于在调试阶段,我们使用第一种方案,临时换pid
有位老哥要问了,为什么同一个pod里面可以有相同的uid呢?因为每个容器有自己的pid namespace,所以就算在容器里面,他们依然试隔离开的,当然也可以直接配置共享同一个pid namespace:
shareProcessNamespace: true
[2025-12-26T03:35:12.708Z] "GET /test HTTP/1.0" 200 40 1 856d3200-abb3-486f-8e4c-8441f20bdbb0 "curl/7.81.0" "-" 10.244.0.114:10000 app_service - [2025-12-26T03:35:12.937Z] "GET rq.52haike.com/test HTTP/1.0" 200 40 1 271f828e-5bd0-4fa1-95dc-3f17364ba8b8 "curl/7.81.0" "-" 10.244.0.111:10000 app_service -
流量终于被iptables转发至envoy,并且转发到后端的backend去了
上面已经验证了,通过iptables可以劫持相关流量,而业务层不需要做改变即可完成。现在需要将这一套方法变成实际可行的工程方案,并且自动执行,不可能每次都人工修改
使用initContainers,让每一个pod启动都优先处理iptables配置
initContainers:
- args:
- |
apk add --no-cache iptables
iptables -t nat -A OUTPUT -m owner --uid-owner 1234 -j RETURN
iptables -t nat -A OUTPUT -p tcp --dport 10000 ! -d 127.0.0.1/32 -j REDIRECT --to-ports 10000 command:
- /bin/sh
- -c
image: alpine:3.23
imagePullPolicy: Always
name: iptables-init
resources: rr.52haike.com{} securityContext:
privileged: true
这里不太优雅的是该容器每次都要先安装iptables,可以自己打一个镜像,预装好iptables
Module 特性用于定义模块级别的多语言配置,通常放在
ApiControllerBase 上:
using CodeSpirit.Core.Attributes;using CodeSpirit.Core.Enums;using CodeSpirit.Navigation.Resources;// 模块级配置(在 ApiControllerBase 上)[Module("identity",
displayName: "用户中心", // 回退文本
DisplayNameResourceKey = "Module.Identity", // 资源键
DisplayNameResourceType = typeof(NavigationResources), // 资源类型
Icon = "fa-solid fa-user-group")]
[Navigation(
Icon = "fa-solid fa-user-group",
PlatformType = PlatformType.Both,
TitleResourceKey m.52haike.com= "Module.Identity", // 与 Module 的资源键保持一致
TitleResourceType = typeof(NavigationResources)
)]public abstract class ApiControllerBase : CodeSpirit.Shared.Controllers.ApiControllerBase{
}
NavigationAttribute 用于控制器级别的多语言配置:
using CodeSpirit.Core.Attributes;using CodeSpirit.Navigation.Resources;using System.ComponentModel;// 控制器级配置[DisplayName("用户管理")]
[Navigation(
Icon = "fa-solid fa-users",
PlatformType = PlatformType.Tenant,
TitleResourceKey =52haike.com "Controller.Users",
TitleResourceType = typeof(NavigationResources)
)]public class UsersController : ApiControllerBase{
}
// 清空所有导航缓存await _navigationService.ClearAllNavigationCacheAsync();// 清空特定模块缓存(实际上也会清空整个缓存)await _navigationService.ClearModuleNavigationCacheAsync("Identity");// 重新初始化导航树(清空并重建缓存)await _navigationService.InitializeNavigationTree();
# 清空所有导航缓存DELETE /api/navigation/cache# 清空特定模块缓存DELETE /api/navigation/cache?moduleName=Identity# 重新初始化导航树(清空并重建缓存)POST /api/navigation/initialize
确保资源文件正确配置为嵌入式资源:
<ItemGroup> <EmbeddedResource Include="Resources\NavigationResources.resx"> <Generator>PublicResXFileCodeGenerator</Generator> </EmbeddedResource> <EmbeddedResource Include="Resources\NavigationResources.en.resx" /></ItemGroup>
Module 和
Navigation 特性配置模块级多语言displayName、
Title),确保资源不可用时仍能显示Module 的
DisplayNameResourceKey 和
Navigation 的
TitleResourceKey 通常使用相同的资源键以下是用户中心模块的完整多语言配置示例(来自实际代码):
using CodeSpirit.Core;using 5g.xyhcenter.com CodeSpirit.Core.Attributes;using CodeSpirit.Core.Enums;using CodeSpirit.Navigation.Resources;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;using System.ComponentModel;namespace CodeSpirit.IdentityApi.Controllers{ /// <summary>
/// 身份认证API控制器基类
/// </summary>
[ApiController]
[Authorize(policy: "DynamicPermissions")]
[Route("api/identity/[controller]")] // 模块级配置(使用 Module 和 Navigation 特性)
[Module("identity",
displayName: "用户中心", // 回退文本
DisplayNameResourceKey = "Module.Identity", // 资源键
DisplayNameResourceType = typeof(NavigationResources), // 资源类型
Icon = "fa-solid fa-user-group")]
[Navigation(
Icon = "fa-solid fa-user-group",
PlatformType = PlatformType.Both,
TitleResourceKey = "Module.Identity",
TitleResourceType rt.xyhcenter.com= typeof(NavigationResources)
)] public abstract class ApiControllerBase : CodeSpirit.Shared.Controllers.ApiControllerBase
{
} /// <summary>
/// 用户管理控制器
/// </summary>
[DisplayName("用户管理")]
[Navigation(
Icon = "fa-solid fa-users",
PlatformType = PlatformType.Tenant,
TitleResourceKey = "Controller.Users",
TitleResourceType = typeof(NavigationResources)
)] public class UsersController : ApiControllerBase
{ private readonly IUserService _userService; public UsersController(IUserService userService)
{
_userService = userService;
} /// <summary>
/// 获取用户列表
/// </summary>
[HttpGet]
[DisplayName("获取用户列表")] rr.xyhcenter.com public async Task<ActionResult<ApiResponse<PageList<UserDto>>>> GetUsers([FromQuery] UserQueryDto queryDto)
{
PageList<UserDto> users = await _userService.GetUsersAsync(queryDto); return SuccessResponse(users);
}
}
}
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3107746/ ,如需转载,请注明出处,否则将追究法律责任。