Mallux - 宁静致远

Consul 与 Registrator

[Doc] https://www.consul.io/docs/index.html

Consul 概述

Consul 简介

Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,Consul 的方案更 "一站式",内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等)。使用起来也较为简单。Consul 用 Golang 实现,因此具有天然可移植性(支持 Linux、windows 和 Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。其一致性协议采用 Raft 算法,用来保证服务的高可用。使用 GOSSIP 协议管理成员和广播消息,并且支持 ACL 访问控制。

值得一提的是,命令行超级好用的虚拟机管理软件 vgrant 也是 HashiCorp 公司开发的产品。

Consul 的使用场景

  • Docker 实例的注册与配置共享
  • CoreOS 实例的注册与配置共享
  • vitess 集群
  • SaaS 应用的配置共享
  • 与 confd 服务集成,动态生成 nginx 和 haproxy 配置文件

Consul 的优势

  • 使用 Raft 算法来保证一致性, 比复杂的 Paxos 算法更直接。
  • 支持多数据中心,内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟, 分片等情况等。zookeeper 和 etcd 均不提供多数据中心功能的支持。
  • 支持健康检查,Etcd 不提供此功能。
  • 支持 HTTP 和 DNS 协议接口,Zookeeper 的集成较为复杂,Etcd 只支持 HTTP 协议
  • 官方提供 Web 管理界面, Etcd 无此功能。

Consul 的架构

[Doc] https://www.consul.io/docs/internals/architecture.html

术语

  • agent:运行在 consul 集群所有节点上的核心服务,其可运行在 client 或 server 模式,通过 consul agent 命令来启动。由于所有节点必须运行 agent,指定节点运行于 client 或者 server 模式是很简单地,除非有其它的 agent 实例。所有的 agent 都能运行 DNS 或者 HTTP 接口,并负责运行时检查和保持服务同步。

  • client:一个 client 是一个 转发所有 RPC 到 server 的 agent 。这个 client 是相对无状态的。client 唯一执行的后台活动是加入 LAN gossip 池。这有一个最低的资源开销并且仅消耗少量的网络带宽。

  • server:一个 server 是一个有一组扩展功能的 agent ,这些功能包括 参与 Raft 选举,维护集群状态,响应 RPC 查询,与其他数据中心交互 WAN gossip 和转发查询给 leader 或者远程数据中心。每个数据中心至少有一个 agent 运行在 server 模式,推荐为 3 个或 5 个。

  • DataCenter:虽然数据中心的定义是显而易见的,但是有一些细微的细节必须考虑。例如,在 EC2 中,多个可用区域被认为组成一个数据中心?我们定义数据中心为一个私有的,低延迟和高带宽的一个网络环境。这不包括访问公共网络,但是对于我们而言,同一个 EC2 中的多个可用区域可以被认为是一个数据中心的一部分。

  • Consensus:在我们的文档中,我们使用 Consensus 来表明就 leader 选举和事务的顺序达成一致。由于这些事务都被应用到有限状态机上,Consensus 暗示复制 状态机的一致性

  • Gossip:Consul 建立在 Serf 的基础之上,它提供了一个用于多播目的的完整的 gossip 协议。Serf 提供 成员关系,故障检测和事件广播。更多的信息在 gossip 文档中描述。这足以知道 gossip 使用基于 UDP 的随机的点到点通信。

  • LAN Gossip:它包含所有位于同一个局域网或者数据中心的所有节点。

  • WAN Gossip:它只包含 Server。这些 server 主要分布在不同的数据中心并且通常通过因特网或者广域网通信。

  • RPC:远程过程调用。这是一个允许 client 请求 server 的请求/响应机制。

架构

上图中我们能看到有两个数据中心,标记为 “DATACENTER1” 和 “DATACENTER2”。Consul 对多数据中心有一流的支持并且希望这是一个常见的情况。

在每个数据中心,client 和 server 是混合的。一般建议有 3-5 台 server。这是基于有故障情况下的可用性和性能之间的权衡结果,因为越多的机器加入达成共识越慢。然而,并不限制 client 的数量,它们可以很容易的扩展到数千或者数万台。

同一个数据中心的所有节点都必须加入 gossip 协议。这意味着 gossip 协议包含一个给定数据中心的所有节点。这服务有几个目的:第一,不需要在 client 上配置 server 地址。发现都是自动完成的。第二,检测节点故障的工作不是放在 server上,而是分布式的。这是的故障检测相比心跳机制有更高的可扩展性。第三:它用来作为一个消息层来通知事件,比如 leader 选举发生时。

每个数据中心的 server 都是 Raft 节点集合的一部分。这意味着它们一起工作并选出一个 leader,一个有额外工作的 server。leader 负责处理所有的查询和事务。作为一致性协议的一部分,事务也必须被复制到所有其他的节点。因为这一要求,当一个非 leader 的 server 收到一个 RPC 请求时,它将请求转发给集群 leader。

server 节点也作为 WAN gossip Pool 的一部分。这个 Pool 不同于 LAN Pool,因为它是为了优化互联网更高的延迟,并且它只包含其他 Consul server 节点。这个 Pool 的目的是为了允许数据中心能够以 low-touch 的方式发现彼此。这使得一个新的数据中心可以很容易的加入现存的 WAN gossip。因为 server 都运行在这个 pool 中,它也支持跨数据中心请求。当一个 server 收到来自另一个数据中心的请求时,它随即转发给正确数据中心的一个 server。该 server 再转发给本地 leader。

这使得数据中心之间只有一个很低的耦合,但是由于故障检测,连接缓存和复用,跨数据中心的请求都是相对快速和可靠的。

Consul 的实现

Consul 使用 Consensus 协议提供一致性(Consistency)—— CAP 定义的一致性。Consensus 协议是基于 “Raft: In search of an Understandable Consensus Algorithm” 实现的。

Consul Protocol

Raft 算法

Raft 是基于 Paxos 的一致性算法。 与 Paxos 相比,Raft 被设计为具有更少的状态和更简单,更容易理解的算法。

Raft 算法是一种基于日志复制实现数据同步的高效算法,它在解决分布式系统中各个节点记录内容一致性问题的同时,也使得集群具备一定的容错能力。这种容错能力体现在两个方面。

  • 当集群中出现网络故障或少于半数节点发生故障时,仍可以保证其余大多数节点正运行。

  • 当集群中超过半数节点出现故障时,将导致集群不可用,但 Raft 算法依然可以保证节点中的数据不会出现错误的结果,从而为集群提供灾后备份和恢复的机会。

Raft 术语

  • Log:在 Raft 系统主要的工作单元为 Log entry。一致性的问题可以分解为复制日志。一个 log 是一个顺序的条目列表(entrylist)。如果所有成员都同意 log 的 entry 和顺序,我们认为日志是一致的。

  • FSM:有限状态机。FSM 是有限个状态的集合以及在这些状态之间的转移和动作等行为的数学模型。当应用新日志时,允许 FSM 在状态之间转换。 相同的日志序列的应用必须导致相同的状态,这意味着行为必须是确定性的。

  • Peer set:Peer set 是参与日志复制的所有成员的集合。对于 Consul 而言,所有的 server 节点 均属于本地数据中心的 Peer set。

  • Quorum:法定票数,取决于 Peer set 大多数成员。对于大小为 n 的 set,quorum 需要至少(n / 2)+1 个成员。例如,Peer set 有 5 个 server 节点,就需要至少 3 个节点才能形成 quorum。无论什么原因,只要 quorum 是无效的,那么 cluster 就会变为 unavailable,并且不会再有 log 提交。

  • Committed Entry:当条目永久存储在在法定数量的节点上,该条目才会被视为已提交(committed)。 一旦条目提交,它可以被应用。

  • Leader:在任何给定的时间,Peer set 选举一个节点作为 Leader。当 log commited 时,Leader 负责新 entry 处理、复制到 Followers,以及管理何时 entry 被视为已提交。

Raft 角色和状态机

Raft 集群中的每个节点都处于一种基于角色的状态机中。具体来说,Raft 定义了节点的三种角色:Follower、Candidata 和 Leader。

  • Leader(领导者)

Leader 节点在集群中有且仅能有一个,它负责向所有的 Follower 节点同步日志数据。

  • Follower(跟随者)

Follower 节点从 Leader 节点获取日志,提供数据查询功能,并将所有修改请求转发给 Leader 节点。

  • Candidate(侯选者)

当集群中的 Leader 节点不存在或失联后,其它 Follower 节点转换为 Candidata,然后开始新的 Leader 节点选举。

在节点初始启动时,所有节点的 Raft 状态机均处于 Follower 状态。当 Follower 在一定时间周期内没有收到来自 Leader 节点的心跳数据包时,节点会将自己的状态切换为 Candidate,并向集群中其它 Follower 节点发送投票请求,Follower 都会将自己的票投给收到的第一个投票请求节点。当 Candidate 收到来自集群超过半数节点的接收投票后,即成为新的 Leader 节点。Leader 节点将接收并保存用户发送的数据,并向其它的 Follower 节点同步日志。

Leader 节点依靠定时向所有 Follower 发送心跳数据包来保持其地位。当集群中的 Leader 节点出现故障失联时,Follower 中又会重新选举出新的节点,从而确保整个集群正常运作。

每成功选举一次,新 Leader 的 Team(任届)值都会比之前 Leader 的增加 1。当集群中由于网络或其它原因的故障出现分裂又重新合并时,集群中可能会出现多于一个的 Leader 节点,此时,Term 值更高的一个节点将成为真正的 Leader。

Raft in Consul

只有 Consul server 节点参与 Raft,并且组成 Peer set。 所有 clients 节点将请求转发到 server 节点。 此设计的部分原因是,随着更多成员被添加到 Peer set,法定票数(quorum )的大小也随之增加。 这就引入了性能问题,因为您可能正在等待数百台机器同意一个 entry,而不是少数。

开始时,单个 Consul 服务器被置于 bootstarp(引导) 模式,且在一个 Datacenter 中 只能有一个 server 节点 处于 bootstrap 模式(多 bootstrap 模式的 server 会使群集处于不一致的状态),当一个 server 处于 bootstrap 模式时,可以选举自己为 raft leader。一旦 lender 被选举,其他 server 节点可以以保持一致性和安全性的方式添加到集群中。最终,一旦添加了指定数目的(-bootstrap-expect 选项指定)server 节点后,那么就可以禁用第一个节点的 “bootstarp” 模式。

Consistency Modes

虽然对复制日志的所有写入都通过 Raft,读取更灵活。 为了支持开发人员可能需要的各种权衡,Consul 支持 3 种不同的一致性模式进行读取。

  • Default-Raft

采用 Leader 租赁模式,提供了一个时间窗口,在该时间段内, Leader 角色是稳定的。但是,如果 Leader 从 Peers set 分裂出去,新的 Leader 就可能选举出来,而旧 Leader 持有租赁。这意味着有 2 个 leader 节点。因为旧 Leader 不能提交新日志,就没有脑裂的风险。然而,如果旧 Leader 执行任何读取操作,其读取到的结果就可能是过期的。Default 一致性模式只依赖于 Leader 租赁,Client 可能使用过期的数据。之所以这么权衡,因为读取是快速的,通常是采用强一致性模式,只有在一个难以触发的情况下过期。并且时间窗口也是有界的,时间到了,leader 也会下台。

  • consistent

这种模式是无条件一致性。它要求 leader 必须与 quorum 个 Peer 校验,虽然它仍然是 Leader。这将引入额外的节点轮询,增加了延迟。采用一致性读取,会导致额外的轮询开销。

  • stale

这种模式允许在任何 Server 节点执行读取操作,无论它是不是 Leader。这意味着可能读取到旧的数据,但一般而言,速度与 leader 相比差距在 50 毫秒内。这种方式,读取速度是非常快的,但可能是旧的数据。这种模式下,即使没有 Leader,一样可以相应读取操作。

有关使用这些各种模式的更多文档,请参阅 HTTP API

Bootstrapping a Datacenter

[Doc] https://www.consul.io/docs/guides/bootstrapping.html

一个 agent 节点可以运行在 client 和 sever 模式。server 节点负责运行协商一致协议,并存储集群状态。 client 节点大多是无状态的,并且严重依赖于 server 节点。

在 Consul 集群开始服务请求之前,必须有一个 server 节点被选举为 leader。 因此,被启动的第一节点必须是 server 节点。 Bootstrapping 是将这些初始 server 节点加入集群的过程。

推荐的 bootstrap 方式是使用 -bootstrap-expect 配置选项。 该选项表明:在一个 Datacenter 中期望提供的 server 节点数目,当该值提供的时候,consul 一直等到 server 节点达到指定数目时才会引导整个集群。这是为了防止不一致和裂脑情况(即多个服务器认为自己处于领导地位的集群),所有 server 节点应为 “-bootstrap-expect” 指定相同的值或完全不指定任何值。 只有指定值的 servers 将尝试引导集群。我们建议每个数据中心 3 或 5 个 server 节点。

创建集群

要触发 lender 选举,我们必须将这些 servers 节点一起加入并创建集群。 有两种选择加入:

  • 使用 HashiCorp 提供的 Atlas 自动加入,Atlas 是用于部署应用程序和管理基础架构的服务
  • 手动添加 servers 节点至集群中

Consul server 节点推荐至少在 3~5 个之间,这样可以最大限度地提高可用性,而不会大大牺牲性能。创建集群的方式,推荐的方法是一开始启动其中一台 server 节点,并且配置到 bootstrap 模式(指定 -bootstrap 选项,指示以 bootstrap 模式启动 agent,常用于手动引导,consul 0.4 版本以前只能手动引导;该选项和 -bootstrap-expect 非 bootstrap 模式自动引导是冲突的),该模式下节点可以指定自己作为 leader,而不用进行选举。然后再依次启动其他 server 节点,配置为非 bootstrap 模式。最后把第一个 server 的 bootstrap 模式停止,重新以非 bootstrap 模式启动,这样 server 之间就可以自动选举 leader。参见 https://www.consul.io/docs/guides/manual-bootstrap.html。

集群节点数目与容忍的故障节点数目:

Servers Quorum Size Failure Tolerance
1 1 0
2 2 0
3 2 1
4 3 1
5 3 2
6 4 2
7 4 3

Gossip in Consul

Consul 使用 gossip 协议管理成员关系、广播消息到整个集群。 所有这些都是通过使用 Serf 库提供的。Serf 使用的 gossip 协议是基于 “SWIM:可伸缩可传导的弱一致性进程组成员资格协议”,它用来维护分布式系统中进程成员资格的协议”。

Consul 使用两个不同的 goosip pool,分别把他们称为 局域网池(LAN Pool)广域网池(WAN Pool)

每个 Consul 数据中心都有一个包含所有成员(Server 和 Client)的 LAN gossip pool。

LAN Pool 有如下几个目的:首先,成员关系允许 Client 自动发现 Server 节点,减少所需的配置量。然后,分布式故障检测允许的故障检测的工作在某几个 Server 节点执行,而不是集中整个集群所有节点上。最后,gossip 允许可靠和快速的事件广播,比如,Leader 选举。

WAN Pool 是全局唯一的,无论属于哪一个数据中心,所有 Server 应该加入到 WAN Pool。由 WAN Pool 提供成员信息让 Server 节点可执行跨数据中心的请求。集成中故障检测允许 Consul 妥善处理整个数据中心失去连接,或在远程数据中心只是单个的 Server 节点。

所有这些功能都是通过利用 Serf 提供。从用户角度来看,它是作为一个嵌入式库提供这些功能。但其被 Consul 屏蔽,用户无需关心。作为开发人员可以去了解这个库是如何利用。

Lifguard 增强

SWIM 假定本地节点在 “数据包的软实时处理是可能的” 意义上是健康的。然而,当本地节点经历 CPU 或网络耗尽时,可以违反这种假设。结果是,serfhealth 状态就会“抖动” —— 摆来摆去,造成虚假报警,增加噪声遥测,简单可预见的结果就是 —— 导致集群浪费 CPU 和网络资源的来处理不存在的故障。

Network Coordinates

[Doc] https://www.consul.io/docs/internals/coordinates.html

Consul 使用 network tomography(网络层析成像) 系统来计算集群中的节点的网络坐标。这些坐标允许使用非常简单的计算来估计任何两个节点之间的网络往返时间。 这允许许多有用的应用,例如找到最接近请求节点的 server 节点,或故障切换到下一个最接近的数据中心中的 servers 节点。

所有这些都是通过使用 Serf 库提供的。 Serf 的 network tomography 是基于 “Vivaldi:分布式网络坐标系统”,其基于其他研究一些增强。

Network Coordinates in Consul

  • consul rtt 令可用于查询任意两个节点之间的网络往返时间。

  • Catalog endpointsHealth endpoints 可以使用 “?near=” 参数,基于来自给定节点的网络往返时间对查询的结果进行排序。

  • Prepared queries 可以根据网络往返时间自动将 services 故障转移到其他 Consul 数据中心。

  • Coordinate endpoint 暴露原始网络坐标以用于其他应用程序。

Consul 使用 Serf 管理两个不同的 gossip pool,一个用于具有给定数据中心成员的 LAN(所有 client 和 server 节点),一个用于仅由所有数据中心的 Consul server 节点组成的 WAN。 请注意,这两个 poll 之间的网络坐标不兼容。 LAN 坐标只有在与其他 LAN 坐标的计算中才有意义,WAN 坐标仅与其他 WAN 坐标有意义。

Sessions

[Doc] https://www.consul.io/docs/internals/sessions.html

Consul 提供了一个可用于 构建分布式锁 的会话(session)机制。 Session 充当节点、运行状况检查和键/值数据之间的绑定层。 它们旨在提供细粒度的锁定,并受到松散耦合的分布式系统的分布式锁服务(The Chubby Lock Service for Loosely-Coupled Distributed Systems)的大量启发。

Consul 中的 session 表示具有非常特定语义的 contract 。 当构建会话时,可以提供节点名称、健康检查列表、行为、TTL 和 锁定延迟。 新构造的 session 具有可用于识别它的命名 ID。 此 ID 可与 KV 存储一起使用以获取锁:用于互斥的咨询机制。

Consule 提供的 contract ,在下列任何情况下,会话将失效:

  • 节点已取消注册
  • 任何健康检查都被取消注册
  • 任何健康检查都进入临界状态
  • 会话被明确销毁
  • TTL 到期(如果适用)

当 session 无效时,会被销毁,不能再使用。 相关锁的操作取决于在创建时指定的行为。 Consul 支持 releasedelete 行为。 如果未指定任何内容,则 release 行为是缺省值。

ACLs

[Doc] https://www.consul.io/docs/internals/acl.html

Consul 提供了可选的访问控制列表(ACL)系统,可用于控制对数据和 API 的访问。ACL 是 Capability-based 的,依赖于可以应用细粒度规则的 token。 它在很多方面与 AWS IAM 非常相似。

作用范围

当在 Consul 0.4 中启动 ACL 系统时,只能为 KV 存储 指定策略。 在 Consul 0.5 中,ACL 策略扩展到 服务注册。 在 Consul 0.6 中,ACL 被进一步扩展以限制 服务发现机制,用户事件 和 encryption keyring 操作。

Anti-Entropy

[Doc] https://www.consul.io/docs/internals/anti-entropy.html

Consule 使用一个高级的方式来维护 service 和 health 信息的方法。Anti-Entropy(反熵 ) 机制被用来保证在不同节点上的备份(replica)都持有最新版本。

这里有两个概念性地描述,可以让我们对 Anti-Entropy 更容易理解。

  • Agent

每个 Consul agent 都维护自己的一套 service 和 check 注册以及健康信息。 Agent 负责执行自己的健康检查并更新其本地状态。

在 agent 的上下文中的 serivice 和 check 具有丰富的可用配置选项。这是因为 agent 负责通过使用健康检查来生成有关其 serivce 及其健康的信息。

  • Catalog

Consul service 发现由 service catalog(服务目录)支持。 此 catalog 是通过聚合 agent 提交的信息形成的。 Catalog 维护集群的 high-level view,包括哪些 service 可用,哪些 node 运行这些 service,运行状况信息等。 Catalog 用于通过 Consul 提供的各种接口(包括 DNS 和 HTTP)公开此信息。

与 agnet 相比,Catalog 上下文中的 service 和 check 具有更有限的字段集。 这是因为 Catalog 仅负责记录和返回有关 service、node 和运行状况的信息。

Catalog 仅由 server 节点维护。 这是因为 Catalog 是通过 Raft 日志复制的,以提供集群的统一和一致的 view。

Anti-Entropy

Entropy(熵,物理意义是体系混乱程度的度量)是系统变得越来越无序的趋势。 Consul 的反熵(Anti-Entropy)机制被设计为抵抗这种趋势,即使通过其组件的故障来保持群集的状态有序。

Consul 在全局 service catalog 和 agent 本地状态之间有明确的区分,如上所述。 反熵机制协调世界的这两个观点:反熵是本地 agent 状态和目录的同步。 例如,当用户向 agent 注册新 service 或 check 时,agent 进而通知 catalog 该新 check 存在。 类似地,当从 agent 中删除 check 时,也会将其从 catalog 中删除。

反熵也用于更新可用性信息。 agent 程序运行其运行状况检查时,其状态可能会更改,在这种情况下,其新状态将同步到 catalog。 使用此信息,catalog 可以基于其可用性智能地响应关于其节点和服务的查询。

在此同步期间,还会检查 catalog 的正确性。 如果 agent 不知道的 catalog 中存在任何 service 或 check,它们将被自动删除,以使 catalog 反映该 agent 的正确服务集和健康信息集。 Consul 将 agent 的状态视为权威;如果 agent 和 catalog view 之间存在任何差异,则将始终使用 agent 本地 view。

Periodic Synchronization

除了在 agent 发生更改时运行,反熵也是一个长时间运行的进程,它会定期唤醒同步 service,并检查 catalog 的状态。 这确保 catalog 与 agent 的真实状态紧密匹配。 这也允许 Consul 重新填充 service catalog,即使在完全数据丢失的情况下。

为了避免饱和,周期性反熵运行之间的时间量将基于簇大小而变化。 下表定义了集群大小和同步间隔之间的关系:

Cluster Size Periodic Sync Interval
1 - 128 1 minute
129 - 256 2 minute
257 - 512 3 minute
513 - 1024 4 minute
…… ……

上述间隔为近似值。 每个 Consul agent 将在间隔窗口内选择随机交错的开始时间,以避免一个惊群(thundering herd)。

Best-effort sync

在许多情况下,Anti-entropy 可能会失败,包括 agent 或其操作环境的配置错误,I/O 问题(整个磁盘,文件系统权限等),网络问题(agent 无法与 server 通信)等。 因此,agent 尝试以尽力而为的方式同步。

如果在 Anti-entropy 运行期间遇到错误,则会记录错误,并且 agent 继续运行。 Anti-entropy 机制周期性地运行以从这些类型的瞬态故障中自动恢复。

EnableTagOverride

可以部分修改服务注册的同步,以允许外部 agent 更改 services 的标记。 这在外部监视服务需要是标签信息的真实性的来源的情况下是有用的。 例如,Redis 数据库及其监视服务 Redis Sentinel 有这种关系。 Redis 实例负责其大部分配置,但 Sentinels 确定 Redis 实例是主实例还是辅助实例。 使用 Consul 服务配置项 EnableTagOverride,您可以指示运行 Redis 数据库的 Consul agent 在反熵同步期间不更新标记。 有关详细信息,请参阅 service 页面。

Security Model

[Doc] https://www.consul.io/docs/internals/security.html

Consul 依靠轻量级的 gossip 机制和 RPC 系统来提供各种功能。 这两个系统都有不同的安全机制,源于他们的设计。 然而,Consul 的安全机制有一个共同的目标:提供保密性、完整性和认证。

Gossip 协议由 Serf 提供支持,Serf 使用对称密钥或共享密钥密码系统。 有更多的细节在这里的 Serf 的安全。 有关如何在 Consul 中启用 Serf 的 gossip 的详细信息,请参阅 encryption doc here

RPC 系统支持使用端到端 TLS 以及可选的客户端身份验证。 TLS 是一种广泛部署的非对称密钥系统,是 Web 安全的基础。

这意味着 Consule 通信被防止窃听、篡改和欺骗。 这使得可以在不受信任的网络(如 EC2 和其他共享托管提供商)上运行 Consul。

thread model

  • 非成员访问数据
  • 由于恶意消息导致的群集状态操作
  • 由于恶意消息造成的假数据生成
  • 篡改造成状态变化
  • 拒绝对节点的服务

此外,我们认识到,可以长时间观察网络流量的攻击者可以推断集群成员。 Consul 使用的 gossip 机制依赖于向随机成员发送消息,因此攻击者可以记录所有目的地并确定集群的所有成员。

当在系统中设计安全性时,您可以将其设计为适合威胁模型。 我们的目标不是保护最高机密数据,而是提供一个 “合理” 的安全级别,这将需要攻击者提交大量的资源来击败。

Network Ports

有关配置网络规则以支持 Consul,请参阅 Ports Used 于 Consul 使用的网络端口列表以及有关使用哪些功能的详细信息。

Jepsen Testing

Jepsen 是一个由 Kyle Kingsbury 编写的工具,旨在测试分布式系统的分区容限。 它创建网络分区,同时用随机操作模糊系统。 分析结果以查看系统是否违反其声称具有的任何一致性属性。

作为 Consul 测试的一部分,我们进行了 Jepsen 测试,以确定是否可以发现任何一致性问题。 在我们的测试中,Consul 从分区优雅地恢复,而不引入任何一致性问题。

目前,使用 Jepsen 的测试是相当复杂的,因为它需要设置多个虚拟机,SSH 密钥,DNS 配置和一个工作的 Clojure 环境。 我们希望在上游贡献我们的 Consule 测试代码,并为 Jepsen 测试提供一个 Vagrant 环境。

Consul ACLs

ACL System Overview

ACL 系统被设计为易于使用、快速执行和灵活地执行新策略,同时提供可管理的能力。

每个 token 都有一个 IDNameTyperule set。 ID 是随机生成的 UUID,使其不可猜测。Name 对于 Consul 来说是不透明的,但对于人类来说是可读的。Type 区分为两种类型:client(此 token 不能修改 ACL 规则),management(此 token 允许执行所有操作)。

Token ID 与 每个 RPC 请求一起传递到服务器。 Consul 的 HTTP endpont 可以通过 token query string parameter 或带 X-Consul-Token 请求头来接受 token。Consul 的 CLI command 可以通过 token argumentCONSUL_HTTP_TOKEN 环境变量来接受 token。

ACL Rules and Scope

Token 绑定到一组控制 token 可以访问的 Consul 资源的规则集。根据 acl_default_policy 的配置,可以在 whitelist 或 blacklist 模式中定义策略。 如果默认策略是拒绝所有操作,则可以将 token 规则设置为将特定操作列入 whitelist 。 反之,允许所有默认行为是 blacklist,其中规则用于禁止操作。默认情况下,Consul 将允许所有操作。

下表汇总了可用于构建规则的 ACL 策略:

Policy Scope
agent Utility operations in the Agent API, other than service and check registration
event Listing and firing events in the Event API
key Key/value store operations in the KV Store API
keyring Keyring operations in the Keyring API
node Node-level catalog operations in the Catalog API, Health API, Prepared Query API, Network Coordinate API, and Agent API
operator Cluster-level operations in the Operator API, other than the Keyring API
query Prepared query operations in the Prepared Query API
service Service-level catalog operations in the Catalog API, Health API, Prepared Query API, and Agent API
session Session operations in the Session API

由于 Consul snapshot 实际上包含 ACL token,Snapshot API 需要一个用于 snapshot operations 的 management token,并且不使用特殊策略。

以下资源不会在 ACL 策略覆盖:

  • Status API 由服务器在 bootstrapping 和公开有关服务器的基本 IP 和端口信息时使用,并且不允许修改任何状态。
  • Catalog API 的数据中心 listing operation 同样公开了已知 Consu l数据中心的名称,并且不允许修改任何状态。

ACL Datacenter

所有节点(clinets 和 servers 端)都必须配置一个 acl_datacenter,这将启用 ACL 机制,但也指定了权威数据中心。Consul 凭据 RPC forwarding 来支持多数据中心配置。但是,由于可以跨数据中心边界进行请求,因此 ACL token 必须全局有效。为了避免一致性问题,单个数据中心被认为是权威的,并存储了标准的 token sets

当向非授权数据中心服务器中的 agent 发出请求时,必须将其解析为适当的策略。 这是通过从授权服务器读取 token 并将结果缓存到可配置的 acl_ttl 来完成的。缓存的含义是缓存 TTL 是强制执行的策略的过期的上限。 可以设置 0 值 TTL,但是这会对性能产生不利影响,因为每个请求都需要通过 RPC 调用刷新策略。

Configuring ACLs

ACL 使用几种不同的配置选项进行配置。它们被标记为是否在服务器、客户端或两者中设置。

Configuration Option Servers Clients Purpose
acl_datacenter REQUIRED REQUIRED Master control that enables ACLs by defining the authoritative Consul datacenter for ACLs
acl_default_policy OPTIONAL N/A Determines whitelist or blacklist mode
acl_down_policy OPTIONAL OPTIONAL Determines what to do when the ACL datacenter is offline
acl_ttl OPTIONAL OPTIONAL Determines time-to-live for cached ACLs

还有一些与 ACL replicationVersion 8 ACL support 相关的其他配置项。这些在下面的相应部分中讨论。

还可以配置一些 special(特殊) tokens,允许 bootstrapping(引导) ACL system,或在特殊情况下访问 Consul:

Special Token Servers Clients Purpose
acl_agent_master_token OPTIONAL OPTIONAL Special token that can be used to access Agent API when the ACL datacenter isn’t available, or servers are offline (for clients); used for setting up the cluster such as doing initial join operations, see the ACL Agent Master Token` section for more details
acl_agent_token OPTIONAL OPTIONAL Special token that is used for an agent’s internal operations, see the ACL Agent Token section for more details
acl_master_token REQUIRED N/A Special token used to bootstrap the ACL system, see the Bootstrapping ACLs section for more details
acl_token OPTIONAL OPTIONAL Default token to use for client requests where no token is supplied; this is often configured with read-only access to services to enable DNS service discovery on agents

In Consul 0.9.1 and later, the agent ACL tokens can be introduced or updated via the /v1/agent/token API.

ACL Agent Master Token

Outages and ACL Replication

Consul ACL 系统设计有灵活的规则,以适应 acl_datacenter 的中断或网络问题阻止访问它。 在这种情况下,非授权数据中心中的 server 可能无法解析 token。 Consul 提供了多个可配置的 acl_down_policy 选项来调整行为。 可以拒绝或允许所有操作或忽略缓存 TTL 并进入故障安全模式。 默认值是忽略任何先前解析的 token 的缓存 TTL,并拒绝任何未缓存的 token。

Consul 0.7 添加了一个 ACL 复制功能,可以允许非授权数据中心 server 解决即使是未缓存的 token 。 这通过在非授权数据中心的 server 上的配置中设置 acl_replication_token 来启用。 启用复制后,server 将在非权威 server 上维护权威数据中心的完整 ACL 集副本。 ACL 复制 token 需要是具有管理权限的有效 ACL token,它也可以与主 ACL token 相同。

复制运行在后台进程,大约每 30 秒寻找新的 ACL。 复制的更改以每秒 100 次更新的速率写入,因此可能需要几分钟时间才能执行大量 ACL 的初始同步。

如果存在影响授权数据中心的分区或其他中断,并且 acl_down_policy 设置为 “extend-cache”,那么将使用复制的 ACL 集合在中断期间解析 token。 ACL replication status 可用于监视复制进程的运行状况。

本地解析的 ACL 将使用非权威数据中心的 acl_ttl 设置进行缓存,因此这些条目可能会在高速缓存中保留到 TTL,即使权威数据中心恢复在线后也是如此。

ACL 复制还可以用于将 ACL 从一个数据中心迁移到另一个数据中心,方法如下:

  1. 在所有数据中心中启用 ACL 复制,以允许在迁移期间继续服务,并填充目标数据中心。 验证复制是否健康,并使用 ACL replication status 捕获目标数据中心中的当前 ACL 索引。
  2. 关闭旧的权威数据中心 servers
  3. 滚动重新启动目标数据中心中的 server,并将 acl_datacenter 配置更改为自身。 这将自动关闭复制,并将使数据中心能够使用之前的复制 ACL 启动作为权威数据中心。
  4. 滚动重新启动其他数据中心中的 server,并将其 acl_datacenter 配置更改为目标数据中心。

Bootstrapping ACLs

在新群集上引导 ACL 需要几个步骤,在本节中的示例中进行了概述。

Step 1:Enable ACLs on the Consul Servers

引导 ACL 的第一步是在 ACL 数据中心的 Consul servers 启用 ACL。

配置示例:

1
2
3
4
5
6
{
"acl_datacenter": "dc1",
"acl_master_token": "b1gs33cr3t",
"acl_default_policy": "deny",
"acl_down_policy": "extend-cache"
}

  • acl_datacenter:对数据中心启用 ACL system
  • acl_master_token:用于引导 ACL system,此 token 将自动创建为 “management” 类型的 token
  • acl_default_policy:启用 whitelist 模式
  • acl_down_policy:确定 ACL 数据中心 offline 要执行的操作

servers 需要重新启动以加载新配置。请注意一次启动一台 server,并确保每台 server 都已加入并正常运行,然后再启动另一台 server。acl_master_token 仅在 server 获得集群 lender 氏角色时才安装。如果您想要安装或更改 acl_master_token,请在所有 servers 的配置中为 acl_master_token 设置新值。一旦完成,重新启动当前 leader 以强制进行 leader 选举。

在 Consul 0.9.1 和更高版本中,您可以使用 /v1/acl/bootstrap API 来创建初始 master token,因此不需要将 token 放置到配置文件中。

一旦引导了 ACL system,就可以通过 ACL API 管理 ACL token。

Step 2:Create an Agent Token

透过 acl_master_token 可以创建拥有各种 ACL rule sets 的 token,将 token 应用于 agent client 端,就可以控制每个 agent 的对 API 的访问控制权限。

1
2
3
4
5
6
7
# cat acl.token
{
"ID": "e79622eb-5aae-4ce3-b5ad-a200b7107e35",
"Name": "Agent Token",
"Type": "client",
"Rules": "node \"\" { policy = \"write\" }\nservice \"\" { policy = \"write\" }\nkey \"\" { policy = \"read\" }"
}
1
2
3
4
[root@c7-Host sample]# curl -s -X PUT -H "X-Consul-Token: LgNnIeUcM0hyNiz/LiSEdA==" http://192.168.251.11:8500/v1/acl/create -d @acl.token
## 命令返回创建的 token 的 ID
{"ID":"e79622eb-5aae-4ce3-b5ad-a200b7107e35"}

Step 3:Set Tokens for UI Use

启用 ACL 机制后,默认时访问 Consul WebUI 会看不到任何数据。需要在 WebUI 的 settings 设置页面,引入有权限的 Token 后方可查看有权限访问的数据。

Step 4:Enable ACLs on the Consul Clients

1
2
3
4
5
6
# cat /data/consul/config/acl.json
{
"acl_datacenter": "gd2a",
"acl_default_policy": "deny",
"acl_token": "e79622eb-5aae-4ce3-b5ad-a200b7107e35"
}

Rule Specification

ACL 系统的核心部分是用于描述必须实施的策略的规则语言。

Key 策略是通过将前缀与策略相耦合来定义。 使用最长前缀匹配策略强制执行规则:Consule 选择可能的最具体的策略。 策略是 “read”、”write” 或 “deny”。 “write” 策略意味着也有 “读”,并且没有办法指定只 write。 如果没有适用的规则,则应用 acl_default_policy。

Service 策略通过耦合服务名称和策略来定义。 使用最长前缀匹配策略(这是 0.5 中的完全匹配,但在 0.5.1中更改)强制执行规则。 应用于任何不具有匹配策略的服务的默认规则是使用空字符串提供的。Service 为 “read”、”write” 或 “deny”。”write” 策略意味着也有 “read”,并且没有办法指定只 write。 如果没有适用的规则,则应用 acl_default_policy。Service ACL 规则中的 “read” 策略允许限制对该 service 前缀的发现的访问。 有关服务发现和 ACL 的更多信息,请参见下文。

"consul" service 的策略总是 “write”,因为它是由 Consul 内部管理。

User event 策略通过将事件名称前缀与策略相结合来定义。使用最长前缀匹配策略强制执行规则。 应用于没有匹配策略的任何用户事件的默认规则由空字符串提供。 事件策略是 “read”、”write” 或 “deny” 中的一个。 目前,在事件触发期间只实施 “write” 级别。 可以随时读取事件。

Prepared query 策略控制对创建、更新和删除 prepared queries 的访问。 在执行 prepared queries 时使用 Service 策略。 有关详细信息,请参阅下文。

我们利用 HashiCorp 配置语言(HCL) 来指定策略。 这种语言是人类可读的,且可与 JSON 互操作,使其易于机器生成。

Specification in the HCL format looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Default all keys to read-only
key "" {
policy = "read"
}
key "foo/" {
policy = "write"
}
key "foo/private/" {
# Deny access to the dir "foo/private"
policy = "deny"
}
# Default all services to allow registration.
# Also permits all services to be discovered.
service "" {
policy = "write"
}
# Deny registration access to services prefixed "secure-".
# Discovery of the service is still allowed in read mode.
service "secure-" {
policy = "read"
}
# Allow firing any user event by default.
event "" {
policy = "write"
}
# Deny firing events prefixed with "destroy-".
event "destroy-" {
policy = "deny"
}
# Default prepared queries to read-only.
query "" {
policy = "read"
}
# Read-only mode for the encryption keyring by default (list only)
keyring = "read"
# Read-only mode for Consul operator interfaces (list only)
operator = "read"

This is equivalent to the following JSON input:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
"key": {
"": {
"policy": "read"
},
"foo/": {
"policy": "write"
},
"foo/private": {
"policy": "deny"
}
},
"service": {
"": {
"policy": "write"
},
"secure-": {
"policy": "read"
}
},
"event": {
"": {
"policy": "write"
},
"destroy-": {
"policy": "deny"
}
},
"query": {
"": {
"policy": "read"
}
},
"keyring": "read",
"operator": "read"
}

Building ACL Policies

Blacklist Mode and consul exec

如果将 acl_default_policy 设置为 deny,则 anonymous token 将无权读取缺省的 _rexec 前缀。因此,使用 anonymous token 的 consul agent 将无法执行 consul exec 操作。

原因如下:agnet 需要对 _rexec 前缀的读/写权限,以便 consul exec 正常工作。 他们使用该前缀作为大多数数据的传输。

您可以通过允许 anonymous token 访问该前缀,从未配置 token 的 agent 中启用 consul exec。 这可以通过将此规则赋予 anonymous token 来实现:

1
2
3
key "_rexec/" {
policy = "write"
}

或者,您当然可以向每个 agent 添加显式 acl_token,使其能够访问该前缀。

Blacklist Mode and Service Discovery

如果 acl_default_policy 设置为 deny,则 anonymous token 将无法读取任何 services 信息。 这将导致 REST API 和 DNS 接口中的 services 发现机制不返回任何 service 查询的结果。 这是因为内部 API 和 DNS 接口使用 RPC 接口,这将过滤 token 无法访问的 service 的结果。

您可以通过为 anonymous token 配置此 ACL 规则,允许发现所有 serivce,模拟 0.6.0 之前发布的行为:

1
2
3
service "" {
policy = "read"
}

以上将允许仅读取 service 信息的访问。 这种访问级别允许发现系统中的其他 service,但不足以允许 agent 在 anti-entropy 期间同步其 service 和检查全局 catalog。

处理 service 注册和发现的最安全的方法是运行 Consul 0.6+,并发布对显式访问预期在每个 agent 上运行的 service 或 service 前缀的 token。

Blacklist mode and Events

与上述类似,如果您的 acl_default_policy 设置为 deny,那么 anonymous token 将无权访问触发用户事件。 这偏离了 0.6.0 之前的版本,用户事件完全不受限制。

事件在 ACL 语法中有自己的 first-class 表达式。 要从任意 agent 恢复对用户事件的访问,请为 anonymous token 配置如下所示的 ACL 规则:

1
2
3
event "" {
policy = "write"
}

一如往常,处理用户事件的更安全的方法是根据应该能够触发的事件显式地授予每个 API token 的访问权限。

Blacklist Mode and Prepared Queries

在 consul 0.6.3 之后,对 prepared queries 的 ACL 进行了重大更改,包括新的查询 ACL 策略。 有关详细信息,请参阅下面的 prepared queries。

Blacklist Mode and Keyring Operations

Consul 0.6 及更高版本支持使用 ACL 保护 encryption keyring 操作。 Encryption 是 gossip 的一个可选组件。 有关 Consul 的 keyring operations 的更多信息,可以在 keyring 命令 文档页面找到。

如果您的 acl_default_policy 设置为 deny,那么 anonymous token 将无法读取或写入 encryption keyring。 Keyring 策略是 ACL 语法中的另一个一等公民。 您可以将 anonymous token 配置为使用以下策略对 Keyring 进行自治管理:

1
keyring = "write"

Encryption keyring 操作是敏感的,应该妥善保护。 建议不要配置像上面那样的全开策略,而是应用每 token 策略以最大化安全性。

Blacklist Mode and Consul Operator Actions

Consul 0.7 添加了被新的 operator ACL 策略保护的特殊 consule actions。Operator actions 包括:

如果您的 acl_default_policy 设置为 deny,那么 anonymous token 将无权访问 Consul operator actions。 授予 read 访问权限允许读取信息用于诊断目的,而不对状态进行任何更改。 授予 write 访问权限允许读取信息和更改状态。 以下是一个示例策略:

1
operator = "write"

非常小心地授予对 operator actions 的 write 访问权限,因为不当使用可能导致 Consul 中断,甚至丢失数据。

Services and Checks with ACLs

Consul 允许配置可以控制对 serivce 的访问和 check 注册的 ACL 策略。 为了成功地注册具有这些类型的策略的 service 或 check,必须提供具有足够特权的 token 以执行到全局目录的注册。 Consul 还执行周期性 anti-entropy 同步,这可能需要 ACL token 来完成。 为了适应这种情况,Consul 提供了两种配置 ACL token 以用于注册事件的方法:

  1. 使用 acl_token 配置指令。 这允许全局配置单个 token,并在所有 service 和 check 注册操作期间使用。

  2. 在注册时提供具有 serivce 和 check 定义的 ACL token。 这允许更大的灵活性并且能够在同一 agnet 上使用多个 token。 这种情况的例子可用于 services 和 checks. 。 还可以将 token 传递给 HTTP API 以用于需要它们的操作。

Restricting service discovery with ACLs

在 Consul 0.6 中,ACL 系统被扩展以支持限制对 service 注册的 read 访问。 这允许更严格的访问控制,并限制受损 token 发现群集中运行的其他服务的能力。

如果在请求期间使用的 token 具有 “read” 级别访问权限或更大权限,ACL 系统允许用户使用 REST API 或 UI 发现 service。 Consul 将过滤出所有 service,token 在所有 API 查询中无法访问,使其看起来好像受限 service 不存在。

Consul 的 DNS 接口也受到 serivce 注册的限制的影响。 如果 agnet 使用的 token 无法访问给定的 service,则 DNS 接口在查询时将不返回任何记录。

Prepared Query ACLs

由于我们收到了 Consule 用户的反馈,我们已经演变了如何在 prepared queries 使用 ACL。

在本节中,我们首先介绍当前的实现,然后我们将详细了解具体的 Consul 版本之间的变化。

Managing Prepared Queries

管理 Prepared Queries 包括创建、读取、更新和删除查询。 有一些变化,其中每个都使用以下两种方式之一的 ACL:打开,受无法验证的 ID 保护或关闭,由 ACL 策略管理。 这些变化在这里,例子:

  • 未定义 Name 的 Static queries 不受任何 ACL 策略控制。 这些类型的 queries 意味着是短暂的,并且不共享给不受信任的 clients,并且它们只有在 prepared query 的 ID 是已知的时才可到达。 由于这些 ID 是使用与 ACL token 相同的随机 ID 方案生成的,因此不可能猜测它们。 列出所有 prepared query,只有 management token才能看到这些类型,尽管 client 可以读取具有 ID 的实例。 此类型的示例用法是由启动脚本创建的 queries ,绑定到 session,并写入配置文件以供通过 DNS 使用的进程。

  • 定义了 Name 的 Static queries 由 query ACL 策略控制。 客户端必须具有前缀足以涵盖其尝试管理的名称的 ACL token,其中最长的前缀匹配提供了定义更具体策略的方法。 client 可以列出或读取基于其前缀具有 “read” 访问权限的 queries 。类似的,他们可以更新他们具有 “write” 访问权限的任何 queries 。 此类型的示例使用是具有众所周知名称的 queries (例如,prod-master-customer-db),其被许多 client 使用和知晓以为数据库提供地理故障转移行为。

  • Template queries queries 的工作方式与定义了名称的 Static queries 类似,只是具有空名称的全部捕获模板需要可以写入任何查询前缀的 ACL token 。

Executing Pepared Queries

当 prepared queries 通过 DNS 查找或 HTTP 请求执行时,ACL 检查针对正在查询的 service 运行,类似于 ACL 如何与其他 service 查找一起工作。 有几种方式为此检查选择 ACL token:

  • 如果在定义 prepared query 时捕获到 ACL token,则它将用于执行 service 查找。 这允许由具有较少或甚至没有 ACL token 的 client 执行 queries ,因此应该谨慎使用。

  • 如果没有捕获 ACL token,则 client 的 ACL token 将用于执行 service 查找。

  • 如果没有捕获 ACL token,并且 cleint 没有ACL token ,则 anonymous token 将用于执行 serivice 查找。

在通常情况下,调用器的 ACL token 用于测试查找 service 的能力。 如果在创建 prepared query 时指定了 token,则行为会更改,现在由 query 的定义器设置的捕获的 ACL token 在查找 service 时使用。

捕获 ACL token 类似于 PostgreSQL 的 SECURITY DEFINER 属性,它可以在函数上设置,并且使用 clinet 的 ACL token 类似于补充的 SECURITY INVOKER 属性。

ACL Implementation Changes for Prepared Queries

Prepared queries 最初在 Consul 0.6.0 中引入,ACL 行为一直到版本 0.6.3 保持不变,但随后更改为允许更好地管理 prepared query namespace。

Operation Version <= 0.6.3 Version > 0.6.3
创建不带名称的静态查询 ACL token 用于创建 prepared query 时被检查,请确保它可以访问被查询的 service。此 token 作为在执行 prepared query 时被捕获。 不使用 ACL 策略,只要没有定义 Name。默认情况下不会捕获 token,除非客户端在创建 query 时特别提供。
创建带名称的静态查询 同上 client token 的查询 ACL 策略用于确定 client 是否被允许注册给定名称的查询。 默认情况下不会捕获 token,除非 client 在创建查询时特别提供。
管理不带名称的静态查询 必须提供用于创建查询的 ACL TOKEN 或管理 token,才能执行这些操作。 具有查询 ID 的任何 client 都可以执行这些操作。
管理带名称的静态查询 同上 与创建类似,client token 的查询 ACL 策略用于确定是否允许这些操作。
列出查询 需要 management token 才能列出任何查询 clent token 的查询 ACL 策略用于确定他们可以查看哪些查询。 只有管理 token 可以查看没有名称的 prepared queries。
执行查询 因为在创建查询时总是捕获 token,这用于检查对正在查询的 service 的访问。 且任何 token 都将被 client 忽略 捕获的 token ,client 的 token 或 anonymous token 用于过滤结果,如上所述。

ACL Changes Coming in Consul 0.8

Consul 0.8 将为所有 Consul 提供完整的 ACL 覆盖。 为了方便过渡到新的策略,从 Consul 0.7.2 开始提供完整 ACL 支持的测试版本。

以下是即将推出的变更摘要:

  • Agent 现在检查 /v1/agent endpoints 中目录相关操作的 node 和 service ACL策略,例如 service 和 check 注册和运行状况检查更新。

  • Agent 对 /v1/agent endpoints(加入和离开)中的 utility operations 实施新的 Agent ACL 策略。

  • 在 Consul 中强制实施新的 node ACL 策略,提供一种机制,通过 Name 限制 node 的注册和发现。 这也适用于 service 发现,因此提供了用于控制对 service 的访问的附加维度。

  • 新的 Session ACL 策略控制通过 node name 创建 session 对象的能力。

  • Anonymous prepared queries (不带 Name 的非模板)现在需要有效的 session,这将使其创建与新的 Session ACL 策略相关联。

  • 现有 event ACL 策略已应用于 /v1/event/list endpoint.

New Configuration Options

要启用完整 ACL 覆盖率的测试版支持,请在 Consul client 和 server 上将 acl_enforce_version_8 配置选项设置为 true。

启用完整 ACL 后,将使用两个新的配置选项:

  • acl_agent_master_token:用作特殊访问 token,在配置它的每个 agent 上具有 agent ACL 策略 write 特权。 只有当 Consul server 不可用于解析 ACL token 时,此 token 此 tokone 才应由运营商在中断期间使用。 应用程序应在正常操作期间使用常规 ACL token。

  • acl_agent_token:由 Consul agnet 内部使用,以在注册自身或向 server 发送网络坐标时对 service 目录执行操作。

对于 client,此 token 必须至少具有 node ACL 策略对其将注册的 node name 的 wrrite 访问权限。 对于 server,这必须具有 node ACL 策略对预期加入集群的所有节点的 write 访问权限,以及 serivice ACL 策略对 consul serivce 的 write 写访问权限,将自动为其注册。

由于 cleint 现在在本地解析 ACL,所以 acl_down_policy 现在适用于 Consul client 以及 Consul server。这将确定在 server 停机的情况下 client 将执行的操作。 Consul client 不需要配置 acl_master_token 或 acl_datacenter。他们将与 Consule server 联系以确定是否启用 ACL。如果他们检测到 ACL 未启用,它们将最多每 2 分钟检查一次是否已启用,并将开始自动实施 ACL。

New ACL Policies

新的 agent ACL策略如下所示:

1
2
3
agent "<node name prefix>" {
policy = "<read|write|deny>"
}

这会影响与实用程序相关的 agent endpoints,例如 / v1/agent/self 和 /v1/agent/join。

新的 node ACL 策略如下所示:

1
2
3
node "<node name prefix>" {
policy = "<read|write|deny>"
}

这会影响 node 注册,node 发现,service 发现和 endpoints(如 /v1/agent/members)。

新的 session ACL 策略如下所示:

1
2
3
session "<node name prefix>" {
policy = "<read|write|deny>"
}

这会影响所有的 /v1/session endpoints。

初识 Consul

[Doc] https://www.consul.io/intro/getting-started/install.html

安装 Consul

[Download] https://releases.hashicorp.com/consul/0.7.2/consul_0.7.2_linux_amd64.zip

安装 Consul 很简单,其实际就是一个可执行的二进制包。

  • 安装 consul
1
# curl http://192.168.112.4:88/scripts/consul > /usr/local/bin/consul | chmod +x /usr/local/bin/consul
  • 验证
1
2
3
# consul -v
Consul v0.7.2
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

开发模式

为了简单起见,我们现在将以开发模式启动 Consul agent 。 此模式对于快速轻松地启动单节点 Consul 环境非常有用。 它不打算在生产中使用,因为它不持续任何状态。

Starting the Agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# consul agent -dev
==> Starting Consul agent...
==> Starting Consul agent RPC...
==> Consul agent running!
Version: 'v0.7.2'
Node name: 'i4-jrocket2-104-02-1'
Datacenter: 'dc1'
Server: true (bootstrap: false)
Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false
Atlas: <disabled>
==> Log data will now stream in as it occurs:
......

列出集群成员

1
2
3
# consul members
Node Address Status Type Build Protocol DC
i4-jrocket2-104-02-1 127.0.0.1:8301 alive server 0.7.2 2 dc1

member 命令的输出基于 gossip 协议,并且最终是一致的。 也就是说,在任何时间点,您的本地 agent 看到的 world view 可能与服务器上的状态不完全匹配。 对于一个强一致性 world view,请使用 HTTP API 做为转发请求转到到 Consul 服务器:

HTTP API 接口查询

1
2
3
4
5
6
7
8
9
10
11
12
13
# curl localhost:8500/v1/catalog/nodes
[
{
"Node": "i4-jrocket2-104-02-1",
"Address": "127.0.0.1",
"TaggedAddresses": {
"lan": "127.0.0.1",
"wan": "127.0.0.1"
},
"CreateIndex": 4,
"ModifyIndex": 5
}
]

DNS 接口查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# dig @127.0.0.1 -p 8600 i4-jrocket2-104-02-1.node.consul
; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> @127.0.0.1 -p 8600 i4-jrocket2-104-02-1.node.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8925
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;i4-jrocket2-104-02-1.node.consul. IN A
;; ANSWER SECTION:
i4-jrocket2-104-02-1.node.consul. 0 IN A 127.0.0.1
;; Query time: 1 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Fri Jan 13 16:03:54 CST 2017
;; MSG SIZE rcvd: 66

Stopping the Agent

您可以使用 Ctrl-C(中断信号)正常停止 agnet。 中断 agent 后,您应该看到它离开集群并关闭。

定义服务

[Doc] https://www.consul.io/intro/getting-started/services.html

一个服务可以通过提供两种方式来注册服务:

service definition

此种方式,我们需要事先创建一个目录用于存储服务定义文件,consule 启动加载配置目录中的所有配置文件。在 Unix 系统上,一个常见约定是将目录命名为 /etc/consul.d

  • 创建配置目录
1
# mkdir /etc/consul.d
  • 定义服务
1
# echo '{"service": {"name": "i4-jrocket2", "tags": ["i4-docker"], "port": 8080}}' | sudo tee /etc/consul.d/i4-jrocket2.json
  • 启动 consul
1
# consul agent -dev -config-dir=/etc/consul.d -bind=192.168.104.2 -client=192.168.104.2
  • DNS 接口查询
1
2
3
4
5
6
# dig @192.168.104.2 -p 8600 i4-jrocket2.service.consul SRV | sed -n '/;; ANSWER/ {N;N;N;N;p}'
;; ANSWER SECTION:
i4-jrocket2.service.consul. 0 IN SRV 1 1 8080 i4-jrocket2-104-02-1.node.dc1.consul.
;; ADDITIONAL SECTION:
i4-jrocket2-104-02-1.node.dc1.consul. 0 IN A 192.168.104.2
  • HTTP API 接口查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# curl http://192.168.104.2:8500/v1/catalog/service/i4-jrocket2
[
{
"Node": "i4-jrocket2-104-02-1",
"Address": "192.168.104.2",
"TaggedAddresses": {
"lan": "192.168.104.2",
"wan": "192.168.104.2"
},
"ServiceID": "i4-jrocket2",
"ServiceName": "i4-jrocket2",
"ServiceTags": [
"i4-docker"
],
"ServiceAddress": "",
"ServicePort": 8080,
"ServiceEnableTagOverride": false,
"CreateIndex": 6,
"ModifyIndex": 6
}
]

更新服务

可以通过更改配置文件并向 agent 发送 SIGHUP 来更新服务定义。 这允许您更新服务,而不会出现任何停机或不可用的服务查询。或者,HTTP API 可用于动态添加,删除和修改服务。

Consul Agent

[Doc] https://www.consul.io/docs/agent/basics.html

运行和停止 Agent

Consul agent 是 Consul 的核心:它运行 agent,执行维护成员信息,运行 check,通知 service,处理 queries 等的重要任务。Agent 必须在作为 Consul 集群一部分运行于每个节点上。

Agent 有两种模式:clientserver

Server 要参与 consensus quorum,当遇到故障情况时,这些节点通过 Raft 提供强一致性和强有效性,在 Server 节点上的较高负载意味着它们应该运行在专属的实例 —— 它们比 client 节点更为资源密集型。在整个集群中,绝大部分都为 Client 节点。

client 把所有的 RPCs 请求转发到 server 端,是相对无状态的。唯一在后台运行的时 client 端执行了 LAN gossip pool,只消耗极少的资源和网络带宽。

Running an Agent

Agent 通过执行 consul agent 启动。这个命令是阻塞的,除非主动终止。Agent 命令需要很多配置参数,但大多数都有默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# consul agent -server \
-data-dir=/tmp/consul \
-config-dir=/etc/consul.d \
-bind=192.168.104.2 \
-client=192.168.104.2 \
-datacenter=gd1 \
-domain=consul
==> Starting Consul agent...
==> Starting Consul agent RPC...
==> Consul agent running!
Version: 'v0.7.2'
Node name: 'i4-jrocket2-104-02-1'
Datacenter: 'gd1'
Server: true (bootstrap: false)
Client Addr: 192.168.104.2 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400)
Cluster Addr: 192.168.104.2 (LAN: 8301, WAN: 8302)
Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false
Atlas: <disabled>
==> Log data will now stream in as it occurs:
......

这段日志中包含比较多的重要信息:

  • Node name:表示节点在集群中的名字,在集群中是唯一的。通常设置为 hostname。可以通过参数 -node 设置。

  • Datacenter:这是 agent 配置为运行的数据中心。 Consul 对多个数据中心有一等公民的支持;但是,要高效工作,必须将每个节点配置为报告其数据中心。 -datacenter 标志可用于设置数据中心。 对于单 DC 配置,Agent 将默认为 “dc1”。

  • Server:这指示 agent 是否以 server 或 client 模式运行。Server 节点具有参与协商 quorum,存储集群状态和处理 queries 的额外负担。 另外,服务器可以处于 “bootstrap(引导)” 模式。 多个 server 不能处于引导模式,因为这将使群集处于不一致的状态。

  • Client Addr:这是用于 agent cient 端接口的地址,包括 HTTP,DNS 和 RPC 接口的端口。 RPC 地址由查询和控制运行 agent 的其他 consul 命令(例如 consul 成员,consul join 等)使用。 默认情况下,它仅绑定到 localhost。 如果更改此地址或端口,则在运行命令(如consul成员)以指示如何访问 agent 程序时,必须指定 -client。 其他应用程序也可以使用 RPC 地址和端口来控制 Consul。

  • Cluster Addr:这是用于集群中的 Consul agent 之间的通信的端口的地址和集合。 并非群集中的所有 Consul agent 都必须使用相同的端口,但是所有其他节点必须可以访问此地址。

  • Atlas:这显示了注册节点的 Atlas 基础架构。 它还指示是否启用自动加入。 使用 -atlas 设置 Atlas 基础结构,并通过设置 -atlas-join 启用自动连接。

Stopping an Agent

Agent 可以通过两种方式停止:gracefully(优雅)or forcefully(强制)。

要正常停止 agent ,请向进程发送中断信号(通常是来自终端的 Ctrl-C 或运行 kill -INT consul_pid)。 当正常退出时,Agent 首先通知它打算离开集群的集群。 这样,其他集群成员通知集群节点已离开。

或者,您可以通过向 Agent 发送 kill 信号来强制杀死 Agent。 当强制 kill 时,agent 立即结束。 集群的其余部分最终(通常在几秒钟内)检测到节点已经死亡,并通知集群节点已经失败。

Server 节点以 gracefully 方式离开时尤为重要,使得对集群的影响减小到很低。

对于 client agent,节点故障和节点离开之间的差异对您的用例可能不重要。 例如,对于 Web 服务器和负载平衡器设置,两者都会产生相同的结果:Web 节点从负载平衡器池中删除。

Lifecycle

Consul 集群中的每个 agent 都经历一个生命周期。 了解此生命周期有助于构建 agnet 与 cluster 交互智能模型和如何处理节点是非常有用的。

当 agent 首次启动时,它不知道集群中的任何其他节点。 要发现它的 Peer set,它必须加入集群。 这是通过 join 命令完成的,或者通过在启动时提供适当的配置来自动加入。 一旦节点加入,这个信息被传送到整个集群,这意味着所有节点最终都会意识到彼此。 如果 agent 是 server,则现有 server 将开始复制到新节点。

在网络故障的情况下,一些节点可能不能由其他节点访问。 在这种情况下,不可达节点被标记为失败。 不可能区分网络故障和 agent 崩溃,因此两种情况处理相同。 一旦节点标记为失败,此信息在 service catalog 中更新。

当节点离开时,它指定它的意图这样做,并且集群标记该节点已经离开。 与失败的情况不同,节点提供的所有 service 都会立即取消注册。 如果 agent 是 server,则复制到 server 将停止。

为了防止死节点(处于故障状态或离开状态的节点)的累积,Consul 将自动从 catalog 中删除死节点。 这个过程称为收敛。目前默认配置的时间间隔为 72 小时(不建议更改 reaping 间隔,可能导致集群中断)。 收敛类似于离开,导致所有相关 service 被取消注册。

DNS 接口

Consul 的主要查询接口之一是 DNS。 DNS 接口允许应用程序使用 service 发现,而无需与 Consul 进行任何集成。

例如,无需向 Consul 发出 HTTP API 请求,主机可以使用 DNS 服务器直接通过名称查找如 redis.service.us-east-1.consul。 此查询自动转换为查找提供 redis 服务的节点,位于 us-east-1 数据中心,并且没有故障健康检查。 就是这么简单!

有一些对 DNS 接口很重要的配置选项,特别是 client_addrports.dnsrecursorsdomaindns_config。 默认情况下,Consul DNS 查询将监听在 127.0.0.1:8600 。 Domain 不支持进一步的 DNS 递归。 有关详细信息,请参阅配置选项的文档,特别是上面链接的配置项。

有几种方法可以使用 DNS 接口

  • 一个选择是使用自定义 DNS 解析器库并指向 Consul
  • 另一个选项是将 Consul 设置为节点的 DNS 服务器,并提供 recursors 配置,以便还可以解析非 Consul 查询。 最后一种方法是转发 “consul” 的所有查询。 Domain 从现有 DNS 服务器到 Consul agent 。

Node Lookups

要解析名称,Consul 依赖于非常特定的查询格式。 基本上有两种类型的查询:node 查找和 service 查找。

节点查找,对命名节点的地址的简单查询,如下所示:

1
<node>.node[.datacenter].<domain>

例如,如果我们有一个具有默认设置的 foo 节点,我们可以查找 foo.node.dc1.consul。datacetner 是 FQDN 的可选部分,如果未提供,则默认为 agent 的数据中心。如果我们知道 foo 与我们的本地 agent 在同一个数据中心中运行,我们可以改用 foo.node.consul。

1
# dig @192.168.104.2 -p 8600 i4-jrocket2-104-02-1.node.gd1.consul


1
# dig @192.168.104.2 -p 8600 i4-jrocket2-104-02-1.node.consul

Service Lookups

一个 service lookup 用于查询服务提供程序。 服务查询支持两种查找方法: standard 和 strict RFC 2782

  • Standard Lookup
1
[tag.]<service>.service[.datacenter].<domain>

Example:

1
i4-jrocket2.service.gd1.consul 等价于 i4-jrocket2.service.consul

对于标准服务查询,支持 ASRV 记录。 SRV 记录提供服务在其上注册的端口,使 client 能够避免依赖已知的端口。 仅当 client 特别请求 SRV 记录时才会提供 SRV 记录,如下所示:

1
2
3
4
5
6
7
# dig @192.168.104.2 -p 8600 i4-jrocket2.service.consul SRV | sed -n '/;; ANSWER/ {x;G;N;N;N;N;p}'
;; ANSWER SECTION:
i4-jrocket2.service.consul. 0 IN SRV 1 1 8080 i4-jrocket2-104-02-1.node.gd1.consul.
;; ADDITIONAL SECTION:
i4-jrocket2-104-02-1.node.gd1.consul. 0 IN A 192.168.104.2
  • RFC 2782 Lookup
1
_<service>._<protocol>.service[.datacenter][.domain]

根据 RFC 2782,SRV 查询应使用下划线 “_” 作为查询中的服务和协议值的前缀,以防止 DNS 冲突。 协议值可以是服务的任何标记。 如果服务没有标签,应使用 tcp。 如果将 tcp 指定为协议,则查询将不执行任何标记过滤。

除了查询格式和默认 tcp 协议/标签值,RFC 样式查找的行为与标准查找样式相同。

Example:

1
2
3
4
5
6
7
# dig @192.168.104.2 -p 8600 _i4-jrocket2._tcp.service.consul SRV | sed -n '/;; ANSWER/ {x;G;N;N;N;N;p}'
;; ANSWER SECTION:
_i4-jrocket2._tcp.service.consul. 0 IN SRV 1 1 8080 i4-jrocket2-104-02-1.node.gd1.consul.
;; ADDITIONAL SECTION:
i4-jrocket2-104-02-1.node.gd1.consul. 0 IN A 192.168.104.2

  • Prepared Query Lookups
1
<query or name>.query[.datacenter].<domain>

query or name 为现有的 Prepared Query 的 ID 或给定 Name。这些行为类似于标准服务查询,但提供更丰富的功能集,例如通过多个 tag 过滤,并且如果本地数据中心中没有可用的健康节点,则自动故障切换以查找远程数据中心中的 sevices。 Consul 0.6.4 及更高版本还添加了 prepared query templates 的支持,这些查询模板可以使用前缀匹配来匹配名称,从而允许一个模板应用于潜在的许多服务。

为了允许简单的负载平衡,返回的节点集合每次被随机化。 支持 A和 SRV 记录。 SRV 记录提供服务在其上注册的端口,使 client 能够避免依赖已知的端口。 仅当 client 特别请求 SRV 记录时,才会提供 SRV 记录。

基于 UDP 的 DNS 查询

当使用 UDP 执行 DNS 查询时,Consul 将截断结果,而不设置截断位。 这是为了防止在 TCP 上产生额外负载的冗余查找。 如果查找是通过 TCP 完成的,结果不会被截断。

Caching

默认情况下,Consul 提供的所有 DNS 结果都设置为 0 的 TTL 值。 这将禁用 DNS 结果的缓存。 然而,在许多情况下,高速缓存对于性能和可伸缩性是期望的。 这在 DNS 缓存 中有更多讨论。

WAN Address Translation

默认情况下,Consul DNS 查询将返回节点的本地地址,即使从远程数据中心查询。 如果需要使用其他地址从其数据中心外部访问节点,可以使用 advertise-wan 和 translate_wan_addrs 配置选项配置此行为。

环境变量

除了 CLI 标志,Consul 读取行为默认值的环境变量。 CLI 标志始终优先于环境变量,但使用环境变量配置 Consul agnet 通常很有帮助,特别是配置管理和初始化系统。

CONSUL_HTTP_ADDR

这是指定为 URI 的本地 Consul agent(而不是远程 server)的 HTTP API 地址:

1
CONSUL_HTTP_ADDR=127.0.0.1:8500

或作为 Unix 套接字路径:

1
CONSUL_HTTP_ADDR=unix://var/run/consul_http.sock

CONSUL_HTTP_TOKEN

这是启用访问控制列表(ACL)时所需的 API 访问 token ,例如:

1
CONSUL_HTTP_TOKEN=aba7cbe5-879b-999a-07cc-2efd9ac0ffe

CONSUL_HTTP_AUTH

这将指定 HTTP 基本访问凭据为 username:password 对:

1
CONSUL_HTTP_AUTH=operations:JPIMCmhDHzTukgO6

CONSUL_HTTP_SSL

这是一个布尔值(默认值为 false),用于启用 HTTPS URI 方案和与 HTTP API 的SSL连接:

1
CONSUL_HTTP_SSL=true

CONSUL_HTTP_SSL_VERIFY

这是一个布尔值(默认为 true),用于指定 SSL 证书验证; 不建议将此值设置为 false 用于生产使用。 用于开发目的的示例:

1
CONSUL_HTTP_SSL_VERIFY=false

CONSUL_RPC_ADDR

这是指定为 URI 的本地 agent 的 RPC 接口地址:

1
CONSUL_RPC_ADDR=127.0.0.1:8300

或作为 Unix 套接字路径:

1
CONSUL_RPC_ADDR=unix://var/run/consul_rpc.sock

HTTP API

Consul 的主要接口是 RESTful HTTP API。 该 API 可用于对节点、服务、检查、配置等执行 CRUD 操作。 endpoints 被版本化以启用更改,而不会破坏向后兼容性。

每个 endpoints 管理 Consul 的不同方面:

配置

[Doc] https://www.consul.io/docs/agent/options.html

agent 有各种各样的配置项可以在命令行或者配置文件进行定义,所有的配置项都是可选择的,当加载配置文件的时候,consul 从配置文件或者配置目录加载配置。后面定义的配置会合并前面定义的配置,但是大多数情况下,合并的意思是后面定义的配置会覆盖前面定义的配置,但是有些情况,例如 event 句柄,合并仅仅是添加到前面定义的句柄后面。consul 重新加载配置文件也支持以信号的方式接收 update 信号。

命令行选项

  • advertise:通告地址用来改变我们给集群中的其他节点通告的地址,一般情况下 -bind 地址就是通告地址
  • bootstrap:用来控制一个 server 是否在 bootstrap 模式,在一个 datacenter 中只能有一个 server 处于 bootstrap 模式,当一个 server 处于 bootstrap 模式时,可以自己选举为 raft leader。
  • bootstrap-expect:在一个 datacenter 中期望提供的 server 节点数目,当该值提供的时候,consul 一直等到达到指定 sever 数目的时候才会引导整个集群,该标记不能和 bootstrap 公用
  • bind:该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是 0.0.0.0
  • client:consul 绑定在哪个 client 地址上,这个地址提供 HTTP、DNS、RPC 等服务,默认是 127.0.0.1
  • config-file:明确的指定要加载哪个配置文件
  • config-dir:配置文件目录,里面所有以 .json 结尾的文件都会被加载
  • data-dir:提供一个目录用来存放 agent 的状态,所有的 agent 允许都需要该目录,该目录必须是稳定的,系统重启后都继续存在
  • datacenter:该标记控制 agent 允许的 datacenter 的名称,默认是 dc1
  • dns-port:更改默认的 DNS 端口 8600,0.7 版本以后开始支持
  • recursor:当 Consul 自身无法解析域名时,指定将其转发给其它的 DNS 服务器
  • encrypt:指定 secret key,使 consul 在通讯时进行加密,key 可以通过 consul keygen 生成,同一个集群中的节点必须使用相同的 key
  • join:加入一个已经启动的 agent 的 ip 地址,可以多次指定多个 agent 的地址。如果 consul 不能加入任何指定的地址中,则 agent 会启动失败,默认 agent 启动时不会加入任何节点。
  • retry-join:和 join 类似,但是允许你在第一次失败后进行尝试。
  • retry-interval:两次 join 之间的时间间隔,默认是 30s
  • retry-max:尝试重复 join 的次数,默认是 0,也就是无限次尝试
  • log-level:consul agent 启动后显示的日志信息级别。默认是 info,可选:trace、debug、info、warn、err。
  • node:节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名
  • protocol:consul 使用的协议版本
  • rejoin:使 consul 忽略先前的离开,在再次启动后仍旧尝试加入集群中。
  • server:定义 agent 运行在 server 模式,每个集群至少有一个 server,建议每个集群的 server 不要超过5个
  • syslog:开启系统日志功能,只在 linux/osx 上生效
  • ui-dir:提供存放 web ui 资源的路径,该目录必须是可读的
  • pid-file:提供一个路径来存放 pid 文件,可以使用该文件进行 SIGINT/SIGHUP(关闭/更新)agent

配置文件

除了命令行参数外,配置也可以写入文件中,在某些情况下配置文件会更简单一些,例如当使用配置管理系统配置 Consul 时。

配置文件是 JSON 格式的,使其易于人和计算机可读和可编辑。 配置被格式化为其中具有配置的单个 JSON 对象。

配置文件不仅用于设置 agent,它们还用于提供 check 和 service 定义。 这些用于通知集群其余部分的系统服务器的可用性。 它们分别记录在 check 配置和 service 配置下。 service 和 check 定义支持在重新加载期间更新。

示列配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"datacenter": "east-aws",
"data_dir": "/opt/consul",
"log_level": "INFO",
"node_name": "foobar",
"server": true,
"watches": [
{
"type": "checks",
"handler": "/usr/bin/health-check-handler.sh"
}
],
"telemetry": {
"statsite_address": "127.0.0.1:2180"
}
}

示例配置文件,带有 TLS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"datacenter": "east-aws",
"data_dir": "/opt/consul",
"log_level": "INFO",
"node_name": "foobar",
"server": true,
"addresses": {
"https": "0.0.0.0"
},
"ports": {
"https": 8080
},
"key_file": "/etc/pki/tls/private/my.key",
"cert_file": "/etc/pki/tls/certs/my.crt",
"ca_file": "/etc/pki/tls/certs/ca-bundle.crt"
}

除非已为 https 端口分配了端口号>0,否则 Consul 将不会为 HTTP API 启用 TLS。

配置文件参数:

  • acl_datacenter:只用于 server,指定的 datacenter 的权威 ACL 信息,所有的 servers 和 datacenter 必须同意 ACL datacenter
  • acl_default_policy:allow 或 deny,默认为 allow。默认策略在没有匹配规则时控制 token 的行为。 在 allow 模式下,ACL 是黑名单:允许任何未特别禁止的操作。 在 deny 模式下,ACL 是白名单:阻止任何未明确允许的操作。
  • acl_down_policy:allow、deny 或 extend-cache。extend-cache 是默认值。 在无法从 acl_datacenter 或 leader 节点读取 token 的策略的情况下,应用向下策略。 在 allow 模式下,允许所有操作,deny 限制所有操作,extend-cache 允许使用任何高速缓存的 ACL,忽略其 TTL值。 如果使用非高速缓存的 ACL,extend-cache 就像 deny。
  • acl_master_token:仅用于 acl_datacenter 中的 servers。 如果此 token 不存在,则将使用管理级权限创建此 token。 它允许操作员使用众所周知的 token ID来引导 ACL 系统。
  • acl_token:agent 会使用这个 token 和 consul server 进行请求
  • acl_ttl:控制 TTL 的 cache,默认是 30s
  • addresses:一个嵌套对象,可以设置以下 key:dns、http、rpc
  • advertise_addr:等同于 -advertise
  • bootstrap:等同于 -bootstrap
  • bootstrap_expect:等同于 -bootstrap-expect
  • bind_addr:等同于 -bind
  • ca_file:提供 CA 文件路径,用来检查客户端或者服务端的链接
  • cert_file:必须和 key_file 一起
  • check_update_interval
  • client_addr:等同于 -client
  • datacenter:等同于 -dc
  • data_dir:等同于 -data-dir
  • disable_anonymous_signature:在进行更新检查时禁止匿名签名
  • disable_remote_exec:禁止支持远程执行,设置为 true,agent 会忽视所有进入的远程执行请求
  • disable_update_check:禁止自动检查安全公告和新版本信息
  • dns_config:是一个嵌套对象,可以设置以下参数:allow_stale、max_stale、node_ttl 、service_ttl、enable_truncate
  • domain:默认情况下 consul 在进行 DNS 查询时,查询的是 consul 域,可以通过该参数进行修改
  • enable_debug:开启 debug 模式
  • enable_syslog:等同于 -syslog
  • encrypt:等同于 -encrypt
  • key_file:提供私钥的路径
  • leave_on_terminate:默认是 false,如果为 true,当 agent 收到一个 TERM 信号的时候,它会发送 leave 信息到集群中的其他节点上。
  • log_level:等同于 -log-level
  • node_name:等同于 -node
  • ports:这是一个嵌套对象,可以设置以下 key:dns: 8600、http api: 8500、rpc: 8400、serf_lan: 8301、serf_wan: 8302、server rpc:8300
  • protocol:等同于 -protocol
  • recursor:此标志提供用于递归解析查询的上游 DNS 服务器的地址,如果它们不在 Consul 的服务域内。 例如,节点可以直接使用 Consul 作为 DNS 服务器,并且如果记录在 “consul” 之外。 域,查询将在上游解决。
  • rejoin_after_leave:等同于 -rejoin
  • retry_join:等同于 -retry-join
  • retry_interval:等同于 -retry-interval
  • server:等同于 -server
  • server_name:会覆盖 TLS CA 的node_name,可以用来确认 CA name 和 hostname 相匹配
  • skip_leave_on_interrupt:和 leave_on_terminate 比较类似,不过只影响当前句柄
  • start_join:一个字符数组提供的节点地址会在启动时被加入
  • statsd_addr:这提供了 statsd 实例的地址,格式为 host:port。 如果提供,consul 将发送各种遥测信息到该实例进行聚合。 这可以用于捕获运行时信息。 这只发送 UDP 数据包,可以与 statsd 或 statsite一起使用。
  • statsite_addr:这提供了格式为 host:port 的站点实例的地址。 如果提供,consul 将流传输各种遥测信息到该实例进行聚合。 这可以用于捕获运行时信息。 此流通过 TCP 流,并且只能与 statsite 一起使用。
  • syslog_facility:当 enable_syslog 被提供后,该参数控制哪个级别的信息被发送,默认 Local0
  • ui_dir:等同于 -ui-dir
  • verify_incoming:默认 false,如果为 true,则所有进入链接都需要使用 TLS,需要客户端使用 ca_file 提供 ca 文件,只用于 consul server 端,因为 client 从来没有进入的链接
  • verify_outgoing:默认 false,如果为 true,则所有出去链接都需要使用 TLS,需要服务端使用 ca_file 提供 ca 文件,consul server 和 client 都需要使用,因为两者都有出去的链接
  • watches:watch 一个详细名单

Ports Used

Consul requires up to 5 different ports to work properly, some on TCP, UDP, or both protocols. Below we document the requirements for each port.

  • Server RPC (Default 8300). This is used by servers to handle incoming requests from other agents. TCP only.

  • Serf LAN (Default 8301). This is used to handle gossip in the LAN. Required by all agents. TCP and UDP.

  • Serf WAN (Default 8302). This is used by servers to gossip over the WAN to other servers. TCP and UDP.

  • CLI RPC (Default 8400). This is used by all agents to handle RPC from the CLI. TCP only. This is deprecated in Consul 0.8 and later.

  • HTTP API (Default 8500). This is used by clients to talk to the HTTP API. TCP only.

  • DNS Interface (Default 8600). Used to resolve DNS queries. TCP and UDP.

可重载配置:

  • Log level
  • Checks
  • Services
  • Watches
  • HTTP Client Address
  • Atlas Token
  • Atlas Infrastructure
  • Atlas Endpoint

Service 定义

[Doc] https://www.consul.io/docs/agent/services.html

服务发现的主要目标之一是提供可用服务的目录。 为此,agetn 提供简单的 service 定义格式来声明 service 的可用性,并且潜在地将其与 health check 相关联。 如果 health check 与 service 相关联,则将其视为应用程序级别。 Service 在配置文件中定义或在运行时通过 HTTP 接口添加。

Service 定义

一个服务定义脚本如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"service": {
"name": "redis",
"tags": ["primary"],
"address": "",
"port": 8000,
"enableTagOverride": false,
"checks": [
{
"script": "/usr/local/bin/check_redis.py",
"interval": "10s"
}
]
}
}

服务定义必须包含 name,并且可以可选地提供 idtagaddressportcheckenableTagOverride。 如果未提供,则将 id 设置为 name 。 要求所有 service 在每个 node 都有唯一的 ID,因此如果 name 可能会冲突,则应提供唯一的 ID。

tags 属性是对 Consul 不透明的值的列表,但可用于区分 主节点辅助节点,不同版本或任何其他服务级别标签。

address 字段可用于指定 service 特定的 IP 地址。 缺省情况下,使用 agent 的 IP 地址,不需要提供。 port 字段也可以用于使面向服务的架构配置更简单;这样,可以发现 servcie 的 address 和 port。

Service 还可以包含 token 字段以提供 ACL token。 此 token 用于与 service 的 catalog 的任何交互,包括反熵同步( anti-entropy syncs)和注销(deregistration)。

Service 可以具有关联的 health check。 这是一个强大的功能,因为它允许 Web balancer 正常删除失败的节点,数据库以替换失败的辅助节点等。health check 也强烈集成在 DNS 接口中。 如果 service 失败其运行状况检查或节点具有任何失败的系统级检查,则 DNS 接口将从任何 service 查询中忽略该节点。

Check 必须是 script、HTTP、TCP 或 TTL 类型。 如果它是 script 类型,则必须提供 script 和 interval。 如果它是 HTTP 类型,则必须提供 http 和 interval。 如果它是 TCP 类型,则必须提供 tcp 和 interval。 如果它是一个 TTL 类型,那么只有 ttl 必须提供。 Check name 自动生成为 service:<service-id>。 如果注册了多个 service check,则 ID 将作为服务生成:<service-id>:<num>,其中 <num> 是从 1 开始的递增数字。

可以选择性地指定 enableTagOverride 以禁用此 service 的反熵特征。 如果 enableTagOverride 设置为TRUE,则外部 agent 可以更新 catalog 中的此 service 并修改 tag。 此 agent 程序的后续本地同步操作将忽略更新的 tag。 例如,如果外部 agent 修改了此 service 的 tag 和 port 以及 enableTagOverride 设置为TRUE,则在下一个同步周期后,service 的 port 将恢复为原始值,但是 tag 将保持更新的值。 作为计数器示例:如果外部 agent 修改了此 service 的 tag 和 port,并且 enableTagOverride 设置为FALSE,则在下一个同步周期后,servcie 的 prot 和 tag 将恢复为原始值,并且所有修改都将丢失。

请注意,这仅适用于本地注册的 service。 如果您有多个节点都注册相同的 service,则它们的 enableTagOverride 配置和所有其他 service 配置项彼此独立。 更新在一个节点上注册的 service 的 tag 与在另一个节点上注册的同一 service(按名称)无关。 如果未指定 enableTagOverride,那么缺省值为 false。 有关详细信息,请参阅反熵同步。

要配置 service,请将其作为 -config-file 选项提供给 agent,或将其放在 agnet 的 -config-dir 内。 文件必须以 .json 扩展名结束,以便由 Consul 加载。 可以通过向 agent 发送 SIGHUP 来更新检查定义。 或者,可以使用 HTTP API 动态地注册服务。

多个 Service 定义

可以使用配置文件中的复数 service 键同时提供多个服务定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"services": [
{
"id": "red0",
"name": "redis",
"tags": [
"primary"
],
"address": "",
"port": 6000,
"checks": [
{
"script": "/bin/check_redis -p 6000",
"interval": "5s",
"ttl": "20s"
}
]
},
{
"id": "red1",
"name": "redis",
"tags": [
"delayed",
"secondary"
],
"address": "",
"port": 7000,
"checks": [
{
"script": "/bin/check_redis -p 7000",
"interval": "30s",
"ttl": "60s"
}
]
},
...
]
}

Service 和 Tag Name 与 DNS

Consul 通过 DNS 接口暴露 service 定义和 tag。 DNS 查询具有严格的允许字符集,且 包含 Consul 无法覆盖的明确定义的格式。 虽然可以注册具有不符合约定的名称的 service 或 tag,但是这些 service 和 tag 将无法通过 DNS 接口发现。 建议始终使用符合 DNS 的 servcie 和 tag 名称。

符合 DNS 的服务和标记名称可以包含任何字母数字字符以及破折号。 不支持点,因为 Consul 内部使用它们来分隔服务标签。

Check 定义

Agent 的主要角色之一是管理 系统级应用程序级别 的运行状况 check。 如果 health check 与 service 相关联,则将其视为 应用程序级别。 如果未与服务关联,则检查将监视 整个节点 的运行状况。

Check 在配置文件中定义或在运行时通过 HTTP 接口添加。 通过 HTTP 接口创建的 check 将与该节点一起保留。

有五种不同的检查:

  • script + interval:这些 check 依赖于调用执行 health check 的外部应用程序,使用适当的退出代码退出,并可能生成一些输出。 脚本与调用间隔(例如每 30 秒)配对。 这类似于 Nagios 插件系统。 脚本检查的输出限制为 4K。 大于此大小的输出将被截断。 默认情况下,脚本检查将配置为超时等于 30 秒。 可以通过在检查定义中指定超时字段来配置自定义脚本检查超时值。

  • HTTP + Interval:这些检查对指定的 URL 每隔一个间隔(例如每 30 秒)发出 HTTP GET 请求。 服务的状态取决于 HTTP响应代码:任何 2xx 代码被视为传递,429 太多请求是警告,并且任何其他都是失败。 这种类型的检查应优先于使用 curl 或另一个外部进程检查简单 HTTP 操作的脚本。 默认情况下,HTTP 检查将配置为请求超时等于检查间隔,最多为 10 秒。 可以通过在检查定义中指定超时字段来配置自定义 HTTP 检查超时值。 检查的输出限制为大约 4K。 超过此大小的响应将被截断。 HTTP 检查也支持 SSL。 默认情况下,需要有效的 SSL 证书。 可以通过在检查定义中将 tls_skip_verify 字段设置为 true 来关闭证书验证。

  • TCP + Interval:这些检查使每个间隔(例如每 30 秒)到指定的 IP/主机名和端口的 TCP 连接尝试。 如果未指定主机名,则默认为 “localhost”。 服务的状态取决于连接尝试是否成功(即端口当前正在接受连接)。 如果连接被接受,状态是成功,否则状态是危急的。 在主机名解析为 IPv4 和 IPv6 地址的情况下,将尝试对这两个地址进行尝试,并且第一次成功的连接尝试将导致成功的检查。 这种类型的检查应优先于使用 netcat 或另一个外部进程检查简单套接字操作的脚本。 默认情况下,TCP 检查将配置为请求超时等于检查间隔,最多为 10 秒。 可以通过在检查定义中指定超时字段来配置自定义 TCP 检查超时值。

  • Time to Live (TTL):这些检查保留其给定 TTL 的最后已知状态。 必须通过 HTTP 接口定期更新检查的状态。 如果外部系统无法更新给定 TTL 内的状态,则检查设置为失败状态。 这种机制,在概念上类似于死人的开关,依靠应用程序直接报告其健康。 例如,健康的应用程序可以定期将状态更新 PUT 到 HTTP 端点;如果应用程序失败,则 TTL 将过期,运行状况检查进入临界状态。 用于更新给定检查的运行状况信息的端点是通过端点和故障端点。 TTL 检查也会将其最后一个已知状态保留到磁盘。 这允许 Consul agent 恢复跨重新启动的检查的最后已知状态。 持续检查状态在从上次检查时起的 TTL 结束时有效。

  • Docker + Interval:这些检查依赖于调用封装在 Docker 容器中的外部应用程序。 应用程序通过 Docker Exec API 在正在运行的容器中触发。 我们期望 Consul agent 用户可以访问 Docker HTTP API 或 unix 套接字。 Consul 使用 $DOCKER_HOST 来确定 Docker API 端点。 应用程序应运行,对容器内运行的服务执行运行状况检查,并使用适当的退出代码退出。 该检查应与调用间隔配对。 必须执行检查的外壳是可配置的,这使得可以在同一主机上运行具有不同 shell 的容器。 检查 Docker 的输出限制为 4K。 任何超过此大小的输出都将被截断。

Check 定义

Script + Interval:

1
2
3
4
5
6
7
8
9
{
"check": {
"id": "mem-util",
"name": "Memory utilization",
"script": "/usr/local/bin/check_mem.py",
"interval": "10s",
"timeout": "1s"
}
}

HTTP + Interval:

1
2
3
4
5
6
7
8
9
{
"check": {
"id": "api",
"name": "HTTP API on port 5000",
"http": "http://localhost:5000/health",
"interval": "10s",
"timeout": "1s"
}
}

TCP + Interval:

1
2
3
4
5
6
7
8
9
{
"check": {
"id": "ssh",
"name": "SSH TCP on port 22",
"tcp": "localhost:22",
"interval": "10s",
"timeout": "1s"
}
}

TTL:

1
2
3
4
5
6
7
8
{
"check": {
"id": "web-app",
"name": "Web App Status",
"notes": "Web app does a curl internally every 10 seconds",
"ttl": "30s"
}
}

Docker + Interval:

1
2
3
4
5
6
7
8
9
10
{
"check": {
"id": "mem-util",
"name": "Memory utilization",
"docker_container_id": "f972c95ebf0e",
"shell": "/bin/bash",
"script": "/usr/local/bin/check_mem.py",
"interval": "10s"
}
}

每种类型的定义都必须包含 name,并且可以选择性地提供 id 和 notes 字段。 如果未提供,则将 id 设置为 name。 要求所有 check 在每个节点都有唯一的 ID:如果 nane 可能冲突,应提供唯一的 ID。

notes 字段对 Consul 是不透明的,但可用于提供对检查当前状态的人类可读的描述。 使用脚本检查,该字段将设置为脚本生成的任何输出。 类似地,外部进程通过 HTTP 接口更新 TTL 检查可以设置 notes 值。

检查也可能包含 token 字段以提供 ACL token。 此 token 用于与 check catalog 的任何交互,包括反熵同步和注销。

Script、TCP、Docker 和 HTTP check 必须包括一个 interval 字段。 此字段由 Go 的时间包解析,并具有以下格式规范:

1
持续时间字符串是可能带符号的十进制数序列,每个具有可选的分数和单位后缀,例如 "300ms"、"-1.5h" 或 "2h45m"。 有效时间单位是 "ns"、"us"(或"μs")、"ms"、"s"、"m"、"h"。

在 Consul 0.7 及更高版本中,与服务关联的检查也可能包含可选的 deregister_critical_service_after 字段,这是与 interval 和 ttl 相同的 Go 时间格式的超时。 如果检查处于 critical 状态超过此配置的值,则其关联的服务(及其所有相关联的检查)将自动取消注册。 最小超时为 1 分钟,并且 critical 服务的复原进程每 30 秒运行一次,因此可能需要比配置的超时稍长的时间才能触发取消注册。 这通常应该配置超时,这比给定服务的任何预期的可恢复中断时间长得多。

要配置 check,请将其作为 -config-file 选项提供给 agent,或将其放在 agent 的 -config-dir 内。 文件必须以 .json 扩展名结尾,以便由 Consul 加载。 还可以通过向 agent 发送 SIGHUP 来更新检查定义。 或者,可以使用 HTTP AP I动态地注册 check。

Check Scripts

检查脚本通常可以自由地做任何事情来确定检查的状态。 唯一的限制是退出代码必须遵守这个约定:

  • Exit code 0 - Check is passing
  • Exit code 1 - Check is warning
  • Any other code - Check is failing

这是 Consul 依赖的唯一约定。 脚本的任何输出都将被捕获并存储在 notes 字段中,以便人类操作员可以查看。

Initial Health Check Status

默认情况下,当针对 Consul agent 注册 check 时,状态立即设置为 “critical”。 这有助于防止服务在被确认为健康之前注册为 “传递” 和进入服务池。 在某些情况下,可能需要指定 health check 的初始状态。 这可以通过在健康检查定义中指定状态字段来完成,如下所示:

1
2
3
4
5
6
7
8
{
"check": {
"id": "mem",
"script": "/bin/check_mem",
"interval": "10s",
"status": "passing"
}
}

上述服务定义将导致新的 “mem” 检查被注册,其初始状态设置为 “通过”。

Service-bound checks

运行状况检查可以可选地绑定到特定服务。 这确保 health check 的状态将仅影响给定服务的健康状态,而不影响整个节点的健康状态。 可以通过向 check 配置添加 service_id 字段来提供服务绑定的运行状况检查:

1
2
3
4
5
6
7
8
{
"check": {
"id": "web-app",
"name": "Web App Status",
"service_id": "web-app",
"ttl": "30s"
}
}

在上述配置中,如果 Web 应用程序运行状况检查开始失败,它将仅影响 Web 应用程序服务的可用性。 节点提供的所有其他服务将保持不变。

多 Check 定义

可以使用配置文件中的 checks(复数)键定义多个检查定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"checks": [
{
"id": "chk1",
"name": "mem",
"script": "/bin/check_mem",
"interval": "5s"
},
{
"id": "chk2",
"name": "/health",
"http": "http://localhost:5000/health",
"interval": "15s"
},
{
"id": "chk3",
"name": "cpu",
"script": "/bin/check_cpu",
"interval": "10s"
},
...
]
}

Encryption

[Doc] https://www.consul.io/docs/agent/encryption.html

Consul agnet 支持加密其所有网络流量。 加密的确切方法在加密内部页面上描述。 有两个单独的加密系统,一个用于 gossip 流量,一个用于 RPC。

Gossip Encryption

启用 gossip 加密只需要在启动 agent 时设置加密密钥。 可以通过 encrypt 参数设置密钥:此设置的值是包含加密密钥的配置文件。

密钥必须是 16 字节,Base64 编码。 为方便起见,Consul 提供了 consul keygen 命令来生成一个加密合适的密钥:

1
2
$ consul keygen
cg8StVXbQJ0gPvMd9o7yrg==

使用该密钥,您可以在 agent 上启用加密。 如果启用加密,则 consul agent 的输出将包括 “Encrypted:true”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cat encrypt.json
{"encrypt": "cg8StVXbQJ0gPvMd9o7yrg=="}
$ consul agent -data-dir=/tmp/consul -config-file=encrypt.json
==> WARNING: LAN keyring exists but -encrypt given, using keyring
==> WARNING: WAN keyring exists but -encrypt given, using keyring
==> Starting Consul agent...
==> Starting Consul agent RPC...
==> Consul agent running!
Node name: 'Armons-MacBook-Air.local'
Datacenter: 'dc1'
Server: false (bootstrap: false)
Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400)
Cluster Addr: 10.1.10.12 (LAN: 8301, WAN: 8302)
Gossip encrypt: true, RPC-TLS: false, TLS-Incoming: false
Atlas: <disabled>
...

Consul 集群中的所有节点必须共享相同的加密密钥才能发送和接收集群信息。

RPC Encryption with TLS

Consul 支持使用 TLS 来验证服务器和客户端的真实性。 要启用此功能,Consul 要求所有客户端和服务器都具有由单个证书颁发机构生成的密钥对。 这可以是私有 CA,仅在内部使用。 然后,CA 会为每个 agent 签署密钥,如本教程中关于使用 OpenSSL 生成 CA 和签名密钥的内容。

注意:客户端证书必须具有启用的客户端和服务器认证的扩展密钥用法。

TLS 可用于验证服务器的真实性或验证客户端的真实性。 这些模式分别由 verify_outgoingverify_server_hostnameverify_incoming 选项控制。

如果设置 verify_outgoing,agent 将验证 Consul 的出站连接的真实性。 服务器节点必须提供由所有代理程序上存在的证书颁发机构签署的证书,通过代理程序的 ca_file 选项设置。 所有服务器节点都必须使用 cert_file 和 key_file 设置适当的密钥对。

如果设置了 verify_server_hostname,则传出连接将执行主机名验证。 所有服务器必须具有有效的证书 server.<datacenter>.<domain> 或客户端将拒绝握手。 这是一个 0.5.1 版本的新配置,用于防止受影响的客户端在服务器模式下重新启动并执行 MITM(中间人)攻击。 新部署应将此设置为 true,并生成正确的证书,但默认情况下为 false,以避免破坏现有部署。

如果设置了 verify_incoming,服务器将验证所有传入连接的真实性。 所有客户端都必须使用 cert_file 和 key_file 设置有效的密钥对集。 服务器还将禁止任何非 TLS 连接。 要强制客户端使用 TLS,还必须设置 verify_outgoing。

TLS 用于保护 agent 之间的 RPC 调用,但节点之间的 gossip 通过 UDP 完成,并使用对称密钥进行保护。 请参阅上面的启用 gossip 加密。

RPC Protocol

[Doc] https://www.consul.io/docs/agent/rpc.html

Telemetry

[Doc] https://www.consul.io/docs/agent/telemetry.html

Watches

[Doc] https://www.consul.io/docs/agent/watches.html

Watchers 是指定被监视更新的数据(例如,节点列表,KV 对,health check)的 view 的方式。 当检测到更新时,调用外部处理程序。 处理程序可以是任何可执行文件。 例如,您可以查看运行状况检查的状态,并在检查至关重要时通知外部系统。

Watches 在调用 http api 接口使用阻塞队列。Agent 会自动调用合适的 API 接口俩监控数据的变化。

Watches 可以作为 Agent 配置的一部分。在 Agent 初始化时就运行,并且支持重新载入配置 —— 运行时新添加或删除配置。

在任一种情况下,必须指定 watches 的类型。 每种类型的 watch 支持不同的参数,一些是必须的一些非必须的。 当使用 agent 配置或作为 watch 命令的 CLI 标志时,这些选项在 JSON 主体中指定。

handles

Watch 配置可以指定监控的数据。一旦数据发生变化,可以运行指定的处理程序——可以是任意可执行的程序。

处理程序应从 stdin 读取其输入,并期望读取 JSON 格式的数据。 数据的格式取决于 watch 的类型。 每个 watch 类型记录格式类型。 因为它们直接映射到 HTTP API,处理程序应该期望输入匹配 API 的格式。

此外,将设置 CONSUL_INDEX 环境变量。 这映射到来自 HTTP API 的响应中的 X-Consul-Index 值。

GlobalParameters

除了每个选项类型支持的参数外,还有一些全部 watch 支持的全局参数:

  • datacenter – 数据中心名字
  • token – ACL token
  • handler – 监控到数据变化后的调用程序。

Watches 类型

  • Key – 监视指定 K/V 键值对
  • Keyprefix – 监视 K/V 键值前缀
  • Services – 监视服务列表
  • nodes – 监控节点列表
  • service – 监视服务实例
  • checks- 监视健康检查的值
  • event – 监视用户事件

Watches 类型

Type:key

Key watch 类型通常用来监视指定键值对的变化,要求提供 key 参数。API 内部接口 /v1/kv/

示例:

1
2
3
4
5
{
"type": "key",
"key": "foo/bar/baz",
"handler": "/usr/bin/my-key-handler.sh"
}

或者,使用命令行:

1
$ consul watch -type=key -key=foo/bar/baz /usr/bin/my-key-handler.sh

此命令的输出示例:

1
2
3
4
5
6
7
8
9
{
"Key": "foo/bar/baz",
"CreateIndex": 1793,
"ModifyIndex": 1793,
"LockIndex": 0,
"Flags": 0,
"Value": "aGV5",
"Session": ""
}

Type:keyprefix

keyprefix” 监视类型用于监视 KV 存储中的键的前缀。 它要求指定 “prefix” 参数。只要匹配前缀的任何键更改,此 watch 将返回与前缀匹配的所有键。API 内部接口 /v1/kv/

示例:

1
2
3
4
5
{
"type": "keyprefix",
"prefix": "foo/",
"handler": "/usr/bin/my-prefix-handler.sh"
}

或者,使用命令行:

1
$ consul watch -type=keyprefix -prefix=foo/ /usr/bin/my-prefix-handler.sh

此命令的输出示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[
{
"Key": "foo/bar",
"CreateIndex": 1796,
"ModifyIndex": 1796,
"LockIndex": 0,
"Flags": 0,
"Value": "TU9BUg==",
"Session": ""
},
{
"Key": "foo/baz",
"CreateIndex": 1795,
"ModifyIndex": 1795,
"LockIndex": 0,
"Flags": 0,
"Value": "YXNkZg==",
"Session": ""
},
{
"Key": "foo/test",
"CreateIndex": 1793,
"ModifyIndex": 1793,
"LockIndex": 0,
"Flags": 0,
"Value": "aGV5",
"Session": ""
}
]

Type:services

“服务”监视类型用于监视可用 service 的列表,它没有参数。API 内部接口 /v1/catalog/services.

此命令的输出示例:

1
2
3
4
5
{
"consul": [],
"redis": [],
"web": []
}

例:

1
2
3
4
5
6
{
"consul": [],
"i4-jrocket2": [
"i4-docker"
]
}

Type:nodes

“节点”监视类型用于监视可用节点的列表,它没有参数。API 内部接口 /v1/catalog/nodes

此命令的输出示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[
{
"Node": "nyc1-consul-1",
"Address": "192.241.159.115"
},
{
"Node": "nyc1-consul-2",
"Address": "192.241.158.205"
},
{
"Node": "nyc1-consul-3",
"Address": "198.199.77.133"
},
{
"Node": "nyc1-worker-1",
"Address": "162.243.162.228"
},
{
"Node": "nyc1-worker-2",
"Address": "162.243.162.226"
},
{
"Node": "nyc1-worker-3",
"Address": "162.243.162.229"
}
]

例:

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"Node": "i4-jrocket2-104-02-1",
"Address": "192.168.104.2",
"TaggedAddresses": {
"lan": "192.168.104.2",
"wan": "192.168.104.2"
},
"CreateIndex": 32767,
"ModifyIndex": 40933
}
]

Type:service

“服务”监视类型用于监视单个服务的提供者。 它需要 “service” 参数,并且可选地采用参数 “tag” 和 “passingonly”。 “tag” 参数将按 tag 过滤,”passingonly” 是一个布尔值,将仅过滤通过所有运行状况检查的实例。API 内部接口 /v1/health/service

示例:

1
2
3
4
5
{
"type": "service",
"service": "redis",
"handler": "/usr/bin/my-service-handler.sh"
}

或,使用命令行:

1
$ consul watch -type=service -service=redis /usr/bin/my-service-handler.sh

此命令输出示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[
{
"Node": {
"Node": "foobar",
"Address": "10.1.10.12"
},
"Service": {
"ID": "redis",
"Service": "redis",
"Tags": null,
"Port": 8000
},
"Checks": [
{
"Node": "foobar",
"CheckID": "service:redis",
"Name": "Service 'redis' check",
"Status": "passing",
"Notes": "",
"Output": "",
"ServiceID": "redis",
"ServiceName": "redis"
},
{
"Node": "foobar",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "",
"ServiceID": "",
"ServiceName": ""
}
]
}
]

Type:check

“检查”监视类型用于监视给定 servcie 或处于特定状态的 servcie 的 check。 它可选地采用 “service” 参数来过滤到特定 service 或 “状态” 参数以过滤到特定状态。 默认情况下,它将监视所有 check。内部 API 接口:/v1/health/state//v1/health/checks/

此命令输出示例:

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"Node": "foobar",
"CheckID": "service:redis",
"Name": "Service 'redis' check",
"Status": "passing",
"Notes": "",
"Output": "",
"ServiceID": "redis",
"ServiceName": "redis"
}
]

Type:event

“事件”监视类型用于监视自定义用户事件。 这些是使用 consul event 命令触发的。 它只需要一个可选的 “name” 参数,将 watch 限制为仅具有给定 name 的事件。API 内部接口 /v1/event/list

示例:

1
2
3
4
5
{
"type": "event",
"name": "web-deploy",
"handler": "/usr/bin/my-deploy-handler.sh"
}

或,使用命令行:

1
$ consul watch -type=event -name=web-deploy /usr/bin/my-deploy-handler.sh

此命令输出示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"ID": "f07f3fcc-4b7d-3a7c-6d1e-cf414039fcee",
"Name": "web-deploy",
"Payload": "MTYwOTAzMA==",
"NodeFilter": "",
"ServiceFilter": "",
"TagFilter": "",
"Version": 1,
"LTime": 18
},
...
]

要激活新的Web部署事件,可以使用以下操作:

1
$ consul event -name=web-deploy 1609030

Consule 命令

[Doc] https://www.consul.io/docs/commands/index.html

搭建 Consul DataCenter

当一个 Consul agnet 启动时,它并不知道任何其他节点,它是一个孤立的集群,集群中只有它自己。 要让 agnet 获取集群中其它成员节点的信息,只要让它加入集群即可。一旦加入集群后,agent 节点将依赖于 gossip 流言协议快速发现集群中的所有其它节点。Consul agent 可以加入任何其他 agnet,而不仅仅是 server monde 下的 agent。

集群中的每个节点都必须具有唯一的名称。 默认情况下,Consul 使用机器的主机名,可以使用 -node 命令行选项指定。

我们还可以使用 -bind 选项指定绑定地址:这是 Consul 侦听的地址,它必须可由集群中的所有其他节点访问。 虽然绑定地址不是绝对必要的,但最好提供一个绑定地址。 Consul 将默认尝试侦听系统上的所有 IPv4 接口,但如果找到多个专用IP,则将无法启动并报错。

第一个节点将作为我们集群中的唯一 server 节点,我们用 -server 选项来指示这一点。

-bootstrap-expect 标志向 Consul server 提示我们期望加入的其他 server 节点的数量。 此标志的目的是延迟复制日志的引导,直到预期的 server 数量已成功加入。 你可以在 bootstrapping 指南中阅读更多。

-bootstrap-expect-bootstrap 是冲突的。

DC 环境

Node Agent Mode DataCenter
i-infra-112-05 server GD1
i-infra-112-04 server GD1
i-infra-112-03 server GD1
i4-jrocket2-104-02-1 client GD1

搭建 Server 集群

Server node1 节点:

1
2
3
4
5
6
# mkdir /etc/consule.d
# consul agent -server -bootstrap-expect=3 \
-data-dir=/tmp/consul -config-dir=/etc/consul.d \
-bind=192.168.112.5 -client=192.168.112.5 \
-dns-port=53 -recursor=223.5.5.5 \
-datacenter=gd1 -domain=consul

Server node2 节点:

1
2
3
4
5
6
# mkdir /etc/consule.d
# consul agent -server -bootstrap-expect=3 \
-data-dir=/tmp/consul -config-dir=/etc/consul.d \
-bind=192.168.112.4 -client=192.168.112.4 \
-dns-port=53 -recursor=223.5.5.5 \
-datacenter=gd1 -domain=consul

Server node3 节点:

1
2
3
4
5
6
# mkdir /etc/consule.d
# consul agent -server -bootstrap-expect=3 \
-data-dir=/tmp/consul -config-dir=/etc/consul.d \
-bind=192.168.112.3 -client=192.168.112.3 \
-dns-port=53 -recursor=223.5.5.5 \
-datacenter=gd1 -domain=consul

此时,三个 server 节点还不知道其它 server 节点的存在,以 node1 节点为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# consul members -rpc-addr=192.168.112.5:8400
Node Address Status Type Build Protocol DC
i-infra-112-05 192.168.112.5:8301 alive server 0.7.2 2 gd1
# consul info -rpc-addr=192.168.112.5:8400
agent:
check_monitors = 0
check_ttls = 0
checks = 0
services = 1
build:
prerelease =
revision = 'a9afa0c'
version = 0.7.2
consul:
bootstrap = false
known_datacenters = 1
leader = false
leader_addr =
server = true
...

Supervisor consul.sh 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env bash
CONFIG_DIR="/etc/consul.d"
DATA_DIR="/tmp/consul"
for dir in $CONFIG_DIR $DATA_DIR
do
[ ! -e "$dir" ] && mkdir -p $dir
done
DATA_CENTER="GD1" ; DOMAIN="consul" ; BOOTSTRAP_EXPECT=3
IPADDR=$(ifconfig eth0 | grep -oP '\d.+(?= (Bcast:|netmask))')
server_vars=(
-server -ui
-bootstrap-expect=$BOOTSTRAP_EXPECT
)
agent_vars=(
-join 192.168.112.5
-join 192.168.112.4
-join 192.168.112.3
)
consul_agent() {
exec consul agent -data-dir=$DATA_DIR -config-dir=$CONFIG_DIR \
-bind=$IPADDR -client=$IPADDR \
-datacenter=$DATA_CENTER -domain=$DOMAIN \
-dns-port=53 -recursor=223.5.5.5 \
"$@" >/tmp/consul/consul.log 2>&1
}
if [ x"$1" == x"server" ]
then
consul_agent ${server_vars[@]}
elif [ x"$1" == x"agent" ]
then
consul_agent ${agent_vars[@]}
fi

systemd 脚本

支持 consul server 和 agent,更改 ExecStart 命令启动参数即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# cat consul-agent.service
[Unit]
Description=Consul agent server
After=network.target
Wants=network.target
[Service]
EnvironmentFile=-/etc/consul/consul.conf
#ExecStart=/apex.fw/bin/consul.sh server
ExecStart=/apex.fw/bin/consul.sh agent
Restart=on-failure
Type=simple
[Install]
WantedBy=multi-user.target

触发 lender 选举

在 node1 节点上,将其它 server 节点加入集群中:

1
2
# consul join -rpc-addr=192.168.112.5:8400 192.168.112.4 192.168.112.3
Successfully joined cluster by contacting 2 nodes.

查看 cluster mebers:

1
2
3
4
5
# consul members -rpc-addr=192.168.112.5:8400
Node Address Status Type Build Protocol DC
I-infra-112-03 192.168.112.3:8301 alive server 0.7.2 2 gd1
I-infra-112-04 192.168.112.4:8301 alive server 0.7.2 2 gd1
i-infra-112-05 192.168.112.5:8301 alive server 0.7.2 2 gd1

添加 client agent 节点

Consul 服务由 Supervisor 控制,通过传递 agent 参数,启动为 Consul client agent 节点。

1
2
3
4
5
# cat /etc/supervisord.d/consul.ini
[program:consul]
directory=/apex.fw/bin/
command=/bin/bash consul.sh agent
autorestart=true

DNS Caching

DNS 是 Consult提供的一个主要接口。使用 DNS 是一种不需要高度集成就可以将 Consul 集成到已有的基础设施中的简单方式。

默认情况下 Consul 服务的所有 DNS 查询数据不会长期保留(0 Time To Live),这防止了所有缓存。它的优势就是每个 DNS 查询都是重新获取的,这样就可以提供最及时的 DNS 信息。然后,这对每一个查找都会增加命中的延迟并且可能会降低整个集群查询的吞吐量。

为此,Consul提供了一些可选参数用来制定DNS查询的使用。

Stale Reads:读取陈旧数据

读取陈旧数据可以用来降低 DNS 查询时的延迟并提高吞吐量。默认情况下,所有的查询都由单一 leader 节点提供服务。这些读取可以保持强一致性,但受限于单一节点的吞吐量。设置一个陈旧数据的读取可以允许任意的 Consul 服务提供查询,但非 lender 节点可能返回过时的数据。通过允许数据稍微过时,我们获得水平读取可伸缩性。 现在任何 Consul server 都可以为请求提供服务,因此我们通过集群中的 server 数量增加吞吐量。

用来控制陈旧数据读取的配置是 dns_config.allow_stale,这里需要设置为启用状态,并且 dns_config.max_stale 可以配置数据陈旧的最长时间。

从 Consul 的 0.7 版本开始,allow_stale 默认设置为开启状态,并且 max_stale 的默认配置时间为 5 秒,这意味着我们从任意的 Consul server 中读取到的数据都与 leader 中获取到的数据存在 5 秒的偏差。在 Consul 0.7.1 中,max_stale 的默认值已从 5 秒增加到接近不确定的阈值(10年),以允许在没有 leader 的长时间中断的情况下继续提供 DNS 查询。 在 consul.dns.stale_queries 中还添加了一个新的遥测计数器,以跟踪 agnet 何时服务于陈旧时间超过 5 秒的 DNS 查询。

Negative Response Caching:拒绝响应缓存

一些 DNS 客户端缓存会拒绝响应,由于一个存在的服务没有处于健康的状态,Consul 会返回了一个 “not found” 格式的响应。这里意味着在执行过程中出现缓存拒绝响应,可能意味着服务出现了“未能正常运行”的时候,通过 DNS 进行服务发现时他们实际获取数据的数据还没有及时更正。

一个简单的例子,操作系统默认会缓存拒绝的响应 15 分钟。DNS 转发器也可能会缓存拒绝的响应造成同样的影响。为了避免这个问题,为你的客户端操作系统和任何介于客户端与 Consul 之间的 DNS 转发器进行默认的拒绝响应缓存的检查并设置适当的缓存时间。在很多情况下“适当的”简单做法就是关闭拒绝响应缓存,当服务再次变成可用时应用最好的恢复时间。

TTL Values

TTL 的值可以用来设置 Consul 用来缓存 DNS 结果的时间。 更高的 TTL 值减少了 Consul server 上的查找次数和客户端的速度查找,但代价是日益陈旧的结果。 默认情况下,所有 TTL 为零,防止任何缓存。

要启用缓存节点查找(例如 “foo.node.consul”),我们可以设置 dns_config.node_ttl 值。 例如,这可以设置为 “10s”,并且所有节点查找将使用 10 秒 TTL 提供结果。

可以以更精细的方式指定服务 TTL。 您可以设置每个服务的 TTL,使用通配符 TTL 作为默认值。 这是使用 dns_config.service_ttl 映射指定的。 “*” 服务是通配符服务。

例如,为服务提供通配符 TTL 和特定 TTL 的 dns_config 可能如下所示:

1
2
3
4
5
6
7
8
{
"dns_config": {
"service_ttl": {
"*": "5s",
"web": "30s"
}
}
}

这将所有查找设置为 “web.service.consul” 以使用 30 秒的 TTL,而查找 “db.service.consul” 或 “api.service.consul” 将使用通配符中的 5 秒 TTL。

Prepared Queries 提供了对 TTL 的额外级别的控制。 它们允许与查询一起定义 TTL,并且可以通过更新查询定义来即时更改 TTL。 如果没有为 Prepared Queries 配置 TTL,则它将回退到如上所述的 Consul 代理中定义的服务特定配置,并且如果没有为 Consul 代理中的服务配置 TTL,则最终为 0。

DNS Forwarding

默认情况下,DNS 通过 53 端口提供服务。在多数操作系统中,这都需要很高的系统权限。通过另一个 DNS 服务器或者端口跳转,可以通过一个没有高级权限端口上运行的 Consul 进行适当的 DNS 查询,而不需要运行在 administrative 或者 root 账号下的 Consul。

默认情况下,Consul 不提供不包含 .consul.DOMAIN 的 DNS 记录的解析,除非在配置文件中已经配置了 recursors。作为一个例子说明这个配置是怎么改变 Consul 的行为的,假设一个 Consul 的 DNS 可以对一个没有指向 .consul 的顶级域名的的权威名称(CNAME)进行应答。通常这个 DNS 只会应答包含权威名称(CNAME)的记录。作为比较,当 recursors 被设置并且 upstream 解析器运行正常的话,Consul 会尝试解析所有的权威名称(CNAME)和包含别名的记录(说明: IPV4 主机地址(A),IPV6 主机地址(AAAA),解析 IP 的指针(PTR))用于它的DNS应答中。

Registrator

[Doc]

Registrator 概述

通常,一个服务发现系统主要由三部分组成:

  • 注册器(registrator):根据服务运行状态,注册/注销服务。主要要解决的问题是,何时发起注册/注销动作。
  • 注册表(registry):存储服务信息。常见的解决方案有 zookeeper、etcd、cousul 等。
  • 发现机制(discovery):从注册表读取服务信息,给用户封装访问接口。

Registrator(原名 Docksul)是一个为 Docker 而设计的服务注册项目,它监听跨主机运行的容器的启动和停止,检查并向 Consul 或者 Etcd 注册它们(容器)。

它一个是独立的服务注册器,无需依赖任何集群管理系统:

  • 通过 docker socket 直接监听容器 event,根据容器启动/停止等 event 来注册/注销服务
  • 每个容器的每个 exposed 端口对应不同的服务
  • 支持可插拔的 registry backend,默认支持 Consul、etcd and SkyDNS2
  • 自身也是 docker 化的,可以容器方式启动
  • 用户可自定义配置,如服务TTL(time-to-live)、服务名称、服务 tag 等

运行 Registrator Docker

1
2
3
4
5
6
# docker run -d \
--name=registrator \
--net=host \
--volume=/var/run/docker.sock:/tmp/docker.sock \
gliderlabs/registrator:latest \
consul://192.168.104.2:8500

注意

  • 以上启动方式启动的 registrator,对于使用原生的 docker run 创建的容器,在向 Consul 注册服务时,使用的注册地址为容器宿主机的 IP 地址,这是没有问题的。

  • 但结合 Marathon 后,实际的情况还是以容器内部的 IP 地址注册。因此在使用 Mesos + Marathon 编排工具后,需要修改启动 registrator 容器的命令为:

1
2
3
4
5
6
7
# docker run -d \
--name=registrator \
--net=host \
--volume=/var/run/docker.sock:/tmp/docker.sock \
docker.io/gliderlabs/registrator \
--ip=192.168.104.2 \
consul://192.168.104.2:8500

相关 registrator 日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
# docker logs --tail 100 -f registrator
2017/02/08 05:52:39 Starting registrator v7 ...
2017/02/08 05:52:39 Forcing host IP to 192.168.104.2
2017/02/08 05:52:39 Using consul adapter: consul://192.168.104.2:8500
2017/02/08 05:52:39 Connecting to backend (0/0)
2017/02/08 05:52:39 consul: current leader 192.168.112.3:8300
2017/02/08 05:52:39 Listening for Docker events ...
2017/02/08 05:52:39 Syncing services on 3 containers
2017/02/08 05:52:39 ignored: eae31803aa5c no published ports
2017/02/08 05:52:39 added: 3be75b9fbf7f i4-jrocket2-104-02-1:k8s_jrocket2-itg04_1:8080
2017/02/08 05:52:39 added: e84d291cc2b8 i4-jrocket2-104-02-1:k8s_fluentd-i4_1:24224
2017/02/08 05:52:39 added: e84d291cc2b8 i4-jrocket2-104-02-1:k8s_fluentd-i4_1:9292
2017/02/08 05:52:39 added: e84d291cc2b8 i4-jrocket2-104-02-1:k8s_fluentd-i4_1:9880

Docker Options

Option Required Description
–volume=/var/run/docker.sock:/tmp/docker.sock yes Allows Registrator to access Docker API
–net=host recommended Helps Registrator get host-level IP and hostname

--net=host 主机网络模式的替代方法是将容器主机名设置为容器宿机的主机名(-h $ HOSTNAME),并使用下面的 -ip registrator 选项。

Option Since Description
-cleanup v7 Cleanup dangling services
-deregister v6 Deregister existed services “always” or “on-success”. Default: always
-internal Use exposed ports instead of published ports
-ip Force IP address used for registering services
-resync v6 Frequency all services are resynchronized. Default: 0, never
-retry-attempts v7 Max retry attempts to establish a connection with the backend
-retry-interval v7 Interval (in millisecond) between retry-attempts
-tags v5 Force comma-separated tags on all registered services
-ttl TTL for services. Default: 0, no expiry (supported backends only)
-ttl-refresh Frequency service TTLs are refreshed (supported backends only)
-useIpFromLabel Uses the IP address stored in the given label, which is assigned to a container, for registration with Consul

如果使用 -internal 选项,Registrator 将注册 docker0 内部 IP 和端口,而不是主机映射的。

默认情况下,注册服务时,Registrator 将通过尝试解析当前主机名来分配服务地址。 如果要强制服务地址为特定地址,可以指定 -ip 参数。

对于支持 TTL 到期的注册表后端(concul 或 etcd),registrator 可以使用 -ttl-ttl-refresh 设置和刷新服务 TTL。

如果你想要无限重试尝试,使用 -retry-attempts -1

-resync 选项控制 Registrator 对 Docker 查询所有容器并重新注册所有服务的频率。 这允许 Registrator 和服务注册表在不同步时恢复同步。 谨慎使用此选项,因为它会通知您可能在您的服务上注册的所有 watches(监视器),并可能迅速泛滥您的系统(例如 consul-template 大量使用 watches)。

Consul ACL token

如果 consul 被配置为需要 ACL token,则 Registrator 需要知道它,否则您将在 consul docker 容器中看到警告。

1
[WARN] consul.catalog: Register of service 'redis' on 'hostname' denied due to ACLs

ACL token 通过 docker 在名为 CONSUL_HTTP_TOKEN 的环境变量中传递。

1
2
3
4
5
6
7
$ docker run -d \
--name=registrator \
--net=host \
--volume=/var/run/docker.sock:/tmp/docker.sock \
-e CONSUL_HTTP_TOKEN=<your acl token> \
gliderlabs/registrator:latest \
consul://localhost:8500

Registry URI

<backend>://<address>[/<path>]

使用 URI 定义使用的 registry backend(注册表后端)。 该方案是支持的注册表名称。address 是用于连接到注册表的主机或主机和端口。 某些注册表支持使用路径定义,例如,作为在基于键值的注册表的服务定义中使用的前缀。

有关受支持的后端的完整参考,请参阅注册表后端

Servcie object

[Doc] http://gliderlabs.com/registrator/latest/user/services/

示例:

1
2
3
4
$ docker run -d --name redis.0 -p 10000:6379 \
-l "SERVICE_NAME=db" \
-l "SERVICE_TAGS=master,backups" \
-l "SERVICE_REGION=us2" dockerfile/redis

运行 docker 容器

[Doc] http://gliderlabs.com/registrator/latest/user/services/

运行容器时,可以通过指定以下环境变量,允许 Registrator 向 Consul 注册相关的 service 和 tag。

  • SERVICE_NAME:指定注册的 service 名称
  • SERVICE_TAGS:指定注删的 tag 名称

容器 docker-compose.yml 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
### message-sms - Service define start
message-sms-itg04:
image: 192.168.101.26/base/ubuntu14-jdk1.8:1.0
volumes:
- /opt/welab/message-sms:/opt/welab/message-sms
- /data/logs/message-sms:/data/logs/message-sms
ports:
- "8084:8080"
- "9084:9090"
hostname: message-sms-itg04
mem_limit: 2048M
restart: always
#log_driver: fluentd
#log_opt:
# tag: "message-sms-itg04"
# fluentd-address: "192.168.104.3:24224"
command:
- "-server -Djsse.enableSNIExtension=false -Dfile.encoding=UTF-8
-XX:InitialHeapSize=1024m -XX:MaxHeapSize=2048m -XX:NewSize=250m
-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof"
- "-Dspring.config.location=/opt/welab/message-sms/application.yml"
#- "-javaagent:/opt/welab/message-sms/pinpoint-bootstrap-1.6.0-SNAPSHOT/pinpoint-bootstrap-1.6.0-SNAPSHOT.jar"
#- "-Dpinpoint.agentId=56365320352406430097"
#- "-Dpinpoint.applicationName=message-sms-itg04"
- "-jar /opt/welab/message-sms/message-sms.jar"
environment:
SERVICE_NAME: message-sms-itg04
SERVICE_TAGS: message-sms-itg04@192.168.104.2
### message-sms - Service define end

验证 DNS 解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# dig @192.168.104.2 message-sms-itg04-8080.service.consul SRV
; <<>> DiG 9.9.4-RedHat-9.9.4-29.el7_2.4 <<>> @192.168.104.2 message-sms-itg04-8080.service.consul SRV
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22747
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; QUESTION SECTION:
;message-sms-itg04-8080.service.consul. IN SRV
;; ANSWER SECTION:
message-sms-itg04-8080.service.consul. 0 IN SRV 1 1 8084 i4-rebuild-104-03-1.node.gd1.consul.
;; ADDITIONAL SECTION:
i4-rebuild-104-03-1.node.gd1.consul. 0 IN A 192.168.104.3
;; Query time: 2 msec
;; SERVER: 192.168.104.2#53(192.168.104.2)
;; WHEN: Fri Feb 03 17:33:13 CST 2017
;; MSG SIZE rcvd: 120

查看 consule service 注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# curl http://192.168.112.5:8500/v1/catalog/service/message-sms-itg04-8080
[
{
"Node": "i4-jrocket2-104-02-1",
"Address": "192.168.104.2",
"TaggedAddresses": {
"lan": "192.168.104.2",
"wan": "192.168.104.2"
},
"ServiceID": "i4-jrocket2-104-02-1:k8s_message-sms-itg04_1:8080",
"ServiceName": "message-sms-itg04-8080",
"ServiceTags": [
"message-sms-itg04@192.168.104.2"
],
"ServiceAddress": "",
"ServicePort": 8084,
"ServiceEnableTagOverride": false,
"CreateIndex": 8957,
"ModifyIndex": 8957
},
{
"Node": "i4-rebuild-104-03-1",
"Address": "192.168.104.3",
"TaggedAddresses": {
"lan": "192.168.104.3",
"wan": "192.168.104.3"
},
"ServiceID": "i4-rebuild-104-03-1:k8s_message-sms-itg04_1:8080",
"ServiceName": "message-sms-itg04-8080",
"ServiceTags": [
"message-sms-itg04@192.168.104.3"
],
"ServiceAddress": "",
"ServicePort": 8084,
"ServiceEnableTagOverride": false,
"CreateIndex": 8938,
"ModifyIndex": 8938
}
]

更改 DNS 解析

修改 /etc/resolv.conf 解析

1
nameserver 192.168.112.5

测试 DNS 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@i4-jrocket2-104-02-1 ~]# nslookup
> message-sms-itg04-8080.service.consul
Server: 192.168.112.5
Address: 192.168.112.5#53
Name: message-sms-itg04-8080.service.consul
Address: 192.168.104.3
Address: 192.168.104.2
> www.sina.com.cn
Server: 192.168.112.5
Address: 192.168.112.5#53
Non-authoritative answer:
www.sina.com.cn canonical name = jupiter.sina.com.cn.
jupiter.sina.com.cn canonical name = ara.sina.com.cn.
Name: ara.sina.com.cn
Address: 58.63.236.248
Name: ara.sina.com.cn
Address: 121.14.1.190

Consul API

环境变量

  • CONSUL_HTTP_ADDR: 192.168.20.4:8500
  • CONSUL_HTTP_TOKEN: sh1a-token
  • CONSUL_HTTP_AUTH: username:password
  • CONSUL_HTTP_SSL: true / false
  • CONSUL_HTTP_SSL_VERIFY : true /false

HTTP API

[Doc] https://www.consul.io/api/agent.html

Consul 目前所有 API 前缀为 /v1/。

带 token 查询

1
2
$ curl --header "X-Consul-Token: sh1a-token" \
http://192.168.20.4:8500/v1/agent/members

ACLs

Status

Health

Agent

Nodes

checks

Services

  • 列出 agent 端注册的服务:-X GET http://$IPADDR:8500/v1/agent/services
  • 注册 agent 端服务:-X PUT http://$IPADDR:8500/v1/agent/service/register
    • agnet 负责管理其本地服务的状态,并将其本地服务的更新发送到 server,以使 catalog 保持同步
      • Name (string: ):指定服务的逻辑名称,多服务实例可以拥有相同的 Name
      • ID (string: “”):指定此 service 的 唯一 ID,每个 agent 必须是唯一的。 如果没有提供,则 默认为 Name
      • Tags (string: nil):指定要分配给 servies 的 tags 列表。 这些 tags 可以用于后续过滤,并通过 API 公开
      • Address (string: “”):指定 service 的地址。如果没有提供,则在 DNS 查询期间,agent 地址将用作 service 的地址
      • check (string: nil):指定 check,有关接受字段的更多信息,请参阅 check documentation
      • EnableTagOverride (bool: false):指定禁用此服务标签的反熵(anti-entropy)特征
        • 如果 EnableTagOverride 设置为 true,则允许外部 agent 可以更新权威 agent 端更新到 catalog 中的 service 并修改 tags。
        • 例如,如果外部 agent 修改了此 service 的 tags 和 ports,并将 EnableTagOverride 设置为 true,则在下一个同步周期后,service 的 ports 将恢复为原始值,但 tags 将维护更新的值。
        • 作为一个例子,如果外部 agent 修改了此 service 的 tags 和 ports,并且 EnableTagOverride 设置为 false,则在下一个同步周期之后,service 的 ports 和 tags 将恢复为原始值,并且所有修改都将丢失。
        • 请注意,这仅适用于 本地注册的 service。如果您有多个节点都注册相同的 services,那么它们的 EnableTagOverride 配置和所有其他 service 配置项彼此独立。 更新在一个节点上注册的 servcie 的 tags 与在另一个节点上注册的相同 service(按 Name)无关。 如果未指定 EnableTagOverride,则默认值为 false。 有关更多信息,请参阅 anti-entropy syncs
  • 注消(deregister)agent 服务:-X PUT http://$IPADDR:8500/v1/agent/service/deregister/:service_id
  • 启用维护模式:-X PUT http://$IPADDR:8500/v1/agent/service/maintenance/:service_id

Catalog(服务目录)

[Doc] https://www.consul.io/api/catalog.html

  • 注册条目:-X PUT http://$IPADDR:8500/v1/catalog/register

    • ID (string: “”):用于分配给 service 的可选 UUID。如果没有提供,则生成一个。这必须是一个 36 个字符的 UUID
    • Node (string: ) :指定要注册的节点 ID
    • Address:指定要注册的 address
    • Datacenter (string: “”):指定数据中心,如果没有提供,默认为 agent 的数据中心
    • TaggedAddresses (map: nil) :指定 tags 的地址
    • Meta (map: nil):指定用于过滤目的的任意 KV 元数据对
    • Service (Service: nil)
      • 指定注册 serice。如果未提供 ID,则将默认为 Service.Service 属性的值。每个节点只能有一个给定 ID 的 service。 tags、address和 port 字段都是可选的
    • Check (Check: nil)
      • 指定注册 check。register API 操作 catalog 中的运行状况 check 条目,但不会设置 scripts、TTL 或 HTTP check 来监视节点的运行状况。 要真正启用新的运行状况 check,check 必须在 agent 配置中提供,或者通过 agent 端点进行设置。
  • 注销条目:-X PUT http://$IPADDR:8500/v1/catalog/deregister

    • Node (string: ):指定节点的ID。如果没有提供其他值,则会删除此节点,其所有 servcies 和 checks
    • Datacenter (string: “”):指定数据中心,如果没有提供,默认为 agnet 的数据中心
    • CheckID (string: “”) :指定要删除的 check 的 ID
    • ServcieID (string: “”):指定要删除的 servcie 的 ID,service 和所有关联的 check 将被删除
  • 列出 Datacenter:-X GET http://$IPADDR:8500/v1/catalog/datacenters

  • 列出 Nodes:-X GET http://$IPADDR:8500/v1/catalog/nodes
  • 列出 servcies:-X GET http://$IPADDR:8500/v1/catalog/servcies

  • 列出服务的节点: -X GET http://$IPADDR:8500/v1/catalog/service/:service

    • Address:services 注册的 Consul node 的 IP 地址
    • TaggedAddresses:agnet 明确的 LAN 和 WAN IP 地址列表
    • Meta:agnet 的用户定义的元数据键/值对列表
    • CreateIndex:service 创建时的内部索引值
    • ModifyIndex:修改 service 的最后一个索引
    • Node:servcie 注册的 Consul node 的名称
    • ServiceAddress:是 service 注册的 IP 地址。如果为空,则应使用节点地址
    • ServiceEnableTagOverride:指示 service tags 是否可以覆盖此 service
    • ServiceID:唯一的 service 实例标识符
    • ServiceName:service 的名称
    • ServicePort:service 的端口号
    • ServiceTags:services 的 tags 列表
  • 列出节点的服务:-X GET http://$IPADDR:8500/v1/catalog/node/:node

KV 键值

  • Read key:-X GET http://$IPADDR:8500/v1/kv/:key

    • key (string: “”)
    • dc (string: “”)
    • recurse (bool: false)
    • raw (bool: false)
    • keys (bool: false)
    • separator (string: ‘/‘)
  • Create/Update Key:-X PUT http://$IPADDR:8500/v1/kv/:key

    • key (string: “”)
    • dc (string: “”)
    • flags (int: 0)
      • 指定 0 和 (2 ^ 64)-1 之间的无符号值。客户端可以选择使用它,但是对于他们的应用是有意义的。这是作为 `查询参数 的 URL 的一部分指定的。
    • cas (int: 0)
      • 指定使用 Check-And-Set 操作。这对于更复杂的同步原语是非常有用的。如果索引为 0,那么 Consul 只会把 key 放在不存在的地方。如果索引不为零,则仅当索引与该键的 ModifyIndex 匹配时才设置该 key。
    • acquire (string: “”)
      • 指定使用 锁定采集 操作。这是有用的,因为它允许 leader 选举建立在 Consul 之上。如果锁定并且会话有效,则会增加 LockIndex,并设置 key 的 Session 值以及更新 key 内容。这使得当前的锁持有人更新 key 内容,而不必放弃锁并重新获取它。
    • release (string: “”)
      • 指定使用锁定释放操作。当与 ?acquire= 配对时,这是有用的,因为它允许客户端产生锁定。这将使 LockIndex 未修改,但将清除 key 关联的 session。 key 必须被 session 保持直到解锁。
    • Delete key:-X DELETE http://$IPADDR:8500/v1/kv/:key

Consul registry 服务

consul-registry 脚本

注意

  • registrator 在使用 --net host 模式时,需要指定 -p <宿主端口>:<容器端口>,才能正常注册服务
  • 在某些特殊场景下,registrator 会注册不了,因此需要自己实现 Consul register/deregister 功能
  • 脚本需要安装额外的 jq 支持,用于解析 Consul Service JSON 数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
#!/usr/bin/env bash
### --------------------------------------------------
### Filename: consul-registry
### Revision: latest stable
### Author: Mallux
### E-mail: hj.mallux@gmail.com
### Blog: blog.mallux.me
### Description:
### --------------------------------------------------
### Copyright © 2014-2017 Mallux
### ------ 修改区域,修改 变量 默认值 ------
cmd=${BASH_SOURCE##*/}
### Docker events fifo file
event_fifo="/tmp/docker.events"
proc_fifo="/tmp/proc.events"
### Env variables
NIC=eth0 ; CONTAINER_NIC=$NIC
CONSUL_PORT=8500 ; CONSUL_IPADDR=$(ifconfig $NIC | grep -oP '\d.+(?= (Bcast:|netmask))')
CONSUL_LOG="/data/consul/logs/${cmd}.log" ; mkdir -p ${CONSUL_LOG%/*}
### 监听正在运行的容器
### - 监听 docker events 事件,写正在运行中的容器 ID 到此目录下。文件内容为:向 Consul 注册的 JSON Data
### - 在容器异常停止时,捕获容器 ID,注消 Consul 中对应的 Servcie ID,并删除对应容器运行时文件
container_rundir="/tmp/$cmd" ; mkdir -p ${container_rundir}
### While [ 1 ] 循环次数: ${cycle_count} + 1
### - 向 Consul 注册/注销 服务,在 容器异常 无法获取容器 $NIC 网卡 IP 地址时,
### - 在 While 循环一定的时间后,自动退出。请记住,当正确获取容器 IP 时,会马上退出 While 循环
cycle_count=3 ; sleep_time=3
### 并发处理 docker events 的进程数
thread_count=3
### ------ 以下内容无需修改,除了改变程序 处理逻辑 和 变量定义 的情况 ------
### 脚本挂起清理操作,无论脚本退出状态是什么,都会执行 __do_clean_up 函数内定义的操作
trap "__do_clean_up" EXIT
__do_clean_up() {
### Delete ${event_fifo} file
[ -e ${event_fifo} ] && {
exec 6>&-
rm -rf ${event_fifo}
}
### Kill docker events process
__do_kill_events_proc
### Delete ${proc_fifo} file
[ -e ${proc_fifo} ] && {
exec 7>&-
rm -rf ${proc_fifo}
}
}
### Kill docker events proc
__do_kill_events_proc() {
pid=$(ps -ef | grep "docker events" | grep -v grep | awk '{ print $2 }')
kill -9 $pid
}
### StartUP docker events proc
__do_up_events_proc() {
nohup docker events -f event=start -f event=unpause \
-f event=pause -f event=die >&6 &
}
### Maximum threads Number
__do_proc_mkfifo() {
thread=$1 ; [ x"$1" == x"" ] && thread=1
[ ! -p ${proc_fifo} ] && {
rm -rf ${proc_fifo}
mkfifo ${proc_fifo}
}
exec 7<>${proc_fifo}
for (( i=0;i<$thread;i++ ))
do
echo
done >&7
}
### Redirect docker events to ${event_fifo} file
__do_events_mkfifo() {
if [ ! -e $1 ]
then
mkfifo $1
else
[ ! -p $1 ] && {
rm -rf $1
mkfifo $1
}
fi
exec 6<>$1
__do_up_events_proc
}
### Consul service JSON data Define
__consul_json_datafile_def() {
json_datafile="${container_rundir}/$id"
}
### Consul query service
__do_consul_query_service() {
### 全局服务目录(catalog)
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/catalog/service"
### 查询类型:注册(register)服务、注消(deregister)服务
### 查询键: 注册 ${reg_addr}、注销 ${reg_id}
query_type=$1 ; query_key=$2
### Consul JSON datafile - Declare
__consul_json_datafile_def
### ./jq 检索 服务(service)的键表达式
jq_expr="ServiceAddrss: .ServiceAddress, ServiceID: .ServiceID, ServiceName: .ServiceName"
### 注册服务:检索指定数据,导出为 文件名 为容器 ID,文件内容 为服务注册数据
curl -s ${HTTP_API}/${reg_name} | jq ".[] | { ${jq_expr} }" > ${json_datafile}
### query_key:注册 ${reg_addr}、注消 ${reg_id}
grep -iq "${query_key}" ${json_datafile}
[ $? -eq 0 ] && exit_status=0 || exit_status=1
}
### Consul register data - Service
__consul_register_service {
### 请注意(强迫症,代码排版):
### - "cat <<-EOF" 到 EOF 区域有前导的 Tab 字符(原有 \t 模式,非四个空格)
### - "-EOF" 代表忽略前导 Tab 字符,相当于 后续字符 顶行输出
### - Author's vim 的本地设置,默认会转换 Tab 为四个空格
### - 因此,若使用 Tab 的原有模式,需要在 INSERT 模式下,使用 ctrl+v and Tab 组合键
cat <<-EOF
{
"id": "${reg_id}",
"name": "${reg_name}",
"address": "${reg_addr}",
"tags": ["${reg_name}"],
"port": ${reg_port},
"enableTagOverride": false,
"checks": [
{
"script": "curl -o /dev/null -s -w %{http_code} http://${reg_addr}:${reg_port}",
"interval": "10s",
"timeout": "3s",
"deregister_critical_service_after": "1m"
}
]
}
EOF
}
### Consul register service
__do_consul_register_service() {
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/agent/service/register"
reg_id=${reg_array[reg_id]:=nil}
reg_name=${reg_array[reg_name]:=nil}
reg_addr=${reg_array[reg_addr]:=nil}
reg_port=${reg_array[reg_port]:=nil}
### 自定日志格式
date_now=$(date "+%Y-%m-%d %H:%M:%S")
history_format='"%s - %-13s | %s | %s, %s, %s | %s\n" "${date_now}" "event=$event" "reg_id=${reg_id}"
"reg_name=${reg_name}" "reg_addr=${reg_addr}"
"reg_port=${reg_port}" "${event_remark}"'
if [ "$reg_id" == "nil" -o "$reg_name" == "nil" -o "$reg_addr" == "nil" -o "$reg_port" == "nil" ]
then
### 事件 remark:缺少数据
event_remark="服务注册失败,缺少数据"
else
### 注册 service, "@-" 表示 curl 从 stdin 读取数据,即 "__consul_register_service" 的 stdout
__consul_register_service | curl -s -X PUT ${HTTP_API} -d @-
### 查询 service
__do_consul_query_service register ${reg_addr}
### 事件 remark:服务注册状态
if [ x"${exit_status}" == x"0" ]
then
event_remark="服务注册成功"
else
event_remark="服务注册失败"
fi
fi
### 写日志到 ${CONSUL_LOG}
eval printf ${history_format} >> ${CONSUL_LOG}
}
### Consul deregister service
__do_consul_deregister_service() {
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/agent/service/deregister"
### Consul JSON datafile - Declare
__consul_json_datafile_def
### Consul service ID(UUID)and servcie name
eval reg_id=$(cat ${json_datafile} | jq ". | .ServiceID" | grep ${CONSUL_IPADDR})
eval reg_name=$(cat ${json_datafile} | jq ". | .ServiceName" | sort -u)
: ${reg_id:=nil}
: ${reg_name:=nil}
### 自定日志格式
date_now=$(date "+%Y-%m-%d %H:%M:%S")
history_format='"%s - %-13s | %s | %s\n" "${date_now}" "event=$event" "reg_id=${reg_id}"
"${event_remark}"'
if [ x"${reg_id}" == x"nil" ]
then
### 事件 remark:缺少数据
event_remark="服务注销失败,缺少数据"
else
### 注销 service
curl -s -X PUT ${HTTP_API}/${reg_id}
### 查询 service
__do_consul_query_service deregister ${reg_id}
### 事件 remark:服务注销状态
if [ x"${exit_status}" == x"1" ]
then
event_remark="服务注销成功"
[ -e "${json_datafile}" ] && rm -rf ${json_datafile}
else
event_remark="服务注销失败"
fi
fi
### 写日志到 ${CONSUL_LOG}
eval printf ${history_format} >> ${CONSUL_LOG}
}
### Read docker events
__do_events_readline() {
### readline conntent and proc count settings
readline=$1 ; proc_count=$2
### Maximum threads Number
__do_proc_mkfifo ${proc_count}
cat < $readline | while read line
do
id_expr="container start |container die |container pause |container unpause "
id=$(echo $line | grep -oP "(?<=${id_expr})[[:alnum:]]+(?= \(*)") ; id=${id:0:12}
event_expr="start|die|pause|unpause"
event=$(echo $line | grep -oP "(?<=container )(${event_expr})")
read -u7
{
### 往 ${proc_fifo} 文件中,增加 1 个可用进程
echo >&7
### Cycle init Num and Exit status
cycle_init_num=0 ; exit_status="nil"
case $event in
#### 容器启动(start)、恢复(unpause)事件
start|unpause)
### Debug
#echo "ID=$id | event=$event" ; exit 1
### 保存默认的内部域分隔符(OIFS,用于还原 IFS 分隔符)
### 新 IFS 分隔符,设置为 "."(用于验证 IP 的合法字段数)
OIFS=$IFS ; IFS="."
while [ 1 ]
do
IPADDR=$(docker exec $id ifconfig ${COTAINER_NIC} | grep -oP '\d.+(?= (Bcast:|netmask))')
array_IP=( $IPADDR )
if [ x"${#array_IP[@]}" == x"4" ] && \
[[ $IPADDR =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || \
[ "$cycle_init_num" == "$cycle_count" ]
then
IPADDR=${IPADDR:=nil} ; break
fi
cycle_init_num=$[cycle_init_num + 1]
sleep ${sleep_time}
done
### 还原内部域分隔符
IFS=$OIFS
### IPADDR 获取异常退出,记得需要往 ${proc_fifo} 文件中,增加 1 个可用进程
[ x"$IPADDR" == x"nil" ] && exit 1
### Retrieve $HOST, $SERVICE_NAME and $SERVICE_PORT variables from Container {{.Config.Env}}
env_var=( HOST SERVICE_NAME SERVICE_PORT )
env_expr=$(echo ${env_var[@]} | sed 's/ /|/g')
declare -A env_array
eval env_array=( $(docker inspect --format '{{join .Config.Env "\n"}}' $id | \
awk -F'=' '/'"${env_expr}"'/ { print "["$1"]="$2 }') )
HOST=$(echo ${env_array[${env_var[0]}]})
SERVICE_NAME=$(echo ${env_array[${env_var[1]}]})
SERVICE_PORT=$(echo ${env_array[${env_var[2]}]})
### Consul register variables
declare -A reg_array
reg_array=(
[reg_id]=${SERVICE_NAME}@${HOST}
[reg_name]=${SERVICE_NAME}
[reg_addr]=$IPADDR
[reg_port]=${SERVICE_PORT}
)
### Debug
#echo ${reg_array[@]} ; exit 1
### Register service
__do_consul_register_service ${reg_array[@]}
;;
#### 容器停止(stop)、暂停(pause)事件
die|pause)
### Debug
#echo "ID=$id | event=$event" ; exit 1
### Deregister service
__do_consul_deregister_service $id
;;
esac
} &
event_proc=$(ps -ef | grep "docker events" | grep -v grep | wc -l)
[ x"$event_proc" != x"1" ] && {
__do_kill_events_proc
__do_up_events_proc
}
sleep ${sleep_time}
done
}
### Main
__main__() {
__do_events_mkfifo $event_fifo
__do_events_readline $event_fifo $thread_count
}
__main__

Systemd service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=Consul registry Service
After=network.target
Wants=network.target
[Service]
ExecStartPre=/apex.fw/bin/check-env.sh eth0
EnvironmentFile=/apex.fw/conf/host.env
EnvironmentFile=/apex.fw/conf/consul.env
ExecStart=/apex.fw/bin/consul-registry
Restart=on-failure
Type=simple
[Install]
WantedBy=multi-user.target

测试

日志输出

1
2
3
4
2017-04-20 14:01:23 - event=pause | reg_id=gitbook@192.168.20.7 | 服务注销成功
2017-04-20 14:01:28 - event=unpause | reg_id=gitbook@192.168.20.7 | reg_name=gitbook, reg_addr=192.168.100.71, reg_port=4000 | 服务注册成功
2017-04-20 14:01:31 - event=die | reg_id=gitbook@192.168.20.7 | 服务注销成功
2017-04-20 14:01:35 - event=start | reg_id=gitbook@192.168.20.7 | reg_name=gitbook, reg_addr=192.168.100.71, reg_port=4000 | 服务注册成功

DNS 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# dig gitbook.service.mallux.org
; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> gitbook.service.mallux.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51397
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;gitbook.service.mallux.org. IN A
;; ANSWER SECTION:
gitbook.service.mallux.org. 0 IN A 192.168.100.71
gitbook.service.mallux.org. 0 IN A 192.168.100.51
;; Query time: 2 msec
;; SERVER: 192.168.20.5#53(192.168.20.5)
;; WHEN: Thu Apr 20 14:03:42 CST 2017
;; MSG SIZE rcvd: 74

ping 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ping -c2 gitbook.service
PING gitbook.service.mallux.org (192.168.100.71) 56(84) bytes of data.
64 bytes from 192.168.100.71: icmp_seq=1 ttl=61 time=0.727 ms
64 bytes from 192.168.100.71: icmp_seq=2 ttl=61 time=0.886 ms
--- gitbook.service.mallux.org ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.727/0.806/0.886/0.084 ms
# ping -c2 gitbook.service
PING gitbook.service.mallux.org (192.168.100.51) 56(84) bytes of data.
64 bytes from 192.168.100.51: icmp_seq=1 ttl=61 time=1.10 ms
64 bytes from 192.168.100.51: icmp_seq=2 ttl=61 time=1.11 ms
--- gitbook.service.mallux.org ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.108/1.111/1.114/0.003 ms

查询服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# curl -s http://192.168.20.7:8500/v1/catalog/service/gitbook | \
jq '.[] | { ServiceAddrss: .ServiceAddress, ServiceID: .ServiceID, ServiceName: .ServiceName }'
### 华丽的分隔线
---
{
"ServiceName": "gitbook",
"ServiceID": "gitbook@192.168.20.5",
"ServiceAddrss": "192.168.100.51"
}
{
"ServiceName": "gitbook",
"ServiceID": "gitbook@192.168.20.7",
"ServiceAddrss": "192.168.100.71"
}

Registrator + consul-registry

  • consul-registry 脚本:可以在 不依赖 registrator 的情况下,完成容器 service、health check 的注册和注销
  • 支持 单宿主机,多个同名 service_name 的容器注册。容器 Service 的 ServiceID<宿主机主机名>:<容器 ID>:<容器 SERVICE_PORT> 组成
  • 容器 service 注册事件:
    • 结合 registrator,registrator 会监听 docker events,向 Consul 注册 service
    • 当容器启动时,会向 Consul 查询相关的 service 是否定义
    • 若在容器启动时,没有查询到 service 定义,则由 __do_consul_register_service_check 函数注册 service
  • registrator 注册事件:
    • 添加(added)、删除(removed) 的容器可以正常触发 consul service 注册、注销事件
      • 添加:注册 Consul service、check
      • 删除:注销 Consul service、check
    • 暂停(pause)、恢复(unpause) 的容器不会触发注册、注销事件(registrator 不支持,没有监听该类事件):
      • 暂停:是否注销服务由 __do_consul_deregister_service 函数控制,可传参数为:
        • deregister:注销 Consul servcie
        • underegister:不注销 Consul service,注销时间由 deregister_critical_service_after 参数控制
      • 恢复:由 __do_consul_register_service_check 函数控制,当事件为 unpause 时,重注册 service check

容器需要提供的 env 环境变量

  • SERVINE_NAME:Service Name,例 SERVICE_NAME=gitbook
  • SERVICE_PORT:Service Port(仅一个),指定需要检测服务存活的端口,例 SERVICE_PORT=4000
  • SERVICE_TAGS:Service Tags,可以指定多个,以逗号隔开,例 SERVICE_TAGS=gitbook,book

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
#!/usr/bin/env bash
## --------------------------------------------------
## Filename: consul-registry
## Revision: latest stable
## Author: Mallux
## E-mail: hj.mallux@gmail.com
## Blog: blog.mallux.me
## Description:
## --------------------------------------------------
## Copyright © 2014-2017 Mallux
## ------ 修改区域,修改 变量 默认值 ------
cmd=${BASH_SOURCE##*/}
## Docker events fifo file
event_fifo="/tmp/docker.events"
proc_fifo="/tmp/proc.events"
## Env variables
NIC=eth0 ; CONTAINER_NIC=$NIC
CONSUL_PORT=8500 ; CONSUL_IPADDR=$(ifconfig $NIC | grep -oP '\d.+(?= (Bcast:|netmask))')
CONSUL_LOG="/data/consul/logs/${cmd}.log" ; mkdir -p ${CONSUL_LOG%/*}
## 监听正在运行的容器
## - 监听 docker events 事件,写正在运行中的容器 ID 到此目录下。文件内容为:向 Consul 注册的 Service Data
## - 在容器异常停止时,捕获容器 ID,注消 Consul 中对应的 ServiceID,并删除对应容器运行时文件
container_rundir="/tmp/$cmd" ; mkdir -p ${container_rundir}
## While [ 1 ] 循环次数: ${cycle_count} + 1 ; 脚本 单流程 处理总时间 = ${cycle_count} * ${sleep_time}
## - 向 Consul 注册/注销 服务,在 容器异常 无法获取容器 $NIC 网卡的 IP 地址或相关的 Service 定义时,
## - 在 While 循环一定的时间后,自动退出。当正确检索到容器 IP 或 Service 时,会马上退出 While 循环
cycle_count=120 ; sleep_time=1
## 并发处理 docker events 的进程数
thread_count=3
## ------ 以下内容无需修改,除了改变程序 处理逻辑 和 变量定义 的情况 ------
## 脚本挂起清理操作,无论脚本退出状态是什么,都会执行 __do_clean_up 函数内定义的操作
trap "__do_clean_up" EXIT
__do_clean_up() {
## Delete ${event_fifo} file
[ -e ${event_fifo} ] && {
exec 6>&-
rm -rf ${event_fifo}
}
## Kill docker events process
__do_kill_events_proc
## Delete ${proc_fifo} file
[ -e ${proc_fifo} ] && {
exec 7>&-
rm -rf ${proc_fifo}
}
}
## Kill docker events proc
__do_kill_events_proc() {
pid=$(ps -ef | grep "docker events" | grep -v grep | awk '{ print $2 }')
[ -n "$pid" ] && kill -9 $pid
}
## StartUP docker events proc
__do_up_events_proc() {
nohup docker events -f event=start -f event=unpause \
-f event=pause -f event=die >&6 &
}
## Maximum threads Number
__do_proc_mkfifo() {
thread=$1 ; [ x"$1" == x"" ] && thread=1
[ ! -p ${proc_fifo} ] && {
rm -rf ${proc_fifo}
mkfifo ${proc_fifo}
}
exec 7<>${proc_fifo}
for (( i=0;i<$thread;i++ )) ; do
echo
done >&7
}
## Redirect docker events to ${event_fifo} file
__do_events_mkfifo() {
if [ ! -e $1 ] ; then
mkfifo $1
else
[ ! -p $1 ] && {
rm -rf $1
mkfifo $1
}
fi
exec 6<>$1 ; __do_up_events_proc
}
## ------ Consul Service and Health Check 定义 ------
## Consul service register datafile - Declare
__consul_register_datafile_def() {
register_datafile="${container_rundir}/${container_id}"
}
## Consul service register data - ${reg_tags}
__consul_register_tags_def() {
tags_array=( $(echo ${reg_tags} | sed 's/,/ /g' | xargs -n1 | sed -e "s/^/\"/ ; s/$/\"/") )
reg_tags=$(echo ${tags_array[@]} | sed 's/ /,/g')
}
## Consul service register data - CheckID
declare -A check_id_array=(
[ping_check]=ping_check
[tcp_check]=tcp_check
)
## Consul register service
__consul_register_service() {
## 请注意(强迫症,代码排版):
## - "cat <<-EOF" 到 EOF 区域有前导的 Tab 字符(原有 \t 模式,非四个空格)
## - "-EOF" 代表忽略前导 Tab 字符,相当于 后续字符 顶行输出
## - Author's vim 的本地设置,默认会转换 Tab 为四个空格
## - 因此,若使用 Tab 的原有模式,需要在 INSERT 模式下,使用 ctrl+v and Tab 组合键
cat <<-EOF
{
"id": "${reg_id}",
"name": "${reg_name}",
"address": "${reg_addr}",
"tags": [${reg_tags}],
"port": ${reg_port},
"enableTagOverride": false
}
EOF
}
## Consul health check - ping_check
__consul_register_ping_check() {
cat <<-EOF
{
"name": "${check_id_array[ping_check]}@${container_id}",
"script": "/welab.co/bin/consul-health ping",
"interval": "10s",
"timeout": "3s",
"deregister_critical_service_after": "1m",
"ServiceID": "${reg_id}"
}
EOF
}
## Consul health check - tcp_check
__consul_register_tcp_check() {
cat <<-EOF
{
"name": "${check_id_array[tcp_check]}@${container_id}",
"tcp": "${reg_addr}:${reg_port}",
"interval": "10s",
"timeout": "3s",
"deregister_critical_service_after": "1m",
"ServiceID": "${reg_id}"
}
EOF
}
## ------ Consul Service register/deregister events ------
## Consul query service
__do_consul_query_service() {
## Consul register service name and id
reg_name=$1 ; reg_id=$2
## Consul service 查询 API
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/agent/services"
## ./jq 检索 服务(service)的键表达式
jq_expr=".ID, .Service"
service_isReg=$(curl -s -X GET ${HTTP_API} | jq ".[] | ${jq_expr}" | xargs -n2 | grep "${reg_id} ${reg_name}")
## Consul service 注册状态,初始 nil
: ${service_isReg:=nil}
## Consul service register status
if [ x"$service_isReg" != x"nil" ] ; then
reg_status=true
## 注册服务:检索指定数据,导出为 文件名 为容器 ID,文件内容 为服务注册的数据
reg_data=$(
for key in ${!reg_array[@]} ; do
echo $key=${reg_array[$key]}
done
)
## 写 Consul service register data 到 ${register_datafile}
echo ${reg_data[@]} | xargs -n1 > ${register_datafile}
else
reg_status=false
fi
}
## Consul query service check
__do_consul_query_service_check() {
## Consul register service, id and check name
reg_name=$1 ; reg_id=$2 ; check=$3
## Consul check 查询 API
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/health/checks"
## ./jq 检索 检查(check)的键表达式
jq_expr=".ServiceID, .CheckID"
check_isReg=$(curl -s -X GET ${HTTP_API}/${reg_name} | jq ".[] | ${jq_expr}" | xargs -n2 | grep "${reg_id} ${check}")
## Consul check 注册状态,初始 nil
: ${check_isReg:=nil}
## Consul check register status
[ x"$check_isReg" != x"nil" ] && reg_status=true || reg_status=false
}
## retrieve service port isAlive
__do_retrieve_service_port_isAlive() {
nc -w 1 -v ${reg_addr} ${reg_port} 2>/dev/null 1>&1
[ $? -eq 0 ] && service_isAlive=true
}
## Consul register service and check
__do_consul_register_service_check() {
reg_id=${reg_array[reg_id]:=nil}
reg_name=${reg_array[reg_name]:=nil}
reg_addr=${reg_array[reg_addr]:=nil}
reg_port=${reg_array[reg_port]:=nil}
container_id=${reg_array[container_id]:=nil}
## Consul service register datafile - Declare
__consul_register_datafile_def
## 自定日志格式
date_now=$(date "+%Y-%m-%d %H:%M:%S")
history_format='"%s - %-13s | %s | %s, %s, %s | %s\n" "${date_now}" "event=$event"
"container_id=${container_id}" "reg_name=${reg_name}"
"reg_addr=${reg_addr}" "reg_port=${reg_port}"
"${event_remark}"'
if [ x"$reg_id" == x"nil" -o x"$reg_name" == x"nil" -o x"$reg_addr" == x"nil" -o x"$reg_port" == x"nil" ]
then
## 事件 remark:缺少数据
event_remark="Check 注册失败,缺少数据 [ ${cycle_initNum}s ]"
else
if [ x"$event" == x"unpause" ] ; then
## Load consul service register data
. ${register_datafile}
## Consul service register data - ${reg_tags}
__consul_register_tags_def
## Consul service 注册 API
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/agent/service/register"
while [ 1 ] ; do
if [ x"${service_isAlive}" == x"true" ] || [ ${cycle_initNum} == ${cycle_count} ] ; then
break
else
## 检索 service port isAlive
__do_retrieve_service_port_isAlive
[ x"${service_isAlive}" == x"true" ] && {
## 注册 service, "@-" 表示 curl 从 stdin 读取数据,即 "__consul_register_service" 的 stdout
__consul_register_service | curl -s -X PUT ${HTTP_API} -d @-
} || {
event_remark="服务启动异常,端口检测失败 [ ${cycle_initNum}s ]"
}
fi
done
let cycle_initNum+=1 ; sleep ${sleep_time} ; #echo $cycle_initNum
fi
## 检查 Service 是否注册
while [ 1 ] ; do
__do_consul_query_service ${reg_name} ${reg_id}
if [ x"${reg_status}" == x"true" ] || [ ${cycle_initNum} == ${cycle_count} ] ; then
break
elif [ x"${reg_status}" == x"false" ] ; then
## Consul service register data - ${reg_tags}
__consul_register_tags_def
## Consul service 注册 API
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/agent/service/register"
## 检索 service port isAlive
__do_retrieve_service_port_isAlive
[ x"${service_isAlive}" == x"true" ] && {
## 注册 service
__consul_register_service | curl -s -X PUT ${HTTP_API} -d @-
} || {
event_remark="服务启动异常,端口检测失败 [ ${cycle_initNum}s ]"
}
fi
let cycle_initNum+=1 ; sleep ${sleep_time} ; #echo $cycle_initNum
done
## 未注册 service,退出
[ x"${reg_status}" == x"nil" -o x"${service_isAlive}" == x"nil" ] && {
## 写日志到 ${CONSUL_LOG}
eval printf ${history_format} >> ${CONSUL_LOG}
## 中断程序处理流程,退出
exit 1
}
## 事件 remark
event_remark="Check [" ; length=${#check_id_array[@]} ; i=1
## 绑定 service check, "@-" 表示 curl 从 stdin 读取数据,即 "__consul_register_${check}" 的 stdout
for check in ${check_id_array[@]} ; do
## Consul check 注册 API
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/agent/check/register"
## 注册 service check
__consul_register_${check} | curl -s -X PUT ${HTTP_API} -d @-
## 查询 service check
__do_consul_query_service_check ${reg_name} ${reg_id} $check
case $check in
${check_id_array[ping_check]}) chk=PING: ;;
${check_id_array[tcp_check]}) chk=TCP: ;;
esac
## 事件 remark:service check 注册状态
if [ x"${reg_status}" == x"true" ] ; then
event_remark="${event_remark}${chk}✔"
else
event_remark="${event_remark}${chk}✖"
fi
[ x"$i" != x"$length" ] && event_remark="${event_remark},"
let i+=1
done
event_remark="${event_remark}] [ ${cycle_initNum}s ]"
fi
## 写日志到 ${CONSUL_LOG}
eval printf ${history_format} >> ${CONSUL_LOG}
}
## Consul deregister service check
__do_consul_deregister_service() {
## deregister service control
deregister=$2
## Consul service 注销 API
HTTP_API="http://${CONSUL_IPADDR}:${CONSUL_PORT}/v1/agent/service/deregister"
## Consul service register datafile - Declare
__consul_register_datafile_def
## Load consul register data
. ${register_datafile}
: ${reg_id:=nil}
: ${reg_name:=nil}
## 自定日志格式
date_now=$(date "+%Y-%m-%d %H:%M:%S")
history_format='"%s - %-13s | %s | %s\n" "${date_now}" "event=$event" "container_id=${container_id}"
"${event_remark}"'
if [ x"${deregister}" == x"deregister" ] ; then
if [ x"${reg_id}" == x"nil" ]
then
## 事件 remark:缺少数据
event_remark="服务注销失败,缺少数据 [ ${cycle_initNum}s ]"
else
## 注销 service
curl -s -X PUT ${HTTP_API}/${reg_id}
## 查询 service
__do_consul_query_service ${reg_name} ${reg_id}
## 事件 remark:服务注销状态
if [ x"${reg_status}" == x"false" ] ; then
event_remark="服务注销成功 [ ${cycle_initNum}s ]"
else
event_remark="服务注销失败 [ ${cycle_initNum}s ]"
fi
fi
else
event_remark="服务不注销,由 Consul 控制 [ ${cycle_initNum}s ]"
fi
## Docker event die 事件,删除 ${register_datafile}
[ x"$event" == x"die" -a -e "${register_datafile}" ] && rm -rf ${register_datafile}
## 写日志到 ${CONSUL_LOG}
eval printf ${history_format} >> ${CONSUL_LOG}
}
## Read docker events
__do_events_readline() {
## readline conntent and proc count settings
readline=$1 ; proc_count=$2
## Maximum threads Number
__do_proc_mkfifo ${proc_count}
cat < $readline | while read line
do
## 往 ${proc_fifo} 文件中,增加 1 个可用进程
echo >&7
## Container id
id_expr="container start |container die |container pause |container unpause "
id=$(echo $line | grep -oP "(?<=${id_expr})[[:alnum:]]+(?= \(*)") ; container_id=${id:0:12}
## Container event
event_expr="start|die|pause|unpause"
event=$(echo $line | grep -oP "(?<=container )${event_expr}")
## Container name
container_name=$(echo $line | grep -oP "(?<=, name=).*(?=\))") ; container_name=${container_name%%,*}
read -u7
{
## Cycle init Num, Service register and running status
cycle_initNum=1 ; reg_status=nil ; service_isAlive=nil
case $event in
## 容器启动(start)、恢复(unpause)事件
start|unpause)
## Debug
#echo "ID=${container_id} | event=$event" ; exit 1
## 保存默认的内部域分隔符(OIFS,用于还原 IFS 分隔符)
## 新 IFS 分隔符,设置为 "."(用于验证 IP 的合法字段数)
OIFS=$IFS ; IFS="."
while [ 1 ]
do
container_ip=$(docker inspect --format '{{json .NetworkSettings.Networks}}' ${container_id} | \
jq ".[] | .IPAddress" -r)
env_hostIP=$(docker inspect --format '{{join .Config.Env "\n"}}' ${container_id} | \
awk -F'=' '/^HOST/ { print $2 }')
## 获取容器 IP
## - 自定义网络,取 .NetworkSettings.Networks 中的 IPAddress ( HOST 模式下,为空值 )
## - 若上述 IPAddress 取不到值,则取 Config.Env 中的 HOST IP
: ${container_ip:=${env_hostIP}}
array_IP=( ${container_ip} )
if [ x"${#array_IP[@]}" == x"4" ] && \
[[ ${container_ip} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || \
[ ${cycle_initNum} == ${cycle_count} ]
then
container_ip=${container_ip:=nil} ; break
fi
let cycle_initNum+=1 ; sleep ${sleep_time} ; #echo ${cycle_initNum}
done
## 还原内部域分隔符
IFS=$OIFS
## 容器 container_ip 获取异常,退出
[ x"${container_ip}" == x"nil" ] && {
date_now=$(date "+%Y-%m-%d %H:%M:%S")
history_format='"%s - %-13s | %s | %s | %s\n" "${date_now}" "event=$event"
"container_id=${container_id}"
"reg_addr=${container_ip}"
"${event_remark}"'
## 事件 remark:缺少数据
event_remark="Check 注册失败,缺少数据 [ ${cycle_initNum}s ]"
## 写日志到 ${CONSUL_LOG}
eval printf ${history_format} >> ${CONSUL_LOG}
## 中断程序处理流程,退出
exit 1
}
## Retrieve $HOST, $SERVICE_NAME, $SERVICE_PORT variables from Container {{.Config.Env}}
env_var=( HOST SERVICE_NAME SERVICE_PORT SERVICE_TAGS )
env_expr=$(echo ${env_var[@]} | sed 's/ /|/g')
declare -A env_array
eval env_array=( $(docker inspect --format '{{join .Config.Env "\n"}}' ${container_id} | \
awk -F'=' '/'"${env_expr}"'/ { print "["$1"]="$2 }') )
host_name=$HOSTNAME
host=$(echo ${env_array[${env_var[0]}]})
service_name=$(echo ${env_array[${env_var[1]}]})
service_port=$(echo ${env_array[${env_var[2]}]})
service_tags=$(echo ${env_array[${env_var[3]}]})
## Consul register variables
declare -A reg_array
reg_array=(
[reg_id]=${host_name}:${container_name}:${service_port}
[reg_name]=${service_name}
[reg_addr]=${container_ip}
[reg_port]=${service_port}
[reg_tags]=${service_tags}
[container_id]=${container_id}
)
## Debug
#echo ${reg_array[@]} ; exit 1
## Register Health check for service
__do_consul_register_service_check ${reg_array[@]}
;;
## 容器停止(stop)、暂停(pause)事件
die|pause)
## Debug
#echo "ID=${container_id} | event=$event" ; exit 1
## Deregister service, $2 可选值 ( deregister: 注销服务 / underegister: 不注销服务 )
__do_consul_deregister_service ${container_id} deregister
;;
esac
} &
event_proc=$(ps -ef | grep "docker events" | grep -v grep | wc -l)
[ x"$event_proc" != x"1" ] && {
__do_kill_events_proc
__do_up_events_proc
}
sleep ${sleep_time}
done
}
## Main
__main__() {
__do_events_mkfifo $event_fifo
__do_events_readline $event_fifo $thread_count
}
__main__

日志

1
2
3
4
2017-04-22 21:03:11 - event=pause | container_id=7b93b579f36b | 服务注销成功
2017-04-22 21:03:30 - event=unpause | container_id=7b93b579f36b | reg_name=gitbook, reg_addr=192.168.100.250, reg_port=4000 | Check [PING:✔,TCP:✔]
2017-04-22 21:03:42 - event=die | container_id=7b93b579f36b | 服务注销成功
2017-04-22 21:03:45 - event=start | container_id=6c1200f7c88d | reg_name=gitbook, reg_addr=192.168.100.250, reg_port=4000 | Check [PING:✔,TCP:✔]

未完待续

END