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

推荐订阅源

F
Fortinet All Blogs
S
Secure Thoughts
月光博客
月光博客
美团技术团队
雷峰网
雷峰网
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
News and Events Feed by Topic
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Forbes - Security
Forbes - Security
W
WeLiveSecurity
P
Proofpoint News Feed
阮一峰的网络日志
阮一峰的网络日志
爱范儿
爱范儿
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
AI
AI
Last Week in AI
Last Week in AI
Google Online Security Blog
Google Online Security Blog
Schneier on Security
Schneier on Security
云风的 BLOG
云风的 BLOG
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Announcements
Recent Announcements
Webroot Blog
Webroot Blog
T
Tor Project blog
Cisco Talos Blog
Cisco Talos Blog
N
News and Events Feed by Topic
罗磊的独立博客
The Register - Security
The Register - Security
Blog — PlanetScale
Blog — PlanetScale
T
Threat Research - Cisco Blogs
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
人人都是产品经理
人人都是产品经理
T
The Exploit Database - CXSecurity.com
www.infosecurity-magazine.com
www.infosecurity-magazine.com
B
Blog
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Engineering at Meta
Engineering at Meta
Latest news
Latest news
IT之家
IT之家
D
DataBreaches.Net
博客园 - 司徒正美
N
Netflix TechBlog - Medium
V
V2EX
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

元视角

.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 ,大家可以在此基础上做进一步的探索。好了,以上就是这篇博客的的全部内容啦,欢迎大家就本文中提出的方案、代码等进行讨论,如果大家有任何意见或者建议,欢迎在评论区进行留言,谢谢大家!

参考文档