YARN 服务注册表

简介和概念

本文档介绍了一个 Hadoop 服务注册表,用于解决两个问题

  1. 客户端如何与 YARN 部署的服务及其构成此类服务的组件进行通信?
  2. 允许 Hadoop 核心服务进行注册和发现,从而减少配置参数并允许更轻松地移动核心服务。

服务注册和发现是分布式计算中长期存在的问题,可以追溯到施乐的 Grapevine 服务。此提议是用于定位由 YARN 部署的分布式应用程序的注册表,并确定与这些应用程序通信所需的绑定信息。

定义

服务:部署在 Hadoop YARN 集群中或可从该集群访问的潜在分布式应用程序。示例:Apache HBase、Apache hcatalog、Apache Storm。服务可以是短期的或长期的。

服务类:服务类型的名称,用作注册表中的路径,并与 DNS 兼容路径命名方案相匹配。示例:org-apache-hbaseorg-apache-hcatalog

组件:服务的分布式元素。示例:HBase 主节点、HBase 区域服务器和 HBase REST 服务器。

服务实例:应用程序的单个实例。例如,HBase 集群 demo1。如果服务的组件实例正在运行,则服务实例正在运行。这并不意味着分布式计算意义上的“活动”,而仅仅意味着进程正在运行。

组件实例:服务实例中组件的单个实例。示例:主机 rack1server6 上的 HBase 主节点或主机 rack3server40 上的区域服务器。

端点:与服务实例或组件实例绑定的一个方法。示例:HBase 的 Apache Zookeeper 绑定、区域服务器上的 Java JMX 端口、HBase 主节点上的 Web UI 以及 HBase REST 组件实例的 REST API。端点可以是内部的——用于服务实例内,或外部的:用于服务实例的客户端。

服务记录:注册表中描述服务实例或组件实例的记录,包括列出其端点。

YARN 资源管理器,“RM”:允许客户端应用程序向 YARN 集群提交工作(包括部署服务实例的请求)的 YARN 组件。RM 保留所有正在运行应用程序的状态。

YARN 应用程序:通过 YARN 部署的应用程序。每个应用程序实例都有一个唯一的应用程序 ID。

YARN 应用程序主节点,“AM”:由 RM 调度和部署的特定于应用程序的组件。它负责维护应用程序的内部状态,包括请求和管理此应用程序实例的所有其他组件实例。YARN RM 将检测 AM 的故障,并通过重新调度它来响应。

YARN 容器:为组件实例分配的资源,包括 CPU 和 RAM。AM 负责请求其组件所需的容器,并构建命令以将组件实例实例化到已分配的容器上。每个已分配的容器都有一个唯一的容器 ID。

绑定问题

Hadoop YARN 允许应用程序在 Hadoop 集群上运行。其中一些是批处理作业或查询,可以使用 YARN 的现有 API 使用其应用程序 ID 进行管理。此外,YARN 可以部署长期服务实例,例如 Apache Tomcat Web 服务器池或 Apache HBase 集群。YARN 将根据每个组件要求和服务器可用性在集群中部署它们。这些服务实例需要由客户端发现;传统上,它们的 IP 地址会注册在 DNS 或某些配置文件中——但在 YARN 部署的应用程序中这是不可行的,因为主机名和网络端口都无法预先知道。

因此,客户端无法轻松与动态部署的应用程序进行交互。

YARN 支持一个基础注册表,允许 YARN 应用程序主程序注册一个 Web URL 和一个 IPC 地址。但对于我们的目的来说还不够,因为它不允许注册任何其他端点,例如 REST URL、zookeeper 路径或应用程序主程序执行的任务的端点。此外,可以注册的信息被映射到 YARN 应用程序实例,即每当启动 YARN 应用程序时都会更改的唯一实例 ID。这使得无法通过对命名服务的静态引用来解析绑定信息,甚至无法探测当前不存在的服务实例。

用例

服务名称示例

核心 Hadoop 服务。

这些服务可以通过具有写入/services路径权限的帐户进行静态部署、动态部署,甚至可以注册从 Hadoop 集群内访问的远程服务

    /services/hdfs
    /services/yarn
    /services/oozie

属于各个用户的 YARN 部署服务。

    /users/joe/org-apache-hbase/demo1
    /users/joe/org-apache-hbase/demo1/components/regionserver1

注册用例

  1. 未在 YARN 下运行的 Hadoop 核心服务(示例:HDFS)可以注册以供发现。这可以通过服务或管理工具来完成。

  2. 由 YARN 部署的长生命周期应用程序自行注册以供客户端发现。注册数据旨在比应用程序主程序和服务实例的单个部署的生命周期更长。

  3. 服务的组件实例自行注册,发布内部绑定信息,例如 JMX 端口。

  4. YARN 部署的应用程序可以绑定到静态和动态的依赖服务实例。示例:Tomcat Web 池绑定到动态 HBase 服务实例“/users/joe/services/hbase/demo1”。

  5. 组件实例使用注册表绑定到其应用程序主程序的内部端点,并定期向其发送心跳。

不受支持的注册用例

  1. 短生命周期 YARN 应用程序会自动在注册表中注册,包括其所有容器。当作业终止时,会注销。具有大量容器的短生命周期应用程序会给注册表带来过大负载。所有 YARN 应用程序都将获得注册选项,但不会自动注册,并且必须建议应用程序作者不要注册短生命周期容器。

查找用例

  1. 客户端应用程序查找动态部署的服务实例,其用户、服务类和实例名称已知,例如 /users/joe/services/hbase/demo1,并检索连接到服务所需的信息

  2. 客户端应用程序查找静态部署的 Hadoop 服务,例如:/services/hdfs

  3. 应用程序主节点枚举所有已注册的组件实例,发现其列出的 JMX 端口,并初始化自己的 Web UI,提供指向这些端点的链接。

  4. 用户连接到 /users/joe/services/hbase/demo1 处的私有 HBase 服务实例。

  5. 用户连接到 /services/hbase 处的集群的 HBase 服务。

  6. 用户在 /net/cluster4/services/hdfs 处查找指向远程 Hadoop 集群的文件系统的绑定信息。注册信息包括远程文件系统的 webhdfs:// URL。

  7. 用户列出其 HBase 服务实例

    ls /users/joe/services/hbase
    
  8. 用户在集群中查找所有 Hbase 服务

    find -endpointField.api=org.apache.hbase
    
  9. 未来可能:通过 DNS 查找服务。

此注册建议旨在通过为应用程序提供注册其服务端点的方法以及为客户端提供查找服务端点的方法来支持这些用例。

服务注册表的主要要求

允许动态注册服务实例

  • 部署的 YARN 服务实例必须能够注册其绑定并被客户端发现。

  • 核心 Hadoop 服务实例必须能够注册其服务端点。

  • 如果服务移动或 HA 发生故障,则必须能够升级绑定。

  • 服务实例必须能够为服务发布各种端点:Web UI、RPC、REST、Zookeeper 等。此外,还必须能够注册证书,其他公共安全信息可以作为绑定的一部分发布。

注册表服务属性

  • 注册表必须具有高可用性。

  • 规模:大型集群中的许多服务和许多客户端。这将限制服务可以发布的数据量。

  • 普遍性:无论物理、虚拟还是云端,我们都需要在每个 YARN 集群中使用此功能。

  • 必须支持分层名称空间和名称。命名约定必须与 DNS 匹配,以便我们可以在项目的后期通过 DNS 协议访问名称空间。

  • 注册表 API 语言/协议

  • 跨语言:独立于任何语言;客户端语言 != 服务

  • 用于读取注册表数据的 REST API

访问控制

  • 所有人都可以读取

  • 写入受到限制,这样可以避免抢注和冒充。

远程可访问性:即使在只能通过 Apache Knox 访问或托管在云环境中的集群上,也支持远程访问。

非要求

  • 注册表不适用于活动性检测、领导者选举或执行应用程序本身的其它“共享协商状态”操作,除了可能在组件实例之间共享绑定信息。

  • 注册表不适用于存储任意应用程序状态或发布绑定信息以外的配置数据,以用于服务及其组件提供的端点。此类使用会使注册表超载并迅速达到 Zookeeper 允许的限制。

架构

我们提议一个基本注册服务,将字符串名称绑定到描述服务和组件实例的记录。我们计划使用 ZK 作为基本名称服务,因为它支持许多属性,我们选择 ZK 名称空间的一部分作为服务注册的根(默认:yarnRegistry)。

在此基本实现之上,我们构建我们的注册服务 API 和 YARN 将用于其服务的命名约定。注册表将通过注册表 API 访问,而不是直接通过 ZK - ZK 只是一个实现选择(尽管将来不太可能更改)。

  1. 通过将路径绑定到称为服务记录的值来注册服务。路径是分层的,并使用 / 作为根和分隔符。

  2. 服务记录作为持久性 znode 注册。这确保了记录在服务的计划内和计划外中断期间仍然存在,假设客户端代码对瞬态中断具有弹性。

  3. 每个服务实例的服务记录列出了该服务实例导出的各种协议的端点。

  4. 对于每个协议端点,它必须包含

    1. 协议名称,包括:Web、REST、IPC、zookeeper。(类型:字符串)

    2. 地址:用于查找此端点的具体详细信息

    3. 地址类型。这是绑定字符串的格式。(URL、ZK 路径、主机名:端口对)。对于预定义的协议,我们将定义绑定字符串必须是什么格式。示例:protocol==REST 表示绑定类型为 URLprotocol==IPC 绑定使用地址类型 host/port

    4. api。这是端点提供的 API,并且是特定于应用程序的。示例:org.apache.hadoop.namenodeorg.apache.hadoop.webhdfs

  5. 端点可能是外部的——供服务本身以外的程序使用,也可能是内部的——用于连接服务实例中的组件。它们将列在服务记录的不同部分中以区分它们。

  6. 核心服务将使用以下约定进行注册:/services/{servicename},例如 /services/hdfs

  7. YARN 服务应使用以下约定进行注册

    /users/{username}/{serviceclass}/{instancename}
    
  8. 组件实例应注册在以下位置

    /users/{username}/{serviceclass}/{instancename}/components/{componentname}
    
  9. 遵循此约定的每个用户的服务都必须具有唯一的服务类名称,

  10. 每个组件实例都必须具有对该服务实例唯一的名称。对于 YARN 部署的应用程序,这可以从容器 ID 中简单地派生出来。

对唯一名称的要求确保了服务实例或组件实例的路径必定是唯一的,并且可以通过列出服务类路径的所有子项来枚举特定服务类的所有实例。

注册表模型

服务条目必须是持久的——由 YARN 和其他工具负责确定何时删除服务条目。

路径元素

所有路径元素必须与 RFC1123 中定义的主机名路径中的小写条目相匹配;正则表达式为

([a-z0-9]|([a-z0-9][a-z0-9\-]*[a-z0-9]))

此策略将确保如果注册表层次结构曾经由 DNS 服务导出,则所有服务类和名称都将有效。

由于平台可能允许用户名中包含空格、高 Unicode 和其他字符,因此会产生用户名的复杂性。此类路径必须使用国际化 DNS 所用的 punycode 约定转换为有效的 DNS 主机名条目。

服务记录

服务记录包含一些基本信息,还可能包含内部和外部端点的空列表。

服务记录

服务记录包含一些基本信息和两个端点列表:一个列表用于服务的用户,一个列表用于应用程序内的内部使用。

名称 说明
类型:字符串 始终:“JSONServiceRecord”
说明:字符串 可读说明。
外部:端点列表 外部调用者的服务端点列表。
内部:端点列表 服务实例内内部使用的服务端点列表。

类型字段必须为 “JSONServiceRecord”。强制使用此字符串允许使用未来的记录类型并且允许在尝试使用 JSON 解析器解析数据之前快速拒绝缺少此字符串的字节数组。

YARN 持久性策略

属性 yarn:idyarn:persistence 指定哪些记录和任何子条目可以在关联的 YARN 组件完成后删除。

yarn:id 字段定义要匹配的应用程序、尝试或容器 ID;yarn:persistence 属性定义记录清理的触发器,并隐式定义 yarn:id 字段内容的类型。

这些属性使用前缀“yarn:”来表示它们依赖 Hadoop 集群的 YARN 层来实现策略。如果注册表以独立方式运行(这是完全可能的),则所有记录都将隐式持久化。

名称 说明 `yarn:id` 字段的内容
永久 记录将持续存在,直至手动删除。 (未使用)
应用程序 在 ID 字段中定义的 YARN 应用程序终止时删除。 应用程序 ID
应用程序尝试 在当前 YARN 应用程序尝试完成后删除。 应用程序尝试 ID
容器 在 ID 字段中的 YARN 容器完成后删除 容器 ID

清理应用程序、应用程序尝试或容器终止时的策略需要 yarn:id 字段与应用程序、尝试或容器相匹配。如果设置了错误的 ID,则不会进行清理——如果设置为不同的应用程序或容器,则将根据该应用程序的生命周期进行清理。

端点

名称 说明
api: URI 作为字符串 在绑定末尾实现的 API
protocol: 字符串 协议。示例:`http`、`https`、`hadoop-rpc`、`zookeeper`、`web`、`REST`、`SOAP`、...
addressType: 字符串 绑定的格式
addresses: List[Map[String, String]] 地址映射列表

所有字符串字段都有大小限制,以阻止服务在文本描述中隐藏复杂的 JSON 结构。

字段 addressType:地址类型

addressType 字段定义了条目的字符串格式。

具有单独的类型是因为工具(例如 Web 查看器)可以在无需识别协议的情况下处理绑定字符串。

格式 绑定格式
uri uri: 端点的 URI
hostname hostname: 服务主机
inetaddress hostname: 服务主机,port: 服务端口
path path: 通用 Unix 文件系统路径
zookeeper hostname: 服务主机,port: 服务端口,path: ZK 路径

在 zookeeper 绑定中,每个条目都表示仲裁组中的单个节点,hostnameport 字段定义了 ZK 实例的主机名和它正在侦听的端口。path 字段列出了应用程序要使用的 zookeeper 路径。例如,对于 HBase,这将引用包含有关 HBase 集群的信息的 znode。

addresses 列表中所有地址元素的路径必须相同。这确保了任何单个地址都包含足够的信息以连接到仲裁组并连接到相关 znode。

可以定义新的地址类型;如果不是标准,请使用字符序列 "x-" 作为前缀。

字段 api:API 标识符

API 字段必须包含一个 URI,用于标识端点的特定 API。这些字段必须对 API 唯一,以避免混淆。

建议采用以下策略为 API 提供唯一的 URI

  1. 使用 SOAP/WS-* 约定,使用指向定义服务的 WSDL 的 URL
  2. 指向定义 REST API 的 svn/git 托管文档的 URL
  3. classpath 架构,后跟应用程序中类或包的路径。
  4. uuid 架构,带有生成的 UUID。

希望为通用 API 定义标准 API URI。本文档中使用了两个这样的非规范 API

  • http://:面向人类的网站
  • classpath:javax.management.jmx:支持 JMX 管理协议(基于 RMI)的端点

服务条目的示例

以下是针对 YARN 部署的 tomcat 应用程序的服务条目的示例。

在创建和注册应用程序后,注册表如下所示

/users
  /devteam
   /org-apache-tomcat
     /test1
       /components
         /container-1408631738011-0001-01-000002
         /container-1408631738011-0001-01-000001

/users/devteam/org-apache-tomcat/tomcat-test 服务记录描述了整个应用程序。它将 URL 导出到负载均衡器。

{
  "description" : "tomcat-based web application",
  "external" : [ {
    "api" : "http://internal.example.org/restapis/scheduler/20141026v1",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [
     { "uri" : "http://loadbalancer/" },
     { "uri" : "http://loadbalancer2/" }
      ]
  } ],
  "internal" : [ ]
}

服务实例由两个组件实例构建,每个实例都用其容器 ID 转换为 DNS 兼容的主机名来描述。这些条目被标记为临时。如果这些条目在容器内设置,则当该容器被释放或组件发生故障时,这些条目将被自动删除。因此,其持久性策略声明为“3”,容器。yarn:id 字段标识容器,其完成将触发此条目的删除

/users/devteam/org-apache-tomcat/test1/components/container-1408631738011-0001-01-000001

{
  "yarn:id" : "container_1408631738011_0001_01_000001",
  "yarn:persistence" : "container",
  "description" : "",
  "external" : [ {
    "api" : "http://internal.example.org/restapis/scheduler/20141026v1",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [{ "uri" : "rack4server3:43572" }  ]
  } ],
  "internal" : [ {
    "api" : "classpath:javax.management.jmx",
    "addressType" : "host/port",
    "protocol" : "rmi",
    "addresses" : [ {
      "host" : "rack4server3",
      "port" : "48551"
    } ]
  } ]
}

组件实例列出其端点:公共 REST API 作为外部端点,JMX 地址作为内部端点。

/users/devteam/org-apache-tomcat/test1/components/container-1408631738011-0001-01-000002

{
  "registrationTime" : 1408638082445,
  "yarn:id" : "container_1408631738011_0001_01_000002",
  "yarn:persistence" : "container",
  "description" : null,
  "external" : [ {
    "api" : "http://internal.example.org/restapis/scheduler/20141026v1",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [ [ "http://rack1server28:35881" ] ]
  } ],
  "internal" : [ {
    "api" : "classpath:javax.management.jmx",
    "addressType" : "host/port",
    "protocol" : "rmi",
    "addresses" : [ {
      "host" : "rack1server28",
      "port" : "48551"
    } ]
  } ]
}

(假设的)负载均衡器可以使用此信息来枚举组件并构建组件实例列表以分派请求。同样,管理应用程序可以枚举所有可用的组件实例及其 JMX 端口,然后连接到每个端口以收集性能指标。

注册表 API

以下是 Java 应用程序中看到的注册表 API。API 是 ZK 操作之上的一个薄层,主要用于构建路径、读取、写入和更新条目,以及枚举子项。REST API 在服务器中实现,并使用此相同 API 来实现其 REST API。

列出的异常只是可能异常的一个子集——该接口仅列出具有特殊含义的异常。

所有写入操作都必须假设它们与具有 Zookeeper 客户端一致性视图的注册表服务进行通信;只读客户端必须假设其视图可能有点过时。

所有客户端都必须认识到注册表是一个共享资源,并且它可能会在动作序列期间发生改变。

注册表操作

public interface RegistryOperations extends Service {

  /**
   * Create a path.
   *
   * It is not an error if the path exists already, be it empty or not.
   *
   * The createParents flag also requests creating the parents.
   * As entries in the registry can hold data while still having
   * child entries, it is not an error if any of the parent path
   * elements have service records.
   *
   * @param path path to create
   * @param createParents also create the parents.
   * @throws PathNotFoundException parent path is not in the registry.
   * @throws InvalidPathnameException path name is invalid.
   * @throws IOException Any other IO Exception.
   * @return true if the path was created, false if it existed.
   */
  boolean mknode(String path, boolean createParents)
      throws PathNotFoundException,
      InvalidPathnameException,
      IOException;

  /**
   * Set a service record to an entry
   * @param path path to service record
   * @param record service record service record to create/update
   * @param createFlags creation flags
   * @throws PathNotFoundException the parent path does not exist
   * @throws FileAlreadyExistsException path exists but create flags
   * do not include "overwrite"
   * @throws InvalidPathnameException path name is invalid.
   * @throws IOException Any other IO Exception.
   */
  void bind(String path, ServiceRecord record, int createFlags)
      throws PathNotFoundException,
      FileAlreadyExistsException,
      InvalidPathnameException,
      IOException;

  /**
   * Resolve the record at a path
   * @param path path to service record
   * @return the record
   * @throws PathNotFoundException path is not in the registry.
   * @throws InvalidPathnameException the path is invalid.
   * @throws IOException Any other IO Exception
   */

  ServiceRecord resolve(String path) throws PathNotFoundException,
      InvalidPathnameException,
      IOException;

  /**
   * Get the status of a path
   * @param path path to query
   * @return the status of the path
   * @throws PathNotFoundException path is not in the registry.
   * @throws InvalidPathnameException the path is invalid.
   * @throws IOException Any other IO Exception
   */
  RegistryPathStatus stat(String path)
      throws PathNotFoundException,
      InvalidPathnameException,
      IOException;

  /**
   * Probe for a path existing.
   * This is equivalent to {@link #stat(String)} with
   * any failure downgraded to a
   * @param path path to query
   * @return true if the path was found
   * @throws IOException
   */
  boolean exists(String path) throws IOException;

 /**
   * List all entries under a registry path
   * @param path path to query
   * @return a possibly empty list of the full path names of
   * child entries
   * @throws PathNotFoundException
   * @throws InvalidPathnameException
   * @throws IOException
   */
   List<String> list(String path) throws
      PathNotFoundException,
      InvalidPathnameException,
      IOException;

  /**
   * Delete a path.
   *
   * If the operation returns without an error then the entry has been
   * deleted.
   * @param path path delete recursively
   * @param recursive recursive flag
   * @throws PathNotFoundException path is not in the registry.
   * @throws InvalidPathnameException the path is invalid.
   * @throws PathIsNotEmptyDirectoryException path has child entries, but
   * recursive is false.
   * @throws IOException Any other IO Exception
   *
   */
  void delete(String path, boolean recursive)
      throws PathNotFoundException,
      PathIsNotEmptyDirectoryException,
      InvalidPathnameException,
      IOException;

  /**
   * Add a new write access entry to be added to node permissions in all
   * future write operations of a session connected to a secure registry.
   *
   * This does not grant the session any more rights: if it lacked any write
   * access, it will still be unable to manipulate the registry.
   *
   * In an insecure cluster, this operation has no effect.
   * @param id ID to use
   * @param pass password
   * @return true if the accessor was added: that is, the registry connection
   * uses permissions to manage access
   * @throws IOException on any failure to build the digest
   */
  boolean addWriteAccessor(String id, String pass) throws IOException;

  /**
   * Clear all write accessors.
   *
   * At this point all standard permissions/ACLs are retained,
   * including any set on behalf of the user
   * Only  accessors added via {@link #addWriteAccessor(String, String)}
   * are removed.
   */
  public void clearWriteAccessors();
}

RegistryPathStatus

RegistryPathStatus 类总结了注册表中节点的内容。

public class RegistryPathStatus {

  /**
   * Short path in the registry to this entry
   */
  public String path;

  /**
   * Timestamp
   */
  public long time;

  /**
   * Entry size in bytes, as returned by the storage infrastructure.
   * In zookeeper, even "empty" nodes have a non-zero size.
   */
  public long size;

  /**
   * Number of child nodes
   */
  public int children;
}

安全性

注册表将允许服务实例只能在具有权限的路径下进行注册。YARN 将为用户创建具有适当权限的目录,以便用户部署的服务可以由服务实例的用户帐户注册。管理员还将创建具有适当权限的目录(例如 /services),以便核心 Hadoop 服务可以自行注册。

不会尝试限制对注册表信息的读取访问。服务将通过要求身份验证和授权来保护客户端的不当访问。服务记录中有一个范围字段,但这只是一个标记,表示“仅限内部 API”,而不是直接的安全限制。(这就是提出“内部”和“外部”,而不是“公共”和“私有”的原因)。

基本原理:无论如何,都可以通过端口扫描发现正在注册的端点。让所有内容都可供世界读取,可以让 REST API 拥有一个更简单的访问模型——并且与 DNS 一致。

在安全集群上,ZK 令牌续订可能会成为长期服务的难题——如果它们的令牌过期,它们的会话可能会过期。此类令牌的续订不属于 API 实现的一部分——我们可能需要添加一种方法来更新注册表操作类实例的令牌。

安全策略摘要

在非 Kerberos Zookeeper 集群中,不实施任何安全策略。

注册表旨在在由 Kerberos 管理的集群上得到保护。

  • 注册表根授予“系统帐户”完全权限:mapredhdfsyarn"rwcda";所有其他帐户和匿名访问都是只读的。

  • /users/services/ 的权限也受到类似限制

  • 安装可能会扩展或更改这些系统帐户。

  • 当属于用户的应用程序被调度时,YARN 应为该用户创建条目 /users/${username}

  • 此节点将具有对系统的完全访问权限;用户访问权限:“crd”。也就是说,他们可以创建或删除子节点,但不能写入其主页节点,也不能更改其权限。

  • 希望写入注册表的应用程序必须使用 SASL 连接通过 Zookeeper 进行身份验证,

  • 在用户路径中创建节点的应用程序必须在 ACL 列表中包含指定站点的系统帐户,并具有完全访问权限。

  • 在用户路径中创建节点的应用程序必须包含一个 ACL,其中

  • 在用户路径中创建节点的应用程序必须将其自己的用户身份声明为 sasl:user@REALM 条目。

  • 在用户路径中创建节点的应用程序可以添加额外的 digest: ACL 令牌,以便让其服务能够操作注册表的部分,而无需 Kerberos 凭据

基于摘要的身份验证避免了长期应用程序中凭据续订的问题。在启动时,可以将令牌传递给 YARN 应用程序,以便与 ZK 服务连接。然后,它可以创建或更新条目,包括它创建的节点权限中的机密摘要 ACL。因此,即使在凭据过期后,它仍保留一些访问权限。

请注意,要成功实现此操作,客户端需要回退会话,使用 SASL,而是使用身份验证 ID:密码凭据。

群集外和群集间访问

  1. 客户端应该能够访问另一个群集的注册表,以便访问该群集的服务。此需求的详细信息需要进一步完善。

  2. 诸如 Apache Knox 的防火墙服务可以检查已发布服务的内部集合,并发布其端点的子集。他们可以实现未来的 REST API。

限制

条目大小

Zookeeper 的默认限制为 1MB/节点。如果服务或组件的所有端点都存储在附加到该节点的 JSON 中,那么所有端点注册数据的总限制为 1MB。

为了防止这成为问题,客户端 API 应该对字段的最大长度实施严格限制,对 addressType、protocol 和 api 字段实施较低的限制,对 description 和 addresses 元素实施较长的限制,以及对 addresses 字段中的元素数量实施限制。

名称大小

为了在未来支持 DNS,所有路径元素必须限制为 63 个字节。对于非 ASCII 用户名,此限制意味着较短的路径可能是一个限制。

更新速率

ZK 集群中,较快的入口更改速率被视为反社交行为。实现可能会限制更新操作。

轮询速率

轮询注册表的客户端可能会受到限制。

完整的服务记录示例

以下是从 YARN 应用程序中检索到的服务记录(非规范)示例。

{
  "type" : "JSONServiceRecord",
  "description" : "Slider Application Master",
  "yarn:persistence" : "application",
  "yarn:id" : "application_1414052463672_0028",
  "external" : [ {
    "api" : "classpath:org.apache.slider.appmaster",
    "addressType" : "host/port",
    "protocol" : "hadoop/IPC",
    "addresses" : [ {
      "port" : "48551",
      "host" : "nn.example.com"
    } ]
  }, {
    "api" : "http://",
    "addressType" : "uri",
    "protocol" : "web",
    "addresses" : [ {
      "uri" : "http://nn.example.com:40743"
    } ]
  }, {
    "api" : "classpath:org.apache.slider.management",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [ {
      "uri" : "http://nn.example.com:40743/ws/v1/slider/mgmt"
    } ]
  }, {
    "api" : "classpath:org.apache.slider.publisher",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [ {
      "uri" : "http://nn.example.com:40743/ws/v1/slider/publisher"
    } ]
  }, {
    "api" : "classpath:org.apache.slider.registry",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [ {
      "uri" : "http://nn.example.com:40743/ws/v1/slider/registry"
    } ]
  }, {
    "api" : "classpath:org.apache.slider.publisher.configurations",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [ {
      "uri" : "http://nn.example.com:40743/ws/v1/slider/publisher/slider"
    } ]
  }, {
    "api" : "classpath:org.apache.slider.publisher.exports",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [ {
      "uri" : "http://nn.example.com:40743/ws/v1/slider/publisher/exports"
    } ]
  } ],
  "internal" : [ {
    "api" : "classpath:org.apache.slider.agents.secure",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [ {
      "uri" : "https://nn.example.com:52705/ws/v1/slider/agents"
    } ]
  }, {
    "api" : "classpath:org.apache.slider.agents.oneway",
    "addressType" : "uri",
    "protocol" : "REST",
    "addresses" : [ {
      "uri" : "https://nn.example.com:33425/ws/v1/slider/agents"
    } ]
  } ]
}

它发布了多个端点,包括内部和外部端点。

外部

  1. 用于客户端-AM 通信的 IPC 主机名和端口
  2. 指向 AM 的 Web UI 的 URL
  3. Web UI 下用于特定应用程序服务的 REST URL 系列。详细信息无关紧要,请注意它们使用特定于应用程序的 API 值来确保唯一性。

内部

  1. AM 为应用程序本身部署的容器提供的两个 REST API 的 URL。

在容器中运行的 Python 代理检索内部端点 URL 以与其 AM 通信。记录在容器启动时解析,并缓存直到出现通信问题。此时,将查询注册表以获取当前记录,然后尝试重新连接到 AM。

此处“连接”问题既包括“低级套接字/IO 错误”,也包括“HTTPS 身份验证失败”。代理使用双向 HTTPS 身份验证,如果 AM 失败且另一个应用程序开始在同一端口上侦听,则会触发身份验证失败,从而导致重新读取服务记录。