ITPub博客

首页 > 数字化转型 > CRM > nginx->envoy->backend,并且envoy作用sidecar

nginx->envoy->backend,并且envoy作用sidecar

投稿 CRM 作者:ecclw459 时间:2026-01-13 09:48:22 0 删除 编辑

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的端口和envoy的代理端口不一样,那就不能用。而在我们的这个例子中,envoy的代理端口和后端的服务端口,恰好都是10000,可以使用
  • 域名映射是pod级别的,会让所有的containers生效,nginx转发的名字与envoy转发的名字不能是同一个。在我们的例子当中,nginx的upstream转发配置是 backend-service,envoy的转发配置是 backend-headless-service,恰好不同,可以使用

由此可知,该方法是最简单的流量劫持,通过host映射,将流量转发至envoy,但是限制太大,一旦上述2个条件不满足,是没法使用该方法的

使用iptables

到这里,又要请出iptables了,作为老演员,iptables在前面(利用iptables记录后端ip)就出场过,现在又需要了

劫持所有的出流量

  • 要将所有从nginx发送到backend-service:10000的流量劫持到envoy中(127.0.0.1:10000),通过iptables可以完成
  • 由于本文中,后端服务backend与envoy监听的都是同一个10000端口,所以需要做特殊的处理
    • 目标端口为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

利用nsenter进入container

  • 找到nginx容器id
    ▶ 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
  • 找到容器对应的pid
    ▶ sudo crictl inspect 6b143efde5b0f | grep -i pid            "pid": 1,    "pid": 1569896,            "type": "pid"
  • 进入容器
    ▶ sudo nsenter -n --target 1569896
  • 之后开始执行iptables命令即可

验证是否能够劫持流量

执行 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规则全部放过了,那要解决这个问题,有几种方法:

  • 最简单最直接的,用另外一个uid作为envoy的uid
            name: envoy # 和container name一个级别
            ...
            securityContext:
              runAsUser: 1234
  • 在打docker镜像的时候指定uid,这个是最好的方案,彻底隔离开pid

由于在调试阶段,我们使用第一种方案,临时换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去了

使用initContainers配置

上面已经验证了,通过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 特性

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{
}
方法2:通过代码 API 调用
// 清空所有导航缓存await _navigationService.ClearAllNavigationCacheAsync();// 清空特定模块缓存(实际上也会清空整个缓存)await _navigationService.ClearModuleNavigationCacheAsync("Identity");// 重新初始化导航树(清空并重建缓存)await _navigationService.InitializeNavigationTree();
方法3:通过 HTTP API 调用
# 清空所有导航缓存DELETE /api/navigation/cache# 清空特定模块缓存DELETE /api/navigation/cache?moduleName=Identity# 重新初始化导航树(清空并重建缓存)POST /api/navigation/initialize

2. 资源文件编译

确保资源文件正确配置为嵌入式资源:

<ItemGroup>
  <EmbeddedResource Include="Resources\NavigationResources.resx">
    <Generator>PublicResXFileCodeGenerator</Generator>
  </EmbeddedResource>
  <EmbeddedResource Include="Resources\NavigationResources.en.resx" /></ItemGroup>

3. 开发建议

  • 模块配置:推荐同时使用  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/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2026-01-07

  • 博文量
    108
  • 访问量
    102110