和服务树关联的系统

服务树是运维系统中最重要也是最基础的基础组件,基于服务树我们可以方便的统一管理很多资源和系统,包括:

1. 机器管理
2. 权限系统
3. 监控系统
4. 数据展示(系统资源)和可用率展示(节点关联服务)
5. 配置管理
6. 服务发现
7. 部署系统

这七个系统基于服务树节点各司其职:
1. 机器管理负责管理节点下面的机器,包括机器列表、初始化、重启等;
2. 权限系统负责管理节点下面机器的登陆权限、已经其他子系统的查看修改等权限;
3. 监控系统基于节点来管理监控,基于节点增加和删除监控非常简单;
4. 数据展示和可用率展示负责展示节点下面的机器的系统资源使用情况和节点关联服务的可用率情况;
5. 配置管理负责系统基础环境和应用基础环境,装机时指定服务节点,装机后环境会被自动初始化好(我们使用 Puppet)。

事实上 1 – 5 点我们已经有了比较丰富的经验,这里不再赘述,重点阐述一下 6 – 7 点。


服务发现

服务发现需要实现服务变更后自动发现、负载均衡等基本特性,还可以实现某些高级特性,比如区域化解析、CNAME(路由切换)、关系图谱和安全注册。

设计可以参考这篇文章的设计。

这里我们只讨论一下服务发现需要的数据结构,最基本的数据结构包括下面两个部分。

1. 服务的基础信息

1). 服务唯一标识,我们采用服务节点字符串来表示;
2). 服务启动端口。

虽然服务发现基于服务节点,但是服务发现系统对于服务节点下面的机器有独立的一份存储,服务发现系统和服务节点的机器信息对齐可以采用程序的守护进程来保证,在发布一台机器之后守护进程会自动向服务发现系统注册,并向服务节点注册,在停止一台机器的服务之前,先从服务发现系统删除,并从服务节点删除。

2. 机器列表、负责均衡算法

机器列表就是当前服务正在运行的机器列表,我们可以对机器指定负载均衡算法。

比如:
1). WRR,应当支持对节点下面的机器指定权重,默认权重一样;
2). 一致性哈希;
3). 其他算法。

除了上面这两种,我们增加了另外两种比较特殊的数据,LVS 和 Nginx,因为除了这里所说的服务发现系统,LVS 和 Nginx 作为代理也实现了服务发现的功能。增加 LVS 和 Nginx 信息是为了当 LVS 和 Nginx 后面的服务更新之后自动通知 LVS 和 Nginx 。

3. LVS 信息

需要的信息如下:
1). VIP;
2). Src Port;
3). Dest PORT(目的 IP 即是服务节点下机器 IP);
4). 其他信息(比如 syn_proxy、persistence_timeout、weight)。

当 LVS 后面的服务(Nginx)发布时,动态通知 LVS (怎么通知再讨论)摘除或增加,注意需要保证摘除和增加时流量无损。

4. Nginx 信息

需要的信息如下:
1). 服务所属的 domain 和 uri,可以有多个,对于每个 domain,还需要知道启用端口、日志路径、证书文件等;
2). upstream 名称;
3). 其他属性,可以自定义,比如 ip_hash。

当 Nginx 后面的服务(Webapp)发布时,动态通知 Nginx (怎么通知再讨论)摘除或增加,注意需要保证摘除和增加时流量无损。


部署系统

部署系统基于一个服务节点,对服务节点下面的机器进行部署(这里是机器的部署,Docker 部署额外讨论)。

1. 部署系统的组件

简单的说,部署系统至少需要三个组件:
Build Server
控制中心
执行 Agent

简单的流程:
1. Build Server 接受编译请求,编译通过后传入产品库;
2. 控制中心接收部署请求,通过 RPC 连接 Agent;
3. Agent 解析部署请求,开始执行部署工作,Agent 需要支持暂停、停止、查看日志等功能;
4. 控制中心需要支持查看任务状态等功能。

2. 环境管理

这里可以分为基础环境管理和应用环境管理。

基础环境包括:
内存参数、用户、文件句柄、基础软件包、sshd 配置等。

应用环境是指应用需要的环境,比如 Java、tomcat 等,因为我们都是基于虚拟机,每台机器只有一个应用,不存在混布的问题,所以应用环境可以直接在装机时初始化。

基础环境和应用环境通过上面第 5 点的配置管理解决,在装机时指定服务节点, 装机之后即可初始化好基础环境和应用环境。

3. 应用程序配置

通过 Puppet 解决,指定不同的 env 生成不同的配置文件。

4. 部署的扩展

在 Agent 端使用进程守护工具,比如 God,可以带来很多好处(需要做二次开发),比如:
1. 实现统一的停起方式和查看方式;
2. 实现定时任务,比如可以用来解决日志切割问题;
3. 和服务树系统对接,自动把服务信息和机器名注册到服务树;
4. 和监控系统对接,自动生成采集监控数据的 cron 脚本,并注册监控项和报警策略;
5. 和测试系统对接,实现自动化测试;
6. 和 LVS 和服务发现系统对接,自动和 LVS、服务发现系统关联。

这几点里面其中和运维系统的关联很重要。


参考:

服务树设计参考两篇文章 服务管理平台一个关于如何管理海量机器的关联结构模型

机器环境管理

对于机器的环境管理,已经痛苦很长时间了,每次机器创建之后,需要先部署基础环境,比如 Java、tomcat 等,然后再发布应用程序。对于这个问题,我之前一直想用 Docker 解决,但是事实上 Docker 也不能解决所有的问题,比如 Hadoop 并不适合 Docker,对于 Hadoop 这种适合部署在机器上的组件还需要手动维护基础环境。

作为一个有追求的不断提高效率的人,我现在终于决定要解决这个问题了,也就是机器的环境管理问题。

事实上,机器分为 物理机 和 虚拟机,对于大部分类 Web 程序我们使用虚拟机,对于 Hadoop、Mysql 等资源消耗型程序我们使用物理机,而且物理机和虚拟机基本没有「混布」,这是前提,如果混布,管理起来会麻烦很多(需要分目录来管理,不能共用)。

 

因为一个服务的环境是一样的,所以我们基于服务树来统一管理服务的环境,思路很简单:

1. 把 服务节点 和 Puppet class 做一个关联,即表示服务节点下的所有机器都使用此 Puppet class 来配置基础环境。

2. Puppet 有多个 class,class 之间的继承等关联关系通过 Puppet 配置文件来维护;

3. Puppet class 需要暴露出来一个 class 的列表来供做关联的时候做选择,这个列表可以放在 Puppet 配置中的一个文件里,文件对外提供 HTTP 下载(然后再封装成 API)。

那么,当机器来请求 Puppet 获取配置的时候,怎么返回呢?

参考 这篇文章,我们使用 Puppet 的 ENC 功能,写一个自定义脚本,传入机器名,返回包含 class 的 yaml 格式文本。脚本的流程:

1. 根据机器名获取机器属于哪个(些)服务节点(基于服务树 API);

2. 根据服务节点获取 Puppet class;

3. 拼成 yaml 格式文本并打印。

如果属于多个服务节点,而且服务节点的 Puppet class 不一样,此时可以使用 default class (只配置系统基础环境,不配置应用基础环境)。

 

但是,还有一个问题,如何在机器创建之后立即配置环境?

现状是:

1. 机器安装好之后会立即执行 Puppet;

2. Puppet 执行之后会自动安装资产系统 agent,agent 把机器上报到资产系统,表示此机器安装完成;

3. 然后调用 API 把机器关联到安装机器时指定的服务节点。

像上面讲的,Puppet 配置(应用)基础环境需要知道是哪个服务节点,而第 1 步的时候不知道是哪个服务节点。

这里抛出一个修改 ENC 脚本的方法:

1. ENC 调用服务树 API,获取机器绑定的服务节点;

2. 如果找不到节点,那么继续调用装机系统 API,获取服务节点。

这种方法只有「正在装」的机器才会调用装机系统 API,增加的调用不多,所以能接受。

 

如何自动化的生成 Nginx 配置文件

本文讨论如何自动化的生成 Nginx 配置文件,Nginx 配置文件指的是 server 的配置,这里有一个很重要的概念,就是:服务树,我们希望基于服务树来维护 server 配置需要的信息。

 

服务树的每一个节点就是一个服务,那么,我们对每个节点定义必要的信息,包括:

node_id – 节点 id,代表服务;

domain_uris – 代表服务绑定的域名和路径,支持多个,因为一个服务可能同时需要绑定外网域名和内网域名,而且有绑定多个路径的需求,它的格式如下:

[{‘domain_id’: domain_id, ‘uri’: uri }, …] ;

upstream node name – 这个服务的 upstream 名称;

servers – node_id 下绑定的机器列表(每台机器可以绑定不同的权重,以实现流量控制);

port – 这个服务监听的端口;

ip_hash – 是否对 upstream 启用 ip hash。

 

上面的 domain_id 表示域名信息,事实上域名信息包括:

domain – 域名;

ports – 端口,可以包含多个端口,并且支持是否开启 ssl;

access_log_path – 正常日志路径,默认为 ${domain}_access.log;

error_log_path – 错误日志路径,默认为 ${domain}_error.log;

ssl_cert_path – 证书文件路径,如果 ports 中没有开启 ssl,此处可以留空;

ssl_cert_key_path – 私钥文件路径,如果 ports 中没有开启 ssl,此处可以留空;

要注意的一点,如果 ssl 文件配置在 server 中,如果客户端不支持 SNI,ssl 连接可能会失败,这里我们不讨论多个域名证书不放在 server 中的配置问题。

生成配置文件基于模板,很简单,不过要注意的是,location $uri 有顺序要求,只要能保证按照 uri 的长度从高到低就没问题了。

 

自动生成配置文件的好处:

1. 在页面上点点就能增加、修改或删除服务的 Nginx 配置,比较方面,而且彻底解决了使用 git 管理配置带来的各种 rewrite 、if 等冗长的配置问题;

2. 可以基于服务做可用率的报警,根据服务可以查到 domain 和 path,然后过滤 Nginx 日志来计算可用率。而且当服务出问题的时候可以查看到服务的每台机器的可用率状况,然后做出自动剔除机器的动作。

swarm task 的更新流程

swarm 声明了 13 中 task 状态,如下:

TaskStateNew TaskState = 0
TaskStateAllocated TaskState = 64
TaskStatePending TaskState = 128
TaskStateAssigned TaskState = 192
TaskStateAccepted TaskState = 256
TaskStatePreparing TaskState = 320
TaskStateReady TaskState = 384
TaskStateStarting TaskState = 448
TaskStateRunning TaskState = 512
TaskStateCompleted TaskState = 576
TaskStateShutdown TaskState = 640
TaskStateFailed TaskState = 704
TaskStateRejected TaskState = 768

 

1. Orchestrator 判断如果 service 指定的 slot 数量大于正在运行的 slot 数量, 则创建 task,状态为 TaskStateNew;

2. TaskStateAllocated 为 task 收集网络资源之后把状态改成 TaskStateAllocated;

3. TaskStatePending 表示 Scheduler 收到请求但是还未 unassigned,在代码中我未见到有把状态改成 TaskStatePending;

4. Scheduler 收到 task 之后会把 task 调度到合适的 node 上,状态改为 TaskStateAssigned;

5. 进入 Agent 的处理流程:

1). Agent 收到 task,并且获取到 task 的 Controller 之后改成 TaskStateAccepted;

2). 如果状态为 TaskStateAccepted,改成 TaskStatePreparing;

3). 如果状态为 TaskStatePreparing,调用 Controller 的 Prepare 方法,把状态改成 TaskStateReady;

4). 如果状态为 TaskStateReady,改成 TaskStateStarting。

5). 如果状态为 TaskStateStarting,调用 Controller 的 Start 方法,把状态改成 TaskStateRunning;

6). 如果状态为 TaskStateRunning,则调用 Controller 的 Wait 方法,如果 task 执行完了,状态改为 TaskStateCompleted;

当 task 状态有变化,Agent 会通过到 Dispatcher 的 session 把状态汇报给 Dispatcher。

7). 当 task 的 DesiredState 为 TaskStateShutdown 而且状态小于 TaskStateCompleted 时,调用 Controller 的 Shutdown 方法,关闭 task。

task 的 DesiredState 在 task 创建之后是 TaskStateRunning,如果 manager 想关闭 task,把 DesiredState 设置成 TaskStateShutdown(DesiredState 只能被 manager 修改)。