简介
开源 Agent 管理协议 (OpAMP) 是一个网络协议,用于对大型数据采集agent的远程管理。 OpAMP 允许agent上报状态到服务端、从服务端接收配置信息、从服务端接收agent安装包。该协议与agent供应商无关,因此服务器可以远程监控和管理实现了 OpAMP 的多个agent,包括来自不同供应商的agent组合。 OpAMP 支持以下功能:
- agent的远程配置。
- 状态报告。该协议允许agent上报自身的属性,例如自身的类型和版本号、所在的操作系统的类型和版本号。状态报告还允许服务端定制远程配置,对单个agent或某个类型的agent。
- agent自己的指标数据会上报到一个兼容 OTLP 协议的后端,用于监控agent的进程指标,例如 CPU、内存的使用情况,以及特定agent的指标,例如数据处理速率。
- agent软件包的下载管理。
- 安全的自动更新功能(包括agent的升级和降级)。
- 连接凭据管理,包括客户端 TLS 证书的撤销和轮换。
上面列出的功能支持对大量agent(例如OpenTelemetry Collector,Fluentd等)的“单一管理平台”的管理模式。
通信模型
OpAMP服务端会管理具备OpAMP协议的客户端实现的agent。后续简称OpAMP客户端或者客户端。 OpAMP协议并不假定Agent与客户端之间存在某种特殊关系。 客户端可以在与agent有着不同的生命周期的单独进程中运行,或者以sideCar模式运行,以agent插件模式运行,或者完全整合进agent代码中。
opamp服务端可以引导agent将其自身的监控指标发送到一个支持OTLP协议的目标。agent也可以连接其他目标用于发送收集到的数据:
┌────────────┬────────┐ ┌─────────┐
│ │ OpAMP │ OpAMP │ OpAMP │
│ │ ├──────────►│ │
│ │ Client │ │ Server │
│ └────────┤ └─────────┘
│ │
│ ┌────────┤ ┌─────────┐
│ │OTLP │ OTLP/HTTP │ OTLP │
│ Agent │ ├──────────►│ │
│ │Exporter│ │ Receiver│
│ └────────┤ └─────────┘
│ │
│ ┌────────┤
│ │Other ├──────────► Other
│ │Clients ├──────────► Destinations
└────────────┴────────┘
这个规范定义了OpAmp网络协议,以及agent端、服务端的预期行为。OTLP/HTTP协议定义 在此. agent用于连接其他目标的协议与agent类型有关,不在本规范的定义范围内。
OpAMP协议可以工作在以下2中协议:HTTP连接,以及websocket连接。 服务端的实现需要同时支持HTTP和websoucket连接。 OpAMP 客户端实现可以基于自身的需求选择支持http连接或者WebSocket连接。
OpAMP 客户端代表agent连接到 OpAMP 服务端。通常来讲一个服务端会接收到多个客户端的连接。使用agent生成或服务端生成的全局唯一实例标识符(简称instance_uid )来标识各个agent。instance_uid 位于从agent到服务端以及从服务端到agent的每条消息中。
通信连接的默认 URL 路径是/v1/opamp。URL路径能够在客户端或者服务端配置。
在agent侧的OpAmp的典型实现方式,是借助于能够管控agent进程的Supervisor进程。Supervisor也通常负责与OpAMP服务端通信:
┌───────────────────┐
│ Supervisor │
│ │
│ ┌────────┤ ┌────────┐
│ │ OpAMP │ OpAMP │ OpAMP │
│ │ ├──────────►│ │
│ │ Client │ │ Server │
└────┬─────┴────────┘ └────────┘
│
│
▼
┌──────────┐
│ │
│ Agent │
│ │
└──────────┘
OpAMP 规范不要求必须使用Supervisor,也不定义Supervisor于 Agent之间的交互方式。
接下来的OpAMP 中,_Agent _定义为实现了OpAMP的整个实体,不论Supervisor是不是其中的一部分。
WebSocket Transport
OpAMP 协议支持的传输形式包括 WebSocket。 OpAMP 客户端是WebSocket客户端,服务端也是 WebSocket 服务端。 客户端与服务端使用二进制的WebSocket消息进行交互。 消息内容包括一个编码后的 header
,以及一个 二进制编码的 Protobuf 消息 data
(见 WebSocket Message Format).
客户端从agent端发送 AgentToServer 消息,服务端发送 ServerToAgent 的Protobuf 格式的消息:
┌────────────\ \────────┐ ┌──────────────┐
│ / / │ Data:AgentToServer │ │
│ \ \ OpAmp ├───────────────────────►│ │
│ Agent / / │ │ Server │
│ \ \ Client │ Data:ServerToAgent │ │
│ / / │◄───────────────────────┤ │
└────────────\ \────────┘ └──────────────┘
WebSocket 消息格式
WebSocket 消息的格式如下:
┌────────────┬────────────────────────────────────────┬───────────────────┐
│ header │ Varint encoded unsigned 64 bit integer │ 1-10 bytes │
├────────────┼────────────────────────────────────────┼───────────────────┤
│ data │ Encoded Protobuf message, │ 0 or more bytes │
│ │ either AgentToServer or ServerToAgent │ │
└────────────┴────────────────────────────────────────┴───────────────────┘
未编码的 header
是一个 64 bit无符号整数。WebSocket 消息中, 64 bit 未编码的 header
会使用 Base 128 Varint 格式编码成二进制数据。编码后的 header
数据长度可以是 1 到10字节 ,取决于编码前的header
内容。
当前版本的规范中,未编码的 header
值设定为 0 。其他的 header
值是保留值,用于未来的其他用途。会在后续版本的规范中定义。 OpAMP 的符合规范的 WebSocket 消息解码器需要检查 header
值是否等于0,如果不是,则判断这条WebSocket消息格式不正确。
data
字段包含AgentToServer 或 ServerToAgent消息经过 Protobuf 二进制格式编码后的字节码。
注意: header
和 data
字段都包含可变长度的二进制数据,用于header字段的Base 128 Varint 解码算法会根据它读取到的数据决定何时停止。
为了使用Protobuf解码逻辑来解码 data
字段,需要知道 data
字段的字节长度。要计算这个长度,需要从WebSocket消息的字节长度中减去Header的大小。
注意:按照 Protobuf 格式定义, 当 AgentToServer 或 ServerToAgent消息为空时(例如所有字段都未设置),data
字段的字节长度可以为0,这种场景是合法的。
WebSocket 消息传输
基于 WebSocket的OpAMP协议是一个异步全双工的消息交换协议。OpAMP客户端与服务端交互消息的顺序,在本规范中按照特定功能在相应章节中定义。
消息序列的第一条启动消息通常是由外部事件触发的。例如,建立连接之后,客户端发送一条AgentoServer 消息。这个案例中,“建立连接”是触发的外部事件,AgentToServer消息是启动消息。
客户端和服务端都能够发送一条启动消息,开启一个消息序列。
启动消息会触发对方发送一条或多条返回消息,这些返回消息会继续触发反方向的消息。这种消息交换会持续进行,直到达到了消息传输的目标,或者消息序列中报错失败。
注意:相同的消息在某些情况下是启动消息,而另一种情况下是其他消息触发的返回消息。不像其他协议,OpAMP协议中没有严格区分“请求”与“响应”消息类型。消息的角色基于这个消息序列的触发方式。
例如,AgentToServer消息会是一条客户端初次连接上服务端的启动消息;AgentToServer消息也可以是客户端发送给服务端的响应,当服务端对agent发起一次远程配置,agent接收到配置信息后会返回AgentToServer消息。
查看 Operation 章节以获得消息序列的详细信息。
WebSocket 传输经常被用于以下场景,服务端能够与agent进行实时通信,而不需要客户端向服务端轮询(http传输通常需要轮询)。
Plain HTTP 通信
OpAMP支持的第二种通信方式是纯HTTP连接。OpAMP客户端是一个http客户端,服务端是http服务端。客户端发送Post请求到服务器。Post请求的body中是一个 二进制序列化Protobuf 消息。客户端在请求body中发送Protobuf格式的 AgentToServer 消息,服务端在http响应中发送Protobuf 格式的ServerToAgent消息。
基于HTTP的OpAMP协议是一个同步的,半双工的消息传输协议。客户端需要传输AgentToServer消息时,会发起一个HTTP请求。服务端使用ServerToAgent消息来响应http请求,并返回要传输给客户端的信息。如果agent没有信息需要传输,客户端必须使用只设置了 instance_uid 字段的 AgentToServer 消息定时轮询服务端,这让服务端获得机会在http 响应中返回那些要发送给agent的数据(例如新的远程配置信息)。
agent端没有数据发送时,默认的轮询间隔是30秒。轮询间隔能够在客户端被配置。
使用Http时,消息序列与使用WebSocket时一样。唯一的差别是时机:
- 服务端想要发送数据给agent时,需要等待客户端的轮休请求,才能通过http响应返回给客户端;
- 客户端想要发送数据给服务端时,如果上一次请求还没有响应,客户端必须等到服务端响应完成才能发送下一条数据。注意,此时新的请求会在接收到响应的时候被立刻发出。客户端不需要等待轮询间隔时间到达。
使用http传输时,客户端必须设置请求header "Content-Type: application/x-protobuf" 。服务端接收到带有此header的HTTP请求时会判断使用了纯HTTP传输协议,否则会判断这次请求是WebSocket传输的启动消息。
客户端可以使用gzip压缩http请求body,此时必须指定header "Content-Encoding: gzip"。服务端实现需要保证支持 "Content-Encoding" header,并且支持gzip和无压缩的请求body。
当客户端请求header "Accept-Encoding"中表明支持压缩消息时,服务端可以压缩响应body。
AgentToServer 和 ServerToAgent 消息
AgentToServer 消息
OpAMP 协议的WebSocket消息体、HTTP消息体都是二进制Protobuf序列化消息。定义如下(本文档中的所有消息定义都遵循Protobuf 3 格式):
message AgentToServer {
string instance_uid = 1;
uint64 sequence_num = 2;
AgentDescription agent_description = 3;
uint64 capabilities = 4;
AgentHealth health = 5;
EffectiveConfig effective_config = 6;
RemoteConfigStatus remote_config_status = 7;
PackageStatuses package_statuses = 8;
AgentDisconnect agent_disconnect = 9;
uint64 flags = 10;
}
在对应章节中描述了服务端如何支持各个字段
AgentToServer.instance_uid
instance_uid 字段是一个全局唯一的标识符,用于标识所有运行中的Agent实例。Agent可以自己生成并尽量避免与其他agent的标识符冲突。instance_uid 在agent 进程的生命周期中需要保持不变。instance_uid 必须是一个 ULID 格式的,26个字符的标准字符串。
如果Agent想要使用由服务端生成的标识符,这个字段需要设置一个临时值,并且必须设置 RequestInstanceUid 标记。
AgentToServer.sequence_num
sequence_num 是一个客户端发送的每条 AgentToServer消息都会递增1的数字。当这条消息的sequence_num与前一条相比差别大于1时,服务端能够察觉到存在消息丢失的情况。更多详细信息见: Agent 状态压缩 章节。
AgentToServer.agent_description
agent_description是用于描述agent的数据,它的类型、运行环境等等。详情参见 AgentDescription 。当字段值不变的时候,应该取消设置AgentToServer消息中的此字段。
AgentToServer.capabilities
使用AgentCapabilities枚举值定义的二进制位掩码。AgentCapabilities中未定义的二进制位必须设置为0。允许对协议和AgentCapabilities枚举值进行扩展,扩展后旧Agent能够自动报告它们不支持新功能。此字段必须被设置。
enum AgentCapabilities {
// The capabilities field is unspecified. 未定义的
UnspecifiedAgentCapability = 0;
// agent能够上报自己的状态。必须为1.
ReportsStatus = 0x00000001;
// agent支持远程配置
AcceptsRemoteConfig = 0x00000002;
// agent能够上报当前生效的配置信息
ReportsEffectiveConfig = 0x00000004;
// The Agent can accept package offers.
AcceptsPackages = 0x00000008;
// The Agent can report package status.
ReportsPackageStatuses = 0x00000010;.
// agent能够上报自身的链路到ConnectionSettingsOffers.own_traces字段指定的位置
ReportsOwnTraces = 0x00000020;
// agent能够上报自身的指标信息到ConnectionSettingsOffers.own_metrics字段指定的位置
ReportsOwnMetrics = 0x00000040;
// agent能够上报自身的日志到ConnectionSettingsOffers.own_logs 字段指定的位置
ReportsOwnLogs = 0x00000080;
// 能够接受OpAMP的连接配置,通过ConnectionSettingsOffers.opamp 字段指定.
AcceptsOpAMPConnectionSettings = 0x00000100;
// 能够接受其他连接配置,通过ConnectionSettingsOffers.other_connections 字段指定.
AcceptsOtherConnectionSettings = 0x00000200;
// agent支持重启命令
AcceptsRestartCommand = 0x00000400;
// agent能够上报健康度信息,通过 AgentToServer.health 字段上报.
ReportsHealth = 0x00000800;
// agent能够通过AgentToServer.remote_config_status字段上报远程配置状态
ReportsRemoteConfig = 0x00001000;
// 在此处添加新的能力,使用最新的未设置的二进制位
}
AgentToServer.health
agent当前的健康状态,参见 AgentHealth message。如果与上次AgentToServer消息相同,可以省略此字段。
AgentToServer.effective_config
agent当前正在生效的配置。正在生效的配置,是指当前Agent正在使用的配置信息。有效配置可能与服务端先前接收到的远程配置信息不同,例如,Agent可能会使用本地配置作为替代(或作为补充)。详情参见 EffectiveConfig 。如果与上次AgentToServer消息相同,则不应该设置这个字段。
AgentToServer.remote_config_status
上次从服务端接收到的远程配置状态。详情参见 RemoteConfigStatus 。如果上次AgentToServer消息后没发生变化,则不应该设置这个字段。
AgentToServer.package_statuses
agent端的软件包列表,包括软件包的状态。如果上次AgentToServer消息后没发生变化,则不应该设置这个字段。
AgentToServer.agent_disconnect
从客户端发送给服务端的最后一条AgentToServer消息中,必须设置AgentDisconnect。
AgentToServer.flags
AgentToServerFlags中定义的二进制掩码。
enum AgentToServerFlags {
FlagsUnspecified = 0;
// Flags字段是一个二进制掩码。它各个位的值定义如下:
// agent要求服务端生成一个新的instance_uid。会在ServerToAgent 消息中返回。
RequestInstanceUid = 0x00000001;
}
ServerToAgent 消息
WebSocket消息体或者HTTP消息体都是一个二进制的Protobuf格式序列化后的ServerToAgent消息。
ServerToAgent 消息是从服务端发出,用于响应客户端的AgentToServer消息,或者服务端主动发出用于传递消息。
如果服务端接收到一条AgentToServer 消息,但是服务端没有可用于返回的数据,仍然需要返回一条ServerToAgent 消息,此时除了 instance_uid 之外的所有字段都不应设置。(这种情况下ServerToAgent只作为一条确认信息).
接收到ServerToAgent消息后,Agent必须处理。 处理流程基于消息中设置了哪些字段。详情参见规范中的对应章节。
Agent执行完成后,需要上报执行状态到服务端。Agent可以在完全执行了ServerToAgent中的指令后发送一个状态报告,也可以在执行到ServerToAgent消息中的特定环节时,按照执行进度发送多个状态报告(例如Downloading Packages)。多个状态报告能够描述一个耗时很长的执行过程,服务端能够获知任务的执行状态。
注意,服务端会使用ServerToAgent消息回复每个状态报告(或者在发生错误时使用ServerErrorResponse消息返回)。这些 ServerToAgent 消息会跟前一条消息内容相同,如果服务端发生变化,消息内容也会发生变化。Agent需要准备好处理这些额外的ServerToAgent 消息。
如果agent执行完后,状态没有发生变化,则客户端不能发送状态报告。
ServerToAgent 消息的结构如下:
message ServerToAgent {
string instance_uid = 1;
ServerErrorResponse error_response = 2;
AgentRemoteConfig remote_config = 3;
ConnectionSettingsOffers connection_settings = 4;
PackagesAvailable packages_available = 5;
uint64 flags = 6;
uint64 capabilities = 7;
AgentIdentification agent_identification = 8;
ServerToAgentCommand command = 9;
}
ServerToAgent.instance_uid
Agent实例标识符。必须与客户端发送的AgentToServer消息中的 instance_uid 字段一致。服务端使用同一条WebSocket连接与多个客户端通信时,这个字段有助于判断哪个Agent能够接受这条消息。
注意:服务端发送的AgentIdentification字段能够覆盖这个字段的值,这种情况发生的时候,agent必须更新自身的instance_uid字段并在后续的通信中使用。
ServerToAgent.error_response
服务端处理AgentToServer消息时,如果发生了错误,则需要设置error_response 字段。 设置了error_response字段之后,不能带上后续其他字段。反之亦然,后续字段设置了之后, 不能带上error_response字段。
ServerToAgent.remote_config
服务端有要提供给agent的远程配置时,必须设置此字段。详情参见 Configuration 。
ServerToAgent.connection_settings
服务端想要agent更新它的连接设置时(目标、header、认证信息等),必须带上此字段。详情参见 Connection Settings Management 。
ServerToAgent.packages_available
当服务端有软件包需要传输给agent时需要设置这个字段。详情见Packages 。
ServerToAgent.flags
定义在ServerToAgentFlags中的二进制掩码。
当客户端最近一条AgentToServer消息中没有包含特定的数据,并且服务端也不具有该数据时(例如服务端重启导致agent状态丢失),服务端能够使用Report*
标记让客户端上报数据。 详情参见 this section。
enum Flags {
FlagsUnspecified = 0;
// Flags 是一个二进制掩码,它的值定义如下:
// ReportFullState flag can be used by the Server if the Client did not include
// some sub-message in the last AgentToServer message (which is an allowed
// optimization) but the Server detects that it does not have it (e.g. was
// restarted and lost state). The detection happens using
// AgentToServer.sequence_num values.
// 服务端要求客户端通过一个新的完整的AgentToServer消息来上报完整的状态。
ReportFullState = 0x00000001;
}
ServerToAgent.capabilities
在ServerCapabilities枚举类中定义的二进制掩码,所有未定义的位必须设置为0。后续扩展协议以及ServerCapabilities枚举值的时候,旧版本的agent能够自动上报它们不支持新功能。在服务端发送的第一条ServerToAgent消息中必须设置这个字段。后续的消息序列中ServerToAgent消息可以将这个字段设置为UnspecifiedServerCapability。
enum ServerCapabilities {
// 表示这个值未设置
UnspecifiedServerCapability = 0;
// 服务端能够接收状态报告。这个字段必须为1。
AcceptsStatus = 0x00000001;
// 服务端能够提供远程配置功能
OffersRemoteConfig = 0x00000002;
// 服务端支持 AgentToServer中的EffectiveConfig信息
AcceptsEffectiveConfig = 0x00000004;
// 服务端能够提供软件包
OffersPackages = 0x00000008;
// 服务端能够接收软件包状态信息
AcceptsPackagesStatus = 0x00000010;
// 服务端能够提供通信配置
OffersConnectionSettings = 0x00000020;
// Add new capabilities here, continuing with the least significant unused bit.
}
ServerToAgent.agent_identification
agent标识符相关的属性信息,能够被服务端覆写。当设置了new_instance_uid 字段时,agent必须更新instance_uid,并在后续的通信中使用新的instance_id。 new_instance_uid 必须是一个 ULID 格式的 26 个字符的标准字符串。
message AgentIdentification {
string new_instance_uid = 1;
}
ServerToAgent.command
当服务端需要agent重启时,会设置此字段。除了 instance_uid 或 capabilities字段,这个字段不能与其他字段一起设置。其他字段会被忽略,agent会执行此命令。详情参见ServerToAgentCommand Message。
ServerErrorResponse 消息
消息结构如下:
message ServerErrorResponse {
enum Type {
UNKNOWN = 0;
BAD_REQUEST = 1;
UNAVAILABLE = 2
}
Type type = 1;
string error_message = 2;
oneof Details {
RetryInfo retry_info = 3;
}
}
ServerErrorResponse.type
这个字段定义了服务端处理agent请求时产生的错误类型。它的值有如下几种:
UNKNOWN: 未知错误。无法明确的错误。error_message 字段会包含错误详情信息。 BAD_REQUEST: 当且仅当AgentToServer消息格式错误时,会返回这个错误。参见 Bad Request . UNAVAILABLE: 服务器负载过高,无法处理请求。见Throttling.
ServerErrorResponse.error_message
错误消息内容,人类可读的信息。
ServerErrorResponse.retry_info
错误类型是UNAVAILABLE时,附带的重试信息 RetryInfo 。
ServerToAgentCommand 消息
消息结构如下:
// ServerToAgentCommand is sent from the Server to the Agent to request that the Agent
// perform a command.
message ServerToAgentCommand {
enum CommandType {
// The Agent should restart. This request will be ignored if the Agent does not
// support restart.
Restart = 0;
}
CommandType type = 1;
}
当服务端需要agent重启时,会发送ServerToAgentCommand 消息。这条消息只能包含 command, instance_uid, capabilities 三个字段。其他字段会被忽略。
Operation
状态报告
客户端必须发送状态报告:
- 客户端第一次连接上服务端的时候,作为第一条信息立刻发送。
- 后续agent每次发生状态变更的时候。
状态报告是以 AgentToServer 消息的形式发送。这些字段必须设置以反映相应的状态:agent_description, capabilities, health, effective_config, remote_config_status, package_statuses.
服务端必须相应这条 AgentToServer 消息,以 ServerToAgent 消息响应。 如果服务端处理状态报告失败,则在响应中必须设置 error_response 为ServerErrorResponse。 如果处理成功,则不能设置error_response 字段。其他字段可以按需设置。
下面的时序图用来表示状态报告的工作流程(假设服务端处理成功):
Client Server
│ │
│ │
│ WebSocket Connect │
├──────────────────────────────────────►│
│ │
│ AgentToServer │ ┌─────────┐
├──────────────────────────────────────►├──►│ │
│ │ │ Process │
│ ServerToAgent │ │ Status │
│◄──────────────────────────────────────┤◄──┤ │
│ │ └─────────┘
. ... .
│ AgentToServer │ ┌─────────┐
├──────────────────────────────────────►├──►│ │
│ │ │ Process │
│ ServerToAgent │ │ Status │
│◄──────────────────────────────────────┤◄──┤ │
│ │ └─────────┘
│ │
注意:agent在接收到服务端消息之后,状态可能会发生变化。例如,服务端可能发送一个远程配置消息到agent,agent处理这条请求后就会发生状态变化(例如agent的有效配置发生了变化)。这种状态变化会导致客户端发送一条状态报告到服务端。
因此,这种情况下时序图看起来会像这样:
Agent Client Server
│ │ ServerToAgent │
┌───────┤◄──────────────────────────────────────┤
│ │ │
▼ │ │
┌────────┐ │ │
│Process │ │ │
│Received│ │ │
│Data │ │ │
└───┬────┘ │ │
│ │ │
│Status │ │
│Changed│ AgentToServer │ ┌─────────┐
└──────►├──────────────────────────────────────►├──►│ │
│ │ │ Process │
│ ServerToAgent │ │ Status │
│◄──────────────────────────────────────┤◄──┤ │
│ │ └─────────┘
客户端接收到 ServerToAgent 消息时,客户端不能发送状态报告,除非客户端处理消息并导致了agent状态发生了实际变化。此时的时序图如下:
Agent Client Server
│ │ ServerToAgent │
┌──────┤◄──────────────────────────────────────┤
│ │ │
▼ │ │
┌────────┐ │ │
│Process │ │ │
│Received│ │ │
│Data │ │ │
└───┬────┘ │ │
│ │ │
▼ │ │
No Status │ │
Changes │ │
│ │
│ │
重要:如果客户端不遵循这个规则,可能会导致客户端和服务端之间的消息无限轮回。
Agent 状态压缩
客户端发送消息向服务端通知agent的状态。状态信息包括agent描述、有效配置、远程配置的状态、软件包状态。服务端会跟踪agent中状态,通过AgentToServer消息中的子字段。
客户端可以压缩AgentToServer消息,忽略子消息中上次上报之后未变化的字段。以下的字段支持这种压缩方式:AgentDescription, AgentHealth, EffectiveConfig, RemoteConfigStatus 以及 PackageStatuses。
压缩是以忽略AgentToServer的子字段的方式生效的。如果子字段中的任何属性发生了变化,就不能压缩这个子字段,并且这个子字段的所有属性都应该保留。
如果所有 AgentToServer 消息都可靠地传输到服务端,并且服务端都正确的处理了。那么这种压缩方式是安全的,服务端也能持续保存着客户端的最新状态。
但是,客户端与服务端有可能不同步,客户端认为服务端有最新状态但实际上服务端没有。这种情况是可能发生的,例如服务端在客户端发送AgentToServer消息时发生了重启,此时服务端已经停止运行,无法接收数据。
为了检测这种情况,并从中恢复,AgentToServer 消息中包含了 sequence_num 字段。这个字段是一个自增的整数,客户端每次发送AgentToServer消息时都会自增。
服务端接收到一条AgentToServer消息,并且sequence_num字段与上一条不是刚好大1的是,服务端能知道它没有接收到完整数据。
这种情况发生的时候,为了恢复同步,服务端必须要求agent上报那些省略的信息。此时服务端会发送一条ServerToAgent消息到agent,并设置flags中的 ReportFullState
为1.
AgentDescription 消息
AgentDescription 消息结构如下:
message AgentDescription {
repeated KeyValue identifying_attributes = 1;
repeated KeyValue non_identifying_attributes = 2;
}
AgentDescription.identifying_attributes
这个属性用于区分、标记agent。 键值对遵循OpenTelemetry的语义约定。见: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions
对于单独运行的Agent(比如opentelemetry-collector),以下字段必须指定:
- service.name,必须与agent自监控指标中使用的值相同
- service.namespace,agent运行的环境
- service.version ,agent的版本号
- service.instance.id,可以与agent的实例uid相同(等于ServerToAgent.instance_uid字段),或者由其他属性组成的一个唯一标识符。
- 其他任何必要的属性,用于唯一标识这个agent的自监控指标。
agent需要在自监控的Resource信息中包含上述属性信息。在接收这些自监控指标的系统中,这些用于标记的属性要能有效的唯一标记当前agent的自监控指标。
AgentDescription.non_identifying_attributes
这些属性不用于标识当前agent,但能够描述agent的运行环境。 需要包含以下属性:
- os.type, os.version - 描述agent运行的系统类型和版本
- host.* 描述agent运行的主机信息
- cloud.* 描述主机所在的云端环境
- 其他任何相关的能够描述agent以及它所在环境的资源属性
- 用户定义的,最终用户希望与之关联的其他属性
AgentHealth 消息
AgentHealth消息的结构如下:
message AgentHealth {
bool healthy = 1;
fixed64 start_time_unix_nano = 2;
string last_error = 3;
}
AgentHealth.healthy
如果agent正在健康运行,设置为true
AgentHealth.start_time_unix_nano
从agent启动时间戳。它的值是从00:00:00 UTC on 1 January 1970 开始的纳秒数。如果agent未启动,此字段必须设置为0.
AgentHealth.last_error
人类可读的错误信息。当healthy==false,agent处在错误状态时,这个字段必须设置。
EffectiveConfig 消息
EffectiveConfig 消息(有效配置消息)状态如下:
message EffectiveConfig {
AgentConfigMap config_map = 1;
}
EffectiveConfig.config_map
所有有效的配置信息。AgentConfigMap消息定义在 Configuration 章节
RemoteConfigStatus 消息
RemoteConfigStatus 消息结构如下:
message RemoteConfigStatus {
bytes last_remote_config_hash = 1;
enum Status {
// 表示未设置status的值
UNSET = 0;
// 远程配置已经加载到agent上了
APPLIED = 1;
// agent正在加载上次接收到的远程配置
APPLYING = 2;
// agent加载上次接收到的远程配置失败了。详情见error_message
FAILED = 3;
}
Status status = 2;
string error_message = 3;
}
RemoteConfigStatus.last_remote_config_hash
agent最后一次接收到的远程配置的哈希值,记录在AgentRemoteConfig.config_hash 字段中。服务端需要对比agent端上报的哈希值以及自己保存的哈希值,如果有差异,服务端必须在响应消息ServerToAgent中包含remote_config字段。
RemoteConfigStatus.status
agent端对上次接收到的远程配置的应用状态。
RemoteConfigStatus.error_message
如果status==FAILED, 可以带上错误信息。
PackageStatuses 消息
PackageStatuses 消息描述了agent当前以及曾经有过的软件包的状态。消息结构如下:
message PackageStatuses {
map<string, PackageStatus> packages = 1;
bytes server_provided_all_packages_hash = 2;
string error_message = 3;
}
PackageStatuses.packages
PackageStatus 消息的表映射关系,key是软件包的名称,key必须与 PackageStatus 消息中的name字段相同。
PackageStatuses.server_provided_all_packages_hash
agent从服务端接收到的PackagesAvailable消息中的所有软件包的聚合哈希。 服务端需要与自己保存的聚合哈希值做对比,如果不相同,则需要给agent发送一条PackagesAvailable消息。
PackageStatuses.error_message
如果在处理 PackagesAvailable message 消息过程中发生了错误,这个错误并不与特定某个软件包相关。处理过程没有发生错误,则不能设置这个字段。
PackageStatus 消息
PackageStatus消息结构如下:
message PackageStatus {
string name = 1;
string agent_has_version = 2;
bytes agent_has_hash = 3;
string server_offered_version = 4;
bytes server_offered_hash = 5;
enum Status {
INSTALLED = 0;
INSTALLING = 1;
INSTALL_FAILED = 2;
}
Status status = 6;
string error_message = 7;
}
PackageStatus.name
软件包名称字段,必需字段,并且必须与PackageStatuses 消息的key一致。
PackageStatus.agent_has_version
agent具有的软件包版本。 如果agent具有这个软件包,则必须设置。 如果agent没有这个软件包,则必须为空。例如,服务端提供了agent之前没有的软件包,但是agent安装失败。就会出现版本号为空的场景。
PackageStatus.agent_has_hash
agent端软件包的哈希值。 如果agent端有这个软件包,则必须设置。 如果agent端不具备,则必须留空。例如,服务端提供了的这个软件包在agent端没有,并且agent安装失败了,此时这个字段就应该留空。
PackageStatus.server_offered_version
服务端提供给agent端的软件包版本。 服务端通过前一条消息在agent端安装初始化完毕之后,这个字段必须设置。 如果agent端具有这个软件包,但它不是服务端提供,而是从agent本地安装的,则必须设置为空。 注意,agent_has_version 与 server_offered_version 这两个字段可能被设置为不同的两个值。例如,agent已经成功安装了某个版本的软件包,同时服务端提供了另一个版本,但agent安装新版本失败时,这种情况是有可能发生的。
PackageStatus.server_offered_hash
服务端提供给agent的软件包的哈希值。 从服务端提供的软件包安装初始化完毕后,这个字段必须设置。 如果agent从本地安装了这个软件包,而不是服务端提供的,则这个字段必须为空。 注意,agent_has_hash 和 server_offered_hash 这两个字段可能被设置为不同的值。比如,agent已经安装了某个版本的软件包,服务端提供了一个新版本,但是agent安装新版本失败了。
PackageStatus.status
软件包的状态。状态值包括: INSTALLED: 软件包在agent端安装成功。这种状态下,error_message 字段必须为空。 INSTALLING: agent正则下载和安装这个软件包。server_offered_hash字段必须设置,以表明正在安装的软件版本。这种状态下,error_message 字段必须为空。 INSTALL_FAILED: agent尝试安装软件包,但是安装失败了。server_offered_hash 字段必须设置以表明agent要安装的软件包版本。可以在error_message字段中包含此次失败的详细信息。
PackageStatus.error_message
错误状态下的错误详情。
管理连接配置项
OpAMP 协议允许服务端管理agent到各种目标的连接配置信息,以及OpAMP客户端的连接配置项。 下面的图标显示了一个典型的agent,它被OpAMP服务端管理,发送自监控指标到OTLP后台,连接到其他目标等:
┌────────────┬────────┐ ┌─────────┐
│ │ OpAMP │ OpAMP │ OpAMP │
│ │ ├──────────►│ │
│ │ Client │ │ Server │
│ └────────┤ └─────────┘
│ │
│ ┌────────┤ ┌─────────┐
│ │OTLP │ OTLP/HTTP │OTLP │
│ Agent │ ├──────────►│Telemetry│
│ │Client │ │Backend │
│ └────────┤ └─────────┘
│ │
│ ┌────────┤
│ │Other ├──────────► Other
│ │ ├──────────►
│ │Clients ├──────────► Destinations
└────────────┴────────┘
连接到OpAMP服务端或其他目标的时候,通常会让agent(或者OpAMP客户端)使用某种基于Header的权限机制(比如http header "Authorization",或者其他的自定义的连接token),以及用于 TLS 连接的客户端证书(也称为 双向 TLS)。
OpAMP 协议允许服务端提供这些连接的配置项,agent端可以使用或者拒绝这些数据。这种机制可以将agent引导到一个特定的目标,以及用于连接token/TLS认证的注册、吊销和轮换。
服务端能够为以下三类目标提供连接配置:
- OpAMP 服务端。典型用法是用于管理认证信息,比如TLS认证或用于权限验证的请求header。 服务端可以向不同的OpAMP提供不同的目标端点,让它们连接到不同的OpAMP服务端。
- agent上报自监控指标(指标、链路、日志)的目标。
- 使用不同名称的其他连接。agent使用这些配置的方式各不相同。通常情况下名称表示连接目标。例如,opentelemetry collector可以在exporter中使用这种命名连接配置,一个连接配置对应一个exporter。
当agent在AgentToServer.capabilities字段中声明支持某个能力,服务端能够可以提供对应类型的连接配置:
- 如果具备 ReportsOwnTraces 能力,服务端可以在own_traces字段中提供链路上报配置。
- 如果具备 ReportsOwnMetrics 能力,服务端可以在own_metrics字段中提供指标上报配置。
- 如果具备 ReportsOwnLogs 能力,服务端可以在own_logs 字段中提供日志上报配置.
- 如果具备 AcceptsOpAMPConnectionSettings 能力,服务端可以在opamp字段中提供OpAMP连接配置。
- 如果具备 AcceptsOtherConnectionSettings 能力,服务端可以在other_connections 字段中提供其他连接的配置信息。
各种连接配置的操作顺序完全不同。自监控指标的联机配置处理方式在 Own Telemetry Reporting 章节中描述。其他目标的连接设置处理方式在Connection Settings for "Other" Destinations。 OpAMP 连接配置处理方式如下。
OpAMP 连接配置处理流程
OpAMP 连接配置发生过程:
Client Server
│ │ Initiate
│ Connect │ Settings
├──────────────────────────────────────►│ Change
│ ... │ │
│ │◄───────┘
│ │ ┌───────────┐
│ ├─────────►│ │
│ │ Generate │Credentials│
┌───────────┐ │ServerToAgent{ConnectionSettingsOffers}│ and Save │ Store │
│ │◄───────┤◄──────────────────────────────────────┤◄─────────┤ │
│Credentials│ Save │ │ └───────────┘
│ Store │ │ Disconnect │
│ ├───────►├──────────────────────────────────────►│
└───────────┘ │ │
│ Connect, New settings │ ┌───────────┐
├──────────────────────────────────────►├─────────►│ │
│ │ Delete │Credentials│
┌───────────┐ │ Connection established │ old │ Store │
│ │◄───────┤◄─────────────────────────────────────►│◄─────────┤ │
│Credentials│Delete │ │ └───────────┘
│ Store │old │ │
│ ├───────►│ │
└───────────┘ │ │
- 服务端生成新的连接配置,保存在 服务端的认证中心,把新的配置信息与agent的实例UID关联起来。
- 服务端发送ServerToAgent 消息,带上ConnectionSettingsOffers 信息,opamp 字段中包含新的 OpAMPConnectionSettings .
- 客户端接收到配置信息,将新连接配置保存在本地存储中,将其标记为“候选”(如果客户端 崩溃,它将重试“候选”配置,即步骤5-9)
- 客户端断开与服务端的连接。
- 客户端使用新的配置连接到服务端。
- 连接成功后,通过了任何所需的TLS认证,服务端提示授权成功。
- 服务端使用agent实例UID从它自己的认证存储中删除旧连接配置。
- 客户端从它的存储中删除旧连接方式,将新的配置信息标记为“有效”。
- 如果第6步失败,客户端删除新配置,并且退回旧的配置信息,然后重连。
注意: 无法持久化保存新连接配置信息,或者只能临时保存数据的客户端,需要拒绝这种认证数据。否则它们可能会在重启后丢失认证数据。
首次使用时信任
想要使用客户端认证,但是没有获得认证信息的OpAMP 客户端,可以使用首次使用信任(Trust On First Use ,TOFU)流程。流程如下:
- 客户端使用常规TLS,没有客户端证书的情况下连接到服务端,客户端发送Agent的状态报告,以便于被服务端识别。
- 服务端接受连接和客户端状态,等待生成客户端证书。
- 服务器可以等待人工确认,或者可以自动确认所有TOFU请求,如果服务端配置了这个选项。
- 一旦确认,后续流程与OpAMP 连接配置流程 完全一样,除了没有旧的连接认证可供删除之外。
TOFU 流程允许在客户端不需要执行认证安装流程的情况下启动一个安全环境。
TOFU方法也能用在OpAMP客户端在没有获取到必需的授权header的情况下连接上服务端。服务端能够察觉这种连接,在确认后发送授权header到客户端。
首次使用时的注册
在一些用例中,需要给新安装的agent配备初始化连接参数,但要在新的连接创建之后重新生成连接认证。
它的实现方式与TOFU很类似。唯一的区别在于,首次连接会被正确地授权,但服务端会立刻生成并提供新的连接配置给agent。客户端会保存新的配置信息,并在子流程操作中使用。
这允许部署大量的使用了预定义的连接认证的agent,但他们能够在首次连接后获取到各自唯一连接认证。对单个agent的连接认证的撤销操作不会影响到其他agent。
连接撤销
当服务端获取到客户端使用的连接token或客户端证书时,服务端能够撤销单个agent的连接,通过把对应连接配置信息标记为“撤销”的方式。随后,服务端会拒绝使用了已撤销认证的客户端连接。
因为服务端能够控制3种目标的连接参数,这种撤销操作能够在3种目标上执行,前提是服务端提供了、客户端接受了特定类型的连接目标。
对于自监控和“其他”连接目标来说,服务端必须把撤销事件告知对应的通信目标,以便于他们能够在接收到使用了已撤销证书时拒绝连接。
生成认证
服务端生成的客户端证书可以是自签名的,私有CA证书机构签名的,或者是公共CA证书机构签名的。服务端负责生成客户端证书,它们会受到通信目标的信任。这要求其他通信目标记录自签名证书的公钥,或者信任提供签名的证书机构,以便于验证这个信任链。
客户端认证如何生成不在OpAMP规范的讨论范围内。
"其他"目标的连接参数配置
TODO 待定
ConnectionSettingsOffers 消息
ConnectionSettingsOffers 消息描述了agent端使用的连接配置内容:
message ConnectionSettingsOffers {
bytes hash = 1;
OpAMPConnectionSettings opamp = 2;
TelemetryConnectionSettings own_metrics = 3;
TelemetryConnectionSettings own_traces = 4;
TelemetryConnectionSettings own_logs = 5;
map<string,OtherConnectionSettings> other_connections = 6;
}
ConnectionSettingsOffers.hash
所有配置项的哈希值,包括那些未变更而省略了的配置项。
ConnectionSettingsOffers.opamp
用来连接OpAMP服务端的连接参数。如果这个字段为空,客户端需要假定配置未发生变化,继续使用已有的配置。客户端必须在接受这个配置信息前验证配置信息,以保证不会因为配置错误而丢失与OpAMP服务端的连接。
ConnectionSettingsOffers.own_metrics
用于连接到OTLP指标后台的连接参数。用于上报agent的自监控指标。如果这个字段为空,客户端会假定配置信息没有变化。
ConnectionSettingsOffers.own_traces
agent上报自身链路信息的OTLP后台的连接参数。如果字段为空,客户端会假定配置信息没有变化。
ConnectionSettingsOffers.own_logs
用于上报agent自己的log的OTLP后台的连接参数。如果为空,则客户端假定配置没有发生变化变化。
ConnectionSettingsOffers.other_connections
其他连接配置的集合。每个连接都有名称。客户端使用它们的方式各不相同。连接名称通常表示要连接的目标。如果这个字段为空,则假定other_connections 配置信息没有发生变化。
OpAMPConnectionSettings
OpAMPConnectionSettings 消息:服务端提供给OpAMP客户端,用于配置OpAMP连接的一系列字段的集合。
message OpAMPConnectionSettings {
string destination_endpoint = 1;
Headers headers = 2;
TLSCertificate certificate = 3;
}
OpAMPConnectionSettings.destination_endpoint
OpAMP 服务端 URL,必须是一个 WebSocket 或 HTTP URL,必须不能为空。例如 "wss://example.com:4318/v1/opamp"
OpAMPConnectionSettings.headers
用于连接的可选header。通常用于设置连接token,或者其他授权header。对基于HTTP的协议,客户端需要在请求中设置这些header。例如 key="Authorization", Value="Basic YWxhZGRpbjpvcGVuc2VzYW1l"
OpAMPConnectionSettings.certificate
客户端需要在连接中使用给定的认证证书,从现在开始。如果客户端能够验证并且使用给定证书连接上目标,则客户端需要清除这个链接之前的客户端证书。这是个可选字段,为空时客户端不能使用客户端证书。这个字段能够用来执行客户端证书撤销/轮换。
TelemetryConnectionSettings
TelemetryConnectionSettings 消息,是服务端提供给agent,用于设置上报自监控数据的网络连接的参数。
message TelemetryConnectionSettings {
string destination_endpoint = 1;
Headers headers = 2;
TLSCertificate certificate = 3;
}
TelemetryConnectionSettings.destination_endpoint
这个值必须是一个完整的OTLP/HTTP/Protobuf receiver,要以 "https://"开头,例如 "https://example.com:4318/v1/metrics"。如果以"http://"开头,agent可以拒绝发送自监控数据。
TelemetryConnectionSettings.headers
创建连接时可选的header。通常用于设置连接token或者其他授权header。在基于HTTP的协议中,agent会设置请求header。例如 key="Authorization", Value="Basic YWxhZGRpbjpvcGVuc2VzYW1l"
TelemetryConnectionSettings.certificate
agent需要使用给定的认证证书连接目标。如果使用给定的证书验证并连接成功,agent需要清除这个连接的已有证书。这是个可选字段:为空是表示客户端不能使用客户端证书。这个字段能够用于执行客户端证书的失效/轮换。
OtherConnectionSettings
OtherConnectionSettings 消息,服务端提供给客户端,用于创建网络连接到特定目标的参数集合。其中的所有字段都不是必需的。服务端可以只指定部分字段,表示服务端只改变这部分配置,其他字段保持不变。
例如,服务端可以发送一个ConnectionSettings 消息,只设置了certificate字段,这表示服务端想要客户端使用新的证书,向现有的目标地址发送数据,使用现有的header和其他配置信息。
引用了其他消息的字段,如果引用为空,则该字段视为空。
对于原始字段,我们基于“标识符”来表示这个字段是否为空。这是为了克服旧的protoc编译器不生成检查字段是否存在的缺陷。
message OtherConnectionSettings {
string destination_endpoint = 1;
Headers headers = 2;
TLSCertificate certificate = 3;
map<string, string> other_settings = 4;
}
OtherConnectionSettings.destination_endpoint
是一个 URL,host:port 或者其他表示方式。
OtherConnectionSettings.headers
创建连接时可选的header。通常用于设置连接token或者其他授权header。在基于HTTP的协议中,agent会设置请求header。例如 key="Authorization", Value="Basic YWxhZGRpbjpvcGVuc2VzYW1l"
OtherConnectionSettings.certificate
agent需要使用给定的认证证书连接目标。如果使用给定的证书验证并连接成功,agent需要清除这个连接的已有证书。这是个可选字段:为空是表示客户端不能使用客户端证书。这个字段能够用于执行客户端证书的失效/轮换。
OtherConnectionSettings.other_settings
其他配置信息,这些字段每个agent都不一样,完全由agent自己解析。
Headers 消息
message Headers {
repeated Header headers = 1;
}
message Header {
string key = 1;
string value = 2;
}
TLSCertificate 消息
消息中附带TLS证书,可以作为客户端证书。 (public_key,private_key) 证书对,必须是被CA证书机构签名,并且能够被目标服务器识别。 证书可以是自签名的,假设服务器能够验证这个证书。这种情况下 ca_public_key 字段可以为空
message TLSCertificate {
bytes public_key = 1;
bytes private_key = 2;
bytes ca_public_key = 3;
}
TLSCertificate.public_key
证书的 PEM 编码公钥。必填。
TLSCertificate.private_key
证书的 PEM 编码私钥。必填。
TLSCertificate.ca_public_key
证书机构的PEM-编码公钥, 可选字段。如果是通过CA证书机构签名的证书,则字段必填。可以存储在TLS终止代理中,以便于后续对客户端证书做验证。
自监控上报
自监控上报是OpAMP的一个可选能力。服务端可以提供上报自监控指标的目标。如果agent能够产生自监控数据,并且希望上报,那它可以将这些数据通过OTLP/HTTP协议上报到目标服务器。
┌────────────┬────────┐ ┌─────────┐
│ │ OpAMP │ OpAMP │ OpAMP │
│ │ ├──────────►│ │
│ │ Client │ │ Server │
│ └────────┤ └─────────┘
│ Agent │
│ ┌────────┤ ┌─────────┐
│ │OTLP │ OTLP/HTTP │ OTLP │
│ │ ├──────────►│ Metric │
│ │Exporter│ │ Backend │
└────────────┴────────┘ └─────────┘
服务端使用 ServerToAgent 消息以及其中的 connection_settings 字段提供上报目标。其中包含一个或多个 own_metrics, own_traces, own_logs 字段集合。每个这种字段都描述了一个使用OTLP协议接收监控数据的目标。
服务端需要在发送第一条ServerToAgent消息时带上 connection_settings字段,(通常是agent发送的第一条状态报告信息的响应),除非此时没有可用的OTLP后台。如果地址发生变化,服务端也需要在后续的ServerToAgent消息中带上这个字段。如果没有变化,则不应该设置connection_settings字段。 agent接收到ServerToAgent消息并且connection_settings为空时,会继续往之前给定的目标地址发送数据。
agent需要向目标定期上报自监控指标数据。建议的上报间隔是10秒。以下图表显示了操作流程:
Agent Client Server
Metric
│ │ │ Backend
│ │ServerToAgent{ConnectionSettingsOffer}│
┌─────────│◄─────────────────────────────────────┤ │
│ │ │
▼ │
┌────────┐ │
│Collect │ OTLP Metrics │ ──┐
│Own ├───────────────────────────────────────────────►│ │
│Metrics │ │ │
└────────┘ ... . │ Repeats
│ │
┌────────┐ │ │ Periodically
│Collect │ OTLP Metrics │ │
│Own ├───────────────────────────────────────────────►│ │
│Metrics │ │ ──┘
└────────┘ │
agent需要上报agent状态,包括进程指标以及其他自定义指标。agent必须遵守Opentelemetry的规范: conventions for processes.
类似的,agent需要上报自身的链路到 own_traces 字段给定的目标,自身的日志到own_logs 指定的目标。
AgentDescription信息的identifying_attributes 字段中的所有属性都应该被包含在上报OTLP监控数据的Resource属性中。
AgentDescription 消息的non_identifying_attributes 字段中指定的字段,也可以被包含在OTLP监控数据的resource属性中,它们的值需要保持一致。
配置
Agent 配置是OpAMP协议的一个可选功能。在必要的时候,可以禁用远程配置能力,例如使用k8s这种服务编排系统来提供配置信息时。
通过设置ServerToAgent消息的remote_config 字段,服务端给agent可以提供远程配置。因为ServerToAgent消息通常是agent端状态报告的响应消息,服务端能获取到agent描述信息,必要情况下能够为特定agent定制配置信息。
如果agent能够接受远程配置,OpAMP 客户端必须在AgentToServer.capabilities字段中设置AcceptsRemoteConfig,如果没有设置,服务端不能向客户端发送远程配置。
客户端实际使用的配置信息可能与服务端提供的远程配置信息不同。实际使用的配置信息称为agent的有效配置。有效配置通常是agent合并远程配置以及其他有效来源的配置信息得到的。
当有效配置生成之后,agent会在运行中使用它,客户端也会在状态报告的effective_config 字段向OpAMP服务端上报。服务端通常允许终端用户查看有效配置,以及客户端上报的状态报告中的其他信息。
如果agent能够上报有效配置,客户端必须在AgentToServer.capabilities字段中设置ReportsEffectiveConfig。如果没有设置,则服务端不期待 AgentToServer.effective_config 信息。
典型的配置流程如下:
Agent Client Server
│ │ AgentToServer{} │ ┌─────────┐
│ ├──────────────────────────────────►├──►│ Process │
│ │ │ │ Status │
Local │ Remote │ │ │ and │
Config │ Config │ ServerToAgent{AgentRemoteConfig} │ │ Fetch │
│ │ ┌────────┤◄──────────────────────────────────┤◄──┤ Config │
▼ │ ▼ │ │ └─────────┘
┌─────────┐ │ │
│ Config │ │ │
│ Merger │ │ │
└─────┬───┘ │ │
│ │ │
│Effective │ │
│Config │ AgentToServer{} │
└──────────►├──────────────────────────────────►│
│ │
│ │
如果发生了变化,AgentToServer消息需要包含EffectiveConfig 和 RemoteConfigStatus 字段。
注意:如果有效配置或其他字段没有变化,客户端不能发送AgentToServer消息。如果客户端不遵循这个规则,会导致客户端与服务端之间的无限消息循环。
服务端还可以不等待客户端的状态报告,自行发送远程配置信息。这可以用于对已启动但没有数据上报的agent的重新配置。它的流程图如下:
Agent Client Server
│ │ │
│ │ │
│ │ │ ┌────────┐
Local │ Remote │ │ │Initiate│
Config │ Config │ ServerToAgent{AgentRemoteConfig} │ │and │
│ │ ┌────────┤◄──────────────────────────────────┤◄──┤Send │
▼ │ ▼ │ │ │Config │
┌─────────┐ │ │ └────────┘
│ Config │ │ │
│ Merger │ │ │
└────┬────┘ │ │
│ │ │
│Effective │ │
│Config │ AgentToServer{} │
└──────────►├──────────────────────────────────►│
│ │
│ │
如果不想让配置被远程管理,agent可以忽略远程配置消息。
配置文件
agent的配置信息是一系列配置文件。对于远程配置和有效配置来说都是如此。
这个集合中的文件名必须唯一。远程配置和本地配置可能具有相同的文件名但是文件内容不同。它们的合并过程是agent需要考虑的,不是OpAMP协议的一部分。 如果只有一个配置文件,则文件名可以省略。
配置文件集合使用AgentConfigMap消息来表示:
message AgentConfigMap {
map<string, AgentConfigFile> config_map = 1;
}
AgentConfigSet消息中的config_map 字段,是配置文件的集合,key是文件名。
使用单个配置文件的agent,config_map需要包含一个映射关系,它的key可以是空字符串。
AgentConfigFile 消息表示一个配置文件,它的结构如下:
message AgentConfigFile {
bytes body = 1;
string content_type = 2;
}
body字段包含配置文件的原始二进制数据。二进制数据的内容、格式、编码都与agent相关,不在OpAMP的关注范围内。
content_type是一个可选字段。它是一个 MIME Content-Type ,用于描述body字段包含了什么内容,例如 "text/yaml"。 有效配置信息中的content_type有助于服务端在用户界面上展示上报的配置信息。
安全注意事项
远程配置功能是一个潜在的危险的功能,可能会被攻击者利用。例如,如果agent能够读取本地文件内容并通过网络上报,受损的OpAMP服务端可以发送一条有害的远程配置信息给agent,让他收集敏感文件并发送到网络上的指定位置。
查看 general recommendations 中的安全章节,以及给remote reconfiguration功能的特别建议。
AgentRemoteConfig 消息
消息内容如下
message AgentRemoteConfig {
AgentConfigMap config = 1;
}
软件包
每个agent都由一个或多个软件包组成。软件包具有自己的名称,以及保存在文件中的内容。软件包文件、软件包的功能、agent如何使用这些软件包,这些信息都是与agent本身相关,不在OpAMP协议的讨论范围内。
有2种软件包:顶层包和子包。
通常只有一个顶层软件包,他实现了agent的核心功能。如果顶层包只有一个,它的名称可以为空。
子包也可以称为插件。子包可以被安装到agent上,用于为agent增加功能。
agent可以包含一个或多个安装的软件包。每个软件包都有一个名称,agent的软件包名称不能相同。
不同的软件包可以有相同的文件名。文件名不是全局唯一的,它们只在特定软件包内部唯一。
软件包可以由本地提供并安装,也可以从服务端远程提供,远程提供的情况下agent会下载并安装这个软件包。
服务端可以设置ServerToAgent消息中的packages_available字段,将软件包远程提供给agent。服务端可以在agent的状态报告的响应中发送这个消息,也可以主动发送,以推送软件包到客户端。
PackagesAvailable 消息描述了服务端提供给agent的软件包信息。每个软件包都具有文件描述信息以及下载链接url。url指向下载服务器上的文件,下载服务器可以跟OpAMP服务端在同一个主机上,也可以在不同行的主机上。
协议支持的软件包只有一个对应的下载文件。如果agent的软件包由多个文件组成,agent和服务端可以将它们放在同一个文件中(如zip或tar文件)以供下载。下载完成后将多个文件解压出来。这个过程由agent定义,超出OpAMP规范的范围。
只有当OpAMP表明agent支持软件包时,通过设置gentToServer.capabilities字段中的AcceptsPackages 标记,服务端才能提供软件包信息。
下载软件包
接收到 PackagesAvailable 消息后,agent需要遵循以下的下载步骤:
Step 1
计算本地的所有软件包的聚合哈希,并与服务端发送的 all_packages_hash 字段对比。 如果聚合哈希相同则停止下载流程,这表示agent端的所有软件包都与服务端提供的软件包相同。否则执行步骤2:
Step 2
对于服务端提供的每个阮家宝,agent需要检查是否需要下载:
- 如果agent没有指定名称的软件包,则它需要下载。下载过程见步骤3;
- 如果agent有这个软件包,则需要对比本地软件包的哈希值,服务端提供软件包的哈希值在 PackageAvailable 消息的hash字段。 如果哈希值相同,则完成对这个软件包的操作。如果不相同,则执行步骤3中所示的下载过程。
最后,如果agent的软件包在服务端提供的列表中不存在,则需要从agent端删除。
Step 3
对于服务端提供的软件包文件,agent需要逐个检查是否需要下载:
- 如果agent上没有对应名称的文件,则需要下载
- 如果agent端有这个文件,则对比本地文件的哈希值,与服务端文件的哈希值。服务端文件哈希值在DownloadableFile 消息的hash字段中。如果哈希值一样,则不需要下载。否则,服务端提供的文件与本地文件不同,需要从download_url字段给出的下载地址下载这个文件。agent需要使用HTTP GET请求来下载文件。
以上的步骤允许agent只下载新的/变化了的软件包,或者只下载新的/变化了的文件。
下载完成后,agent可以执行任何额外的步骤,这跟agent类型相关,例如agent定义的“激活”或者“安装”流程。
软件包状态报告
下载和安装的过程中,agent可以定期上报状态。OpAMP客户端可以发送一条 AgentToServer 消息,按需设置其中的 package_statuses 字段。
下载安装流程完成之后(不管成功还是失败),客户端都需要向服务端上报所有软件包的状态。
下面是下载软件包和状态报告的一个典型流程:
Download Agent/Client OpAMP
Server Server
│ │ │
│ │ ServerToAgent{PackagesAvailable}│
│ │◄─────────────────────────────────┤
│ HTTP GET │ │
│◄────────────────┤ │
│ Download file #1│ │
├────────────────►│ │
│ │ AgentToServer{PackageStatuses} │
│ ├─────────────────────────────────►│
│ HTTP GET │ │
│◄────────────────┤ │
│ Download file #2│ │
├────────────────►│ │
│ │ AgentToServer{PackageStatuses} │
│ ├─────────────────────────────────►│
│ │ │
. . .
│ HTTP GET │ │
│◄────────────────┤ │
│ Download file #N│ │
├────────────────►│ │
│ │ AgentToServer{PackageStatuses} │
│ ├─────────────────────────────────►│
│ │ │
客户端必须在PackageStatuses消息中包含agent端上的所有软件包,包括已经存在的和正在处理的。
注意,客户端可以上报本地安装的软件包,而不仅仅是服务端提供的并且已下载的软件包。
计算哈希值
服务端和agent端使用哈希值来标识文件内容和软件包,因此agent能够决定要下载哪些文件或软件包。
服务端会执行哈希计算。服务端必须选择一种强哈希算法,减少碰撞概率。
agent端从不会计算哈希值。agent只存储哈希值并与服务端的数据做对比。
有3中层次的哈希值:
文件哈希
软件包文件内容的哈希值。保存在DownloadableFile 消息的 content_hash 字段中。agent使用这个值来判断特定文件是否与服务端相同,是否需要重新下载。
软件包哈希
软件包哈希用于标识整个软件包的哈希值(软件包名以及文件内容)。保存在PackageAvailable 消息的hash字段中。agent使用这个值来判断特定软件包是否与服务端相同,是否需要重新下载。
所有软件包的哈希值
是特定agent上所有软件包的聚合哈希值。通过聚合所有软件包名称和内容来计算。保存在 PackagesAvailable 消息的 all_packages_hash 字段中。agent使用这个字段来判断本地的所有软件包是否与服务端提供的软件包相同,是否需要重新下载。
注意,聚合哈希不包含agent本地安装,不是从服务端下载的软件包。
安全性注意事项
从远程下载软件包是一个潜在的危险功能,它可能会被恶意使用。如果软件包中包含某些可执行代码,损坏的OpAMP服务端可能会将恶意软件包提供给agent并用来执行任意代码。
查看 general recommendations 章节以及专门针对 code signing 能力的建议。
PackagesAvailable 消息
消息结构如下:
message PackagesAvailable {
map<string, PackageAvailable> packages = 1;
bytes all_packages_hash = 2;
}
PackagesAvailable.packages
软件包映射表,key是软件包名称。
PackagesAvailable.all_packages_hash
远程安装的所有软件包的聚合哈希值。
客户端需要在后续的PackageStatuses 消息中包含这个值。这让服务端可以判断这个agent是否具有新的软件包,并且在后续的ServerToAgent消息中包含这些软件包。
这个字段必须一直设置,当服务端支持发送软件包,并且agent具备接受软件包能力。
PackageAvailable 消息
服务端提供给agent的,用于安装新软件包,或者对agent已有的软件包启动升级或下载流程的消息。这个消息具有以下结构:
message PackageAvailable {
PackageType type = 1;
string version = 2;
DownloadableFile file = 3;
bytes hash = 4;
}
PackageAvailable.type
软件包类型,是一个插件还是一个顶层软件包。
enum PackageType {
TopLevelPackage = 0;
AddonPackage = 1;
}
PackageAvailable.version
服务端可用的软件包版本号。agent端可以利用这个字段来避免下载一个曾经安装失败的软件包。
PackageAvailable.file
软件包的可以下载的文件。
PackageAvailable.hash
软件包的哈希值。使用PackageAvailable消息中的其他所有字段,以及文件内容来计算。这个字段在agent端用于判断本地软件包是否与服务端提供的软件包相同。
DownloadableFile 消息
消息结构如下:
message DownloadableFile {
string download_url = 1;
bytes content_hash = 2;
bytes signature = 3;
}
DownloadableFile.download_url
使用Http GET请求下载文件的url。文件服务器需要支持范围请求以支持断点恢复。
DownloadableFile.content_hash
文件内容的SHA256 哈希值。agent使用这个字段判断文件下载过程是否出错。
DownloadableFile.signature
文件内容的可选签名字段。agent可以使用这个字段来判断下载文件的权限信息。例如可以是一个 detached GPG signature。 实际的签名算法和验证方式由agent指定。请参阅 Code Signing。
连接管理
建立连接
The Client connects to the Server by establishing an HTTP(S) connection.
If WebSocket transport is used then the connection is upgraded to WebSocket as defined by WebSocket standard.
After the connection is established the Client MUST send the first status report and expect a response to it.
If the Client is unable to establish a connection to the Server it SHOULD retry connection attempts and use exponential backoff strategy with jitter to avoid overwhelming the Server.
When retrying connection attempts the Client SHOULD honour any throttling responses it receives from the Server.
Closing Connection
WebSocket Transport, OpAMP Client Initiated
To close a connection the Client MUST first send an AgentToServer message with agent_disconnect field set. The Client MUST then send a WebSocket Close control frame and follow the procedure defined by WebSocket standard.
WebSocket Transport, Server Initiated
To close a connection the Server MUST then send a WebSocket Close control frame and follow the procedure defined by WebSocket standard.
Plain HTTP Transport
The Client is considered logically disconnected as soon as the OpAMP HTTP response is completed. It is not necessary for the Client to send AgentToServer message with agent_disconnect field set since it is always implied anyway that the Client connection is gone after the HTTP response is completed.
HTTP keep-alive may be used by the Client and the Server but it has no effect on the logical operation of the OpAMP protocol.
The Server may use its own business logic to decide what it considers an active Agent (e.g. an Client that continuously polls) vs an inactive Agent (e.g. a Client that has not made an HTTP request for a specific period of time). This business logic is outside the scope of OpAMP specification.
Restoring WebSocket Connection
If an established WebSocket connection is broken (disconnected) unexpectedly the Client SHOULD immediately try to re-connect. If the re-connection fails the Client SHOULD continue connection attempts with backoff as described in Establishing Connection.
Duplicate WebSocket Connections
Each Client instance SHOULD connect no more than once to the Server. If the Client needs to re-connect to the Server the Client MUST ensure that it sends an AgentDisconnect message first, then closes the existing connection and only then attempts to connect again.
The Server MAY disconnect or deny serving requests if it detects that the same Client instance has more than one simultaneous connection or if multiple Agent instances are using the same instance_uid.
The Server SHOULD detect duplicate instance_uids (which may happen for example when Agents are using bad UID generators or due to cloning of the VMs where the Agent runs). When a duplicate instance_uid is detected, Server SHOULD generate a new instance_uid, and send it as new_instance_uid value of AgentIdentification.
Authentication
The Client and the Server MAY use authentication methods supported by HTTP, such as Basic authentication or Bearer authentication. The authentication happens when the HTTP connection is established before it is upgraded to a WebSocket connection.
The Server MUST respond with 401 Unauthorized if the Client authentication fails.
Bad Request
If the Server receives a malformed AgentToServer message the Server SHOULD respond with a ServerToAgent message with error_response set accordingly. The type field MUST be set to BAD_REQUEST and error_message SHOULD be a human readable description of the problem with the AgentToServer message.
The Client SHOULD NOT retry sending an AgentToServer message to which it received a BAD_REQUEST response.
Retrying Messages
The Client MAY retry sending AgentToServer message if:
- AgentToServer message that requires a response was sent, however no response was received within a reasonable time (the timeout MAY be configurable).
- AgentToServer message that requires a response was sent, however the connection was lost before the response was received.
- After receiving an UNAVAILABLE response from the Server as described in the Throttling section.
For messages that require a response if the Server receives the same message more than once the Server MUST respond to each message, not just the first message, even if the Server detects the duplicates and processes the message once.
Note that the Client is not required to keep a growing queue of messages that it wants to send to the Server if the connection is unavailable. The Client typically only needs to keep one up-to-date message of each kind that it wants to send to the Server and send it as soon as the connection is available.
For example, the Client should keep track of the Agent's status and compose a AgentToServer message that is ready to be sent at the first opportunity. If the Client is unable to send the AgentToServer message (for example if the connection is not yet available) the Client does not need to create a new AgentToServer every time the Agent's status changes and keep all these AgentToServer messages in a queue ready to be sent. The Client simply needs to keep one up-to-date AgentToServer message and send it at the first opportunity. This of course requires the AgentToServer message to contain all changes since it was last reported and to correctly reflect the current (last) state of the Agent.
Similarly, all other Agent reporting capabilities, such as Addon Status Reporting or Agent Package Installation Status Reporting require the Client to only keep one up-to-date status message and send it at the earliest opportunity.
The exact same logic is true in the opposite direction: the Server normally only needs to keep one up-to-date message of a particular kind that it wants to deliver to the Agent and send it as soon as the connection to the Client is available.
Throttling
WebSocket Transport
When the Server is overloaded and is unstable to process the AgentToServer message it SHOULD respond with an ServerToAgent message, where error_response is filled with type field set to UNAVAILABLE. The Client SHOULD disconnect, wait, then reconnect again and resume its operation. The retry_info field may be optionally set with retry_after_nanoseconds field specifying how long the Client SHOULD wait before reconnecting:
message RetryInfo {
uint64 retry_after_nanoseconds = 1;
}
If retry_info is not set then the Client SHOULD implement an exponential backoff strategy to gradually increase the interval between retries.
Plain HTTP Transport
In the case when plain HTTP transport is used as well as when WebSocket is used and the Server is overloaded and is unable to upgrade the HTTP connection to WebSocket the Server MAY return HTTP 503 Service Unavailable or HTTP 429 Too Many Requests response and MAY optionally set Retry-After header to indicate when SHOULD the Client attempt to reconnect. The Client SHOULD honour the corresponding requirements of HTTP specification.
The minimum recommended retry interval is 30 seconds.
安全性
远程配置、可下载的软件包是明显的安全威胁。服务端可以发送危险配置、有害软件包会导致agent的不可预料的行为。这个章节中定义了一些减少agent端安全风险的建议。
本节中的准则对于实施是可选的,但对于敏感应用强烈建议采用。
一般性建议
建议agent遵循“零信任”模型,不自动信任任何远程配置或者服务端提供的信息。从服务端接收到的大数据,需要验证并清洗,以减少和避免恶意行为带来的伤害。我们建议如下:
- agent应该以最小权限运行,防止自身接触到敏感文件,或者执行高权限操作。agent不能以root用户运行,否则恶意攻击者可以利用agent完全控制这台机器。
- 如果agent能够采集本地数据,则需要限制它采集数据的路径。并且这种限制是在本地生效的,不能被远程配置修改。不遵循这个规则,远程配置功能会将agent所在机器上的敏感数据暴露出来。
- 如果agent能够执行所在机器上的外部代码,并且这个功能是通过配置开启的话,则agent需要限制这些代码所在的路径。并且这种限制需要通过本地修改生效,不能被远程配置修改,否则远程配置功能就可以在机器上执行有害代码。
配置限制
建议在agent端限制它能被远程配置强制执行的动作。
特别的,如果能够通过配置信息让agent在机器上采集器数据(在监控数据采集agent上很常见),我们建议在agent端增加限制,限制agent能够采集的目录和文件。接收到远程配置之后,agent必须验证远程配置的采集点位置,如果不匹配,agent可以完全拒绝或者部分拒绝这些远程配置。以避免收集这些被禁止的目录或文件内容。
类似的情况是,如果远程配置命令agent去执行机器上的某进程或脚本,我们建议增加agent端的限制,限制agent能够执行哪些文件夹中的可执行文件。
我们建议这种限制以“白名单”的形式呈现,而不是“黑名单”。这些限制可以是在agent中的硬编码,或者是终端用户可定义的本地配置文件。不能允许服务端发送的远程配置请求修改/覆盖这些限制文件。
可选的远程配置
建议“远程配置”功能不应在agent端默认开启,而是让用户选择是否开启。
代码签名
任何软件包的可执行代码都需要被签名,以防止某个有漏洞的服务端向客户端发送了有害代码。我们建议如下:
- 任何可以下载的可执行代码(例如可执行的软件包),都需要代码签名。代码签名方法和验证机制跟agent有关,不在OpAMP规范的讨论范围内。
- agent需要验证已下载文件中的可执行代码,以保证这些代码的签名正确。
- 可以使用文件内包含的签名信息对可下载代码进行签名,或者使用记录在DownloadableFile消息中的signature字段的外部签名。外部签名可以参考GPG signing 的例子。
- 如果使用CA证书来进行代码签名,建议CA证书机构以及它的私钥不在OpAMP服务器上。这样,有漏洞的OpAMP服务端就无法为有害代码提供签名认证。
- agent需要在最小必要权限情况下运行任何已下载的可执行代码(软件包、外部进程等),以避免这代码接触到敏感文件或者执行高权限行为。agent不能使用root用户执行已下载的代码。
Interoperability
Interoperability of Partial Implementations
OpAMP defines a number of capabilities for the Agent and the Server. Most of these capabilities are optional. The Agent or the Server should be prepared that the peer does not support a particular capability.
Both the Agent and the Server indicate the capabilities that they support during the initial message exchange. The Client sets the capabilities bit-field in the AgentToServer message, the Server sets the capabilities bit-field in the ServerToAgent message.
Each set bit in the capabilities field indicates that the particular capability is supported. The list of Agent capabilities is here. The list of Server capabilities is here.
After the Server learns about the capabilities of the particular Agent the Server MUST stop using the capabilities that the Agent does not support.
Similarly, after the Agent learns about the capabilities of the Server the Agent MUST stop using the capabilities that the Server does not support.
The specifics of what in the behavior of the Agent and the Server needs to change when they detect that the peer does not support a particular capability are described in this document where relevant.
Interoperability of Future Capabilities
There are 2 ways OpAMP enables interoperability between an implementation of the current version of this specification and an implementation of a future, extended version of OpAMP that adds more capabilities that are not described in this specification.
Ignorable Capability Extensions
For the new capabilities that extend the functionality in such a manner that they can be silently ignored by the peer a new field may be added to any Protobuf message. The sender that implements this new capability will set the new field. A recipient that implements an older version of the specification that is unaware of the new capability will simply ignore the new field. The Protobuf encoding ensures that the rest of the fields are still successfully deserialized by the recipient.
Non-Ignorable Capability Extensions
For the new capabilities that extend the functionality in such a manner that they cannot be silently ignored by the peer a different approach is used.
The capabilities fields in AgentToServer and ServerToAgent messages contains a number of reserved bits. These bits SHOULD be used for indicating support of new capabilities that will be added to OpAMP in the future.
The Client and the Server MUST set these reserved bits to 0 when sending the message. This allows the recipient, which implements a newer version of OpAMP to learn that the sender does not support the new capability and adjust its behavior correspondingly.
The AgentToServer and ServerToAgent messages are the first messages exchanged by the Client and Server which allows them to learn about the capabilities of the peer and adjust their behavior appropriately. How exactly the behavior is adjusted for future capabilities MUST be defined in the future specification of the new capabilities.
Performance and Scale
TBD
FAQ for Reviewers
What is WebSocket?
WebSocket is a bidirectional, message-oriented protocol that uses plain HTTP for establishing the connection and then uses the HTTP's existing TCP connection to deliver messages. It has been an RFC standard for a decade now. It is widely supported by browsers, servers, proxies and load balancers, has libraries in virtually all popular programming languages, is supported by network inspection and debugging tools, is secure and efficient and provides the exact message-oriented semantics that we need for OpAMP.
Why not Use TCP Instead of WebSocket?
We could roll out our own message-oriented implementation over TCP but there are no benefits over WebSocket which is an existing widely supported standard. A custom TCP-based solution would be more work to design, more work to implement and more work to troubleshoot since existing network tools would not recognize it.
Why not alwaysUse HTTP Instead of WebSocket?
Regular HTTP is a half-duplex protocol, which makes delivery of messages from the Server to the client tied to the request time of the client. This means that if the Server needs to send a message to the client the client either needs to periodically poll the Server to give the Server an opportunity to send a message or we should use something like long polling.
Periodic polling is expensive. OpAMP protocol is largely idle after the initial connection since there is typically no data to deliver for hours or days. To have a reasonable delivery latency the client would need to poll every few seconds and that would significantly increase the costs on the Server side (we aim to support many millions simultaneous of Agents, which would mean servicing millions of polling requests per second).
Long polling is more complicated to use than WebSocket since it only provides one-way communication, from the Server to the client and necessitates the second connection for client-to-Server delivery direction. The dual connection needed for a long polling approach would make the protocol more complicated to design and implement without much gains compared to WebSocket approach.
Why not Use gRPC Instead of WebSocket?
gRPC is a big dependency that some implementations are reluctant to take. gRPC requires HTTP/2 support from all intermediaries and is not supported in some load balancers. As opposed to that, WebSocket is usually a small library in most language implementations (or is even built into runtime, like it is in browsers) and is more widely supported by load balancers since it is based on HTTP/1.1 transport.
Feature-wise gRPC streaming would provide essentially the same functionality as WebSocket messages, but it is a more complicated dependency that has extra requirements with no additional benefits for our use case (benefits of gRPC like ability to multiplex multiple streams over one connection are of no use to OpAMP).
Future Possibilities
Define specification for Concentrating Proxy that can serve as intermediary to reduce the number of connections to the Server when a very large number (millions and more) of Agents are managed.
References
Agent Management
- Splunk Deployment Server.
- Centralized Configuration of vRealize Log Insight Agents.
- Google Cloud Guest Agent uses HTTP long polling.
Configuration Management
- Uber Flipr.
- Facebook's Holistic Configuration Management (push).
Security and Certificate Management
- mTLS in Go: https://kofo.dev/how-to-mtls-in-golang
- e2e audit https://pwn.recipes/posts/roll-your-own-e2ee-protocol/
- ACME certificate management protocol https://datatracker.ietf.org/doc/html/rfc8555
- ACME for client certificates http://www.watersprings.org/pub/id/draft-moriarty-acme-client-01.html
Cloud Provider Support
- AWS: https://aws.amazon.com/elasticloadbalancing/features/
- GCP: https://cloud.google.com/appengine/docs/flexible/go/using-websockets-and-session-affinity
- Azure: https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-websocket
Other
- Websocket Load Balancing
--
Copyright The OpenTelemetry Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Copyright The OpenTelemetry Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```