惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
V
Vulnerabilities – Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
V
V2EX
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
Latest news
Latest news
博客园 - 三生石上(FineUI控件)
美团技术团队
aimingoo的专栏
aimingoo的专栏
Google Online Security Blog
Google Online Security Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Threatpost
Y
Y Combinator Blog
T
Tailwind CSS Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
A
Arctic Wolf
C
Cyber Attacks, Cyber Crime and Cyber Security
小众软件
小众软件
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Tenable Blog
W
WeLiveSecurity
L
LINUX DO - 热门话题
D
Docker
Cyberwarzone
Cyberwarzone
量子位
A
About on SuperTechFans
The Last Watchdog
The Last Watchdog
雷峰网
雷峰网
C
CERT Recently Published Vulnerability Notes
P
Palo Alto Networks Blog
The Hacker News
The Hacker News
Blog — PlanetScale
Blog — PlanetScale
P
Proofpoint News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Full Disclosure
The Cloudflare Blog
T
The Blog of Author Tim Ferriss
T
The Exploit Database - CXSecurity.com
Engineering at Meta
Engineering at Meta
O
OpenAI News
Hacker News - Newest:
Hacker News - Newest: "LLM"
Scott Helme
Scott Helme
IT之家
IT之家
S
Secure Thoughts
MongoDB | Blog
MongoDB | Blog
L
Lohrmann on Cybersecurity
博客园 - 司徒正美
Google DeepMind News
Google DeepMind News

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角
飞鸿踏雪 · 2021-07-02 · via 元视角

回想起来,博主第一次接触到Envoy,其实是在微软的示例项目 eShopOnContainers,在这个示例项目中,微软通过它来为Ordering APICatalog APIBasket API 等多个服务提供网关的功能。当时,博主并没有对它做深入的探索。此刻再度回想起来,大概是因为那个时候更迷恋领域驱动设计(DDD)的理念。直到最近这段时间,博主需要在一个项目中用到Envoy,终于决定花点时间来学习一下相关内容。所以,接下来这几篇博客,大体上会以记录我学习Envoy的历程为主。考虑到Envoy的配置项特别多,在写作过程中难免会出现纰漏,希望大家谅解。如对具体的配置项存在疑问,请以官方最新的 文档 为准,本文所用的示例代码已经上传至 Github,大家作为参考即可。对于今天这篇博客,我们来聊聊 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 这个话题,或许你曾经接触过 Nginx 或者 Ocelot,这次我们不妨来尝试一点新的东西,譬如,通过Docker-Compose来实现服务编排,如果对我说的这些东西感兴趣的话,请跟随我的脚步,一起来探索这广阔无垠的技术世界吧!

走近 Envoy

Envoy 官网对Envoy的定义是:

Envoy 是一个开源边缘和服务代理,专为原生云应用设计。

而更进一步的定义是:

Envoy 是专为大型现代服务导向架构设计的 L7 代理和通讯总线。

这两个定义依然让你感到云里雾里?没关系,请看下面这张图:

Envoy架构图 Envoy架构图

注:图片来源

相信从这张图中,大家多少能看到反向代理的身影,即下游客户端发起请求,Envoy对请求进行侦听(Listeners),并按照路由转发请求到指定的集群(Clusters)。接下来,每一个集群可以配置多个终结点,Envoy按照指定的负载均衡算法来筛选终结点,而这些终结点则指向了具体的上游服务。例如,我们熟悉的 Nginx ,使用listen关键字来指定侦听的端口,使用location关键字来指定路由,使用proxy_pass关键字来指定上游服务的地址。同样地,Ocelot 使用了类似的上下游(Upstream/Downstream)的概念,唯一的不同是,它的上下游的概念与这里是完全相反的。

你可能会说,这个Envoy看起来“平平无奇”嘛,简直就像是“平平无奇”的古天乐一般。事实上,Envoy强大的地方在于:

  • 非侵入式的架构: 独立进程、对应用透明的Sidecar模式

Envoy 的 Sidecar 模式 Envoy 的 Sidecar 模式

  • L3/L4/L7 架构:Envoy同时支持 OSI 七层模型中的第三层(网络层, IP 协议)、第四层(传输层,TCP / UDP 协议)、第七层(应用层,HTTP 协议)
  • 顶级 HTTP/2 支持: 视 HTTP/2 为一等公民,且可以在 HTTP/2HTTP/1.1间相互转换
  • gRPC 支持:Envoy 支持 HTTP/2,自然支持使用 HTTP/2 作为底层多路复用协议的 gRPC
  • 服务发现和动态配置:与 Nginx 等代理的热加载不同,Envoy 可以通过 API 接口动态更新配置,无需重启代理。
  • 特殊协议支持:Envoy 支持对特殊协议在 L7 进行嗅探和统计,包括:MongoDBDynamoDB 等。
  • 可观测性:Envoy 内置 stats 模块,可以集成诸如 prometheus/statsd 等监控方案。还可以集成分布式追踪系统,对请求进行追踪。

Envoy配置文件

Envoy通过配置文件来实现各种各样的功能,其完整的配置结构如下:

{
  "node": "{...}",
  "static_resources": "{...}",
  "dynamic_resources": "{...}",
  "cluster_manager": "{...}",
  "hds_config": "{...}",
  "flags_path": "...",
  "stats_sinks": [],
  "stats_config": "{...}",
  "stats_flush_interval": "{...}",
  "watchdog": "{...}",
  "tracing": "{...}",
  "runtime": "{...}",
  "layered_runtime": "{...}",
  "admin": "{...}",
  "overload_manager": "{...}",
  "enable_dispatcher_stats": "...",
  "header_prefix": "...",
  "stats_server_version_override": "{...}",
  "use_tcp_for_dns_lookups": "..."
}

这里我们主要对侦听器(Listeners)、集群(Clusters) 和 管理(Admin)这三个常用的部分来进行说明。其中,(Listeners)、集群(Clusters) 均位于 static_resources 节点下,而 管理(Admin) 则有一个单独的admin节点。

侦听器(Listeners)

侦听器,顾名思义就是侦听一个或者多个端口:

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 9090

在这里,它表示的是侦听9090这个端口,这里的listeners是一个数组,所以,你可以同时侦听多个端口。

过滤器(Filters)

我们知道,单单侦听一个或者多个端口,是无法完成一个HTTP请求的,因为它还不具备处理HTTP请求的能力。

Envoy Filters 架构图 Envoy Filters 架构图

Envoy 中,这一工作由一个或者多个过滤器组成的过滤器链(Filter Chains) 来完成:

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 9090
    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
          codec_type: AUTO
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: backend
              domains:
              - "*"
              routes:
              - match:
                  prefix: "/api/w"
                route:
                  auto_host_rewrite: true
                  prefix_rewrite: /Weather
                  cluster: weatherservice
              - match:
                  prefix: "/api/c"
                route:
                  auto_host_rewrite: true
                  prefix_rewrite: /City
                  cluster: cityservice
          http_filters:
          - name: envoy.filters.http.router

在这段配置中,Http Connection Manager表示的是位于 L3(网络层)/L4(传输层) 的过滤器,这个过滤器连接的下一个过滤器是envoy.filters.http.router,表示的是 L7(应用层) 的关于路由的过滤器。个人感觉,这和我们通常所说的中间件相当接近。注意到,我们在 L3/L4 这个层级上做了什么事情呢?其实应该是 TCP/IP 层面上请求转发,这里定义的路由规则如下:

  • 当外部调用者访问/api/w时,请求会被转发到WeatherService
  • 当外部调用者访问/api/c时,请求会被转发到CityService

集群(Clusters)

在过滤器部分,我们定义了路由,那么,请求的最终去向是哪里呢?这里 Envoy 将其称之为 集群::

static_resources:
  clusters:
  # City Service
  - name: cityservice
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN

集群本身,其实只是一个名字,并没有实际的意义,真正工作的其实是指向上游服务的终结点,我们可以为每一个集群指定一个负载均衡策略,让它决定选择哪一个终结点来提供服务。

负载均衡(Load Assignment)

目前,Envoy支持以下负载均衡算法:

  • ROUND_ROBIN:轮询
  • LEAST_REQUEST:最少请求
  • RING_HASH:哈希环
  • RANDOM:随机
  • MAGLEV:磁悬浮
  • CLUSTER_PROVIDED

下面的示例展示了如何为某个集群配置负载均衡:

static_resources:
  clusters:
  # Weather Service
  - name: weatherservice
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: LEAST_REQUEST
    load_assignment:
      cluster_name: weatherservice
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: weatherservice1
                port_value: 80
        - endpoint:
            address:
              socket_address:
                address: weatherservice2
                port_value: 80

其中,weatherservice1weatherservice2是同一个服务的两个容器实例,当我们使用Docker-Compose进行构建的时候,不需要显式地去指定每一个服务的IP地址,只要对应docker-compose.yaml文件中的服务名称即可。

管理(Admin)

管理(Admin)这块儿相对简单一点,主要用来指定Envoy的的管理接口的端口号、访问日志存储路径等等:

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9091 }

服务编排

好了,在正确配置Envoy以后,我们来考虑如何对这些服务进行编排,在本文的 例子 中,我们有两个后端服务,WeatherServiceCityService,它们本质上是两个ASP.NET Core应用,我们希望通过Envoy来实现反向代理功能。这样做的好处是,后端服务的架构不会直接暴露给外部使用者。所以,你会注意到,在微服务架构的设计中,网关经常扮演着重要的角色。那么,如何对服务进行编排呢?这里我们使用Docker-Compose来完成这个工作:

version: '3'
services:
  envoygateway:
    build: Envoy/
    ports:
      - "9090:9090"
      - "9091:9091"
    volumes:
      - ./Envoy/envoy.yaml:/etc/envoy/envoy.yaml
  cityservice:
    build: CityService/
    ports:
      - "8081:80"
    environment:
      ASPNETCORE_URLS: "http://+"
      ASPNETCORE_ENVIRONMENT: "Development"
  weatherservice:
    build: WeatherService/
    ports:
      - "8082:80"
    environment:
      ASPNETCORE_URLS: "http://+"
      ASPNETCORE_ENVIRONMENT: "Development"

可以注意到,这里需要部署3个服务,其中,Envoy负责监听9090端口,即对外的网关。而两个后端服务,WeatherServiceCityService则被分别部署在80828081端口。这里最重要的是envoy.yaml这个配置文件,我们在上一节编写的配置文件会挂在到容器目录:/etc/envoy/envoy.yamlEnvoy将如何使用这个配置文件呢?事实上,这个Dockerfile是这样编写的:

FROM envoyproxy/envoy-alpine:v1.16-latest
COPY ./envoy.yaml /etc/envoy.yaml
RUN chmod go+r /etc/envoy.yaml
CMD ["/usr/local/bin/envoy", "-c", "/etc/envoy.yaml", "--service-cluster", "reverse-proxy"]

除此之外,Envoy通过9091端口提供管理功能,我们可以通过这个端口来获得集群、请求、统计等信息,这一特性我们将会在接下来的文章里用到:

Envoy 管理界面功能一览 Envoy 管理界面功能一览

这里,想分享一个关于Envoy的小技巧,当我们在指定集群的地址时,可以使用docker-compose.yaml中定义的服务的名称,这会比填入一个固定的IP地址要更优雅一点,理由非常简单,我们不希望每次都来维护这个地址。

 # Weather Service
  - name: weatherservice
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: weatherservice
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                # 建议使用 docker-compose.yaml 文件中对应的服务名称来代替IP地址
                address: weatherservice
                port_value: 80

接下来,如果每一个服务的Dockerfile都编写正确的话,我们就可以通过docker compose up命令启动这一组服务,通过命令行下打印出来的信息,我们可以确认,基于Envoy的网关服务、两个基于ASP.NET Core的后端服务都已经成功运行起来:

在 Docker-Compose 中成功启动服务 在 Docker-Compose 中成功启动服务

还记得我们的路由规则是是如何定义的吗?

  • 当外部调用者访问/api/w时,请求会被转发到WeatherService
  • 当外部调用者访问/api/c时,请求会被转发到CityService

实际的情况如何呢?我们不妨来验证一下:

通过 Envoy 调用 WeatcherService 通过 Envoy 调用 WeatcherService

可以注意到,不管是浏览器返回的结果,还是容器内部输出的日志,都表明请求确实被转发到对应的服务上面去了,这说明我们设计的网关已经生效。至此,我们实现了基于Envoy的反向代理功能,有没有觉得比Nginx要简单一点?重要的是,基于Docker-Compose的服务编排使用起来真的舒适度爆棚,这意味着你会有更多的属于程序员的贤者时间。前段时间热映的电视剧《觉醒时代》,鲁迅先生写完《狂人日记》后那一滴眼泪令人动容。也许,这种如匠人一般反复雕琢、臻于完美的心境是互通的,即使人类的悲欢并不相通,对美和极致的追求竟然出奇的相似。

本文小结

本文主要介绍了 ASP.NET Core 搭载 Envoy 实现反向代理这一过程。对于 Envoy,有两个重要的定义:第一、Envoy 是一个开源边缘和服务代理,专为原生云应用设计。第二、Envoy 是专为大型现代服务导向架构设计的 L7 代理和通讯总线。相比 NginxOcelot ,Envoy 提供了 L7 级别的代理服务,支持 HTTP/2gRPC,无侵入式的Sidecar模式可以提供与应用进程完全隔离的代理服务。接下来,博主对 Envoy 配置文件的结构及主要的配置项进行了说明,对于常见的 API 网关,我们应该重点关注侦听器(Listeners) 和 集群(Clusters)。最终,我们结合Docker-Compose对服务进行了编排,并由此构建出了一个基本的反向代理的方案。本文的源代码已托管至 Github ,大家可以在此基础上做进一步的探索。好了,以上就是这篇博客的的全部内容啦,欢迎大家就本文中提出的方案、代码等进行讨论,如果大家有任何意见或者建议,欢迎在评论区进行留言,谢谢大家!

参考文档