插件是Envoy扩展性支持的重要方式,通过插件扩展,可以支持不同的网络协议和链路治理特性,也可以对流量进行场景化定制处理。
1.插件注册机制
Envoy插件当前采用静态注册的方式,只需要声明Registry::RegisterFactory对应的模板类变量,就可以完成插件的注册。以OriginalDst插件为例,声明如下:
static Registry::RegisterFactory<OriginalDstConfigFactory, Server::Configuration::NamedListenerFilterConfigFactory> registered_;
插件的具体注册通过RegisterFactory类来完成,在RegisterFactory类中,会通过模板参数声明一个类的成员变量,在类的构造函数中,调用registerFactory完成模板参数的实际注册。
template <class T, class Base> class RegisterFactory { public: //通过FactoryRegistry构建模板 RegisterFactory() { FactoryRegistry<Base>::registerFactory(instance_); } private: T instance_{}; };
在FactoryRegistry<Base>::registerFactory中,直接将插件名和插件实例注册到全局的插件实例map上。
static void registerFactory(Base& factory) { factories().emplace(std::make_pair(factory.name(), &factory)); ... }
插件会通过Config::Utility::getAndCheckFactory函数获取注册的插件实例,getAnd-CheckFactory会调用FactoryRegistry::getFactory函数,查找到相应的插件实例。
static Base* getFactory(const std::string& name) { auto it = factories().find(name); if (it == factories().end()) { return nullptr; } return it->second; }
2.插件触发机制
Envoy插件的使用方式均比较类似,上面讲述插件注册机制时可以看出,插件静态注册是一个根据插件名称将插件工厂模板注册到全局map的过程。使用插件前需要根据配置文件,使用插件工厂模板获取当前支持的插件工厂列表,示例代码如下:
std::vector<XXXFactoryCb> ret; for (ssize_t i = 0; i < filters.size(); i++) { //获取当前配置的插件名称,以及对应的插件配置 const auto& proto_config = filters[i]; const auto& string_name = proto_config.name(); const auto& filter_config = getJsonObjectFromMessage(proto_config.config()); //获取插件对应的实例工厂 auto& factory = getAndCheckFactory<XXXFactory>(string_name); //根据插件配置,实例化插件工厂 auto message = Utility::translateToFactoryConfig(proto_config, factory); ret.push_back(factory. createFilterFactoryFromProto (*message, context)); }
上述实例代码最后通过插件工厂模板的createFilterFactoryFromProto方法创建具体的插件工厂,以下面的OriginalDstConfigFactory插件为例,返回的插件工厂采用的是C++11 Lambda表达式(对C++11 Lambda使用方式不太熟悉的读者,可以自行学习下C++11的语法规范,这里不再详述)。
Network::ListenerFilterFactoryCb OriginalDstConfigFactory::createFilterFactoryFromProto( const Protobuf::Message&, ListenerFactoryContext&) override { //返回C++11 Lambda表达式形式的插件工厂 return [](Network::ListenerFilterManager& filter_manager) -> void { filter_manager.addAcceptFilter(std::make_unique<OriginalDstFilter>()); }; }
使用插件前,根据上述返回的插件工厂列表构造具体的插件实例。下面createListener-FilterChain函数中的listener_filter_factories_就是根据监听器配置得出的监听器工厂列表,遍历每一个工厂方法进行Lambda表达式展开,实际执行的是filter_manager.addAccept-Filter(std::make_unique<OriginalDstFilter>())语句,addAcceptFilter完成真正的插件实例构造OriginalDstFilter,然后将插件挂载到插件管理器中。
bool createListenerFilterChain(ListenerFilterManager& manager) { return buildFilterChain(manager, listener_filter_factories_); } bool buildFilterChain(FilterManager& filter_manager, const std::vector<Network::FilterFactoryCb>& factories) { for (const Network::FilterFactoryCb& factory : factories) { //这里会对之前返回的C++11 Lambda表达式形式的插件工厂进行Lambda展开 factory(filter_manager); } return filter_manager.initializeReadFilters(); }
Envoy自带一些插件,统一放在/source/extensions目录下,最大的一块在filters 子目录下,负责网络方面的插件实现,当前主要有3部分:分别是监听过滤器、网络过滤器和HTTP过滤器,分别负责连接元数据层面的扩展支持、网络协议方面的扩展支持以及基于协议内容的扩展支持,下面会分别讨论。
1.监听过滤器
监听过滤器在接收到新连接请求,但还未进行实际的新连接创建工作之前触发,通过监听过滤器插件,可以调整连接元数据信息。Envoy当前支持原目的地、原出发地、Proxy Protocol和TLS Inspestor这几种监听过滤器。
Envoy会对Istio集群的所有流量进行透明拦截,通过监听端口号获取拦截层发过来的请求,原目的地监听过滤器用于获取拦截前的目的地址,然后基于该目的地址进行后续的路由和转发操作。
出于问题定位和诊断的考虑,不少场景下下游服务希望上游连接采用的是真实的客户端地址,而不是中间的代理服务器地址。Proxy Protocol监听过滤器就是为了实现这个需求,Proxy Protocol监听过滤器基于Proxy Protocol协议。Proxy Protocol协议是HA Proxy作者Willy Tarreau设计的一个网络协议,通过为TCP添加一个额外头部信息,方便传递一些客户端信息到服务端,比如协议栈、源IP地址等,这对复杂网络场景下需要同步获取客户端真实IP地址非常有用。Proxy Protocol监听过滤器基于Proxy Protocol协议获取客户端真实的IP地址,Envoy和下游建立连接时采用客户端请求的源IP地址,而不是Envoy自身的IP地址,下游感知不到Envoy的存在,以为请求仍然是从真正的客户端发过来的,实现了真正的Envoy透传。需要特别注意的一点,由于Proxy Protocol协议对TCP协议进行了定制扩展,和正常的TCP协议不兼容,因此 使用Proxy Protocol协议时需要上下游均支持Proxy Protocol协议才可以,否则会导致消息解析出错。
2.网络过滤器
网络过滤器主要有两个层面的工作:一是连接相关的,主要是连接相关的控制,比如限流、认证等;二是协议相关的处理,Envoy接收到请求时首先需要知道该请求是什么协议,如何进行解码等,协议处理是网络过滤器的主要工作,也是整个Envoy的工作重心所在,Envoy当前支持HTTP、Thrift、MySQL、Kafka、Dubbo等众多网络协议,下面重点讲一下协议处理中的网络过滤器的原理和大体实现。
网络过滤器的首要工作是协议编解码处理,收到请求消息时对请求消息进行解码处理,获取上游流量路由和转发相关的元数据信息,收到上游的响应消息后,进行编码处理,返回给下游客户端;其次是流量路由和转发,根据当前使用场景和请求元数据信息,决策最合适的Upstream上游,这个也是Envoy的主要价值所在;最后一项工作是Upstream管理,获取与Upstream之间的连接,并建立和Upstream之间的安全通信,同时在Upstream异常时进行完善的容错处理。
3.协议相关过滤器
协议相关过滤器基于协议内容进行过滤和扩展处理,Envoy当前对HTTP协议、Thrift协议等均有过滤器扩展支持,对HTTP协议的支持比较完善些,当前支持路由、限流、Lua扩展等多种扩展方式。
插件扩展需要有相应的扩展点,对于HTTP协议来说,在编解码的不同阶段,均支持相应的扩展点。当前主要的扩展点有对HTTP消息头、消息体、Trailers这几部分的编码和解码。
除了网络相关插件之外,Envoy还内置了一些插件支持,大体分为流量管理、可观测性和服务管理这几个部分。
流量管理方面,支持的主要是各个网络协议均会用到的协议无关的一些插件,比如健康检查扩展支持、重试和传输层扩展支持等。
可观测性是Envoy一个很大的亮点,Envoy在可观测性的几个核心维度——Metric、Log和Trace均有相应的框架支持。
此外,Envoy还内置一些服务管理方面的插件支持,比如资源限制等。
除了官方源码自带的插件外,Envoy还支持以自定义方式进行插件扩展。Envoy原生代码中没有Mixer相关的实现,Istio数据平面对Mixer的支持是通过单独的proxy项目来实现。