张子阳的博客

首页 读书 技术 店铺 关于
张子阳的博客 首页 读书 技术 店铺 关于

Consul 注册服务和健康检查(script检查) - Part.3

2018-12-26 作者: 张子阳 分类: 分布式系统

Consul提供的一个主要功能就是提供一个可用服务的列表,进行服务注册和发现。这在服务做负载均衡时非常有用,因为前端应用不再直接访问服务(可能访问到不可用的节点,例如节点宕机或者服务进程异常退出),而是先请求consul获取可用的服务列表,然后再访问列表中服务,从而提高了服务的可用性。Consul则负责维护这个服务列表,并对列表中的服务进行健康检查。

这篇文章将介绍如何注册服务和健康检查。注册服务和健康检查有两种方式:通过配置文件 和 通过Http API,这篇文章仅介绍通过配置文件的方式注册。

健康检查的类型分为好几种,常用的有:script检查,通过执行一段脚本检查,功能最强大,但是要手写脚本;http检查,通过发起http Get请求检查;docker检查,检查docker容器;TCP检查,检查TCP是否连接;gRPC检查,通过gRPC的健康协议进行检查。

这篇文章仅就最常用的script检查和http检查快速的过一遍流程。

注册服务(控制台类型服务/Script健康检查)

编写服务定义

服务定义说明了当前节点(定义服务的节点)具有哪些服务。在注册服务的同时,可以直接添加对该服务的健康检查方法。关于健康检查的详细官方文档在这里:https://www.consul.io/docs/agent/checks.html

通过编写脚本进行健康检查是最灵活的,缺点是你需要编写一些检查的代码(通常是shell脚本或者python脚本)。

假设我在某个节点(比如我的工作电脑mac_work上)运行了一个控制台程序,该程序提供RPC服务。有两种方式来验证它是否工作:

  1. 访问RPC服务,看是否能正常调用;
  2. 查看服务进程是否存在。

对于方式1,优点是比较全面,缺点是要编写访问该RPC服务的代码,而且这个代码不够通用。

对于方式2,优点是编写的代码简单,而且相对通用一些,缺点是可能不准确,进程存在,但是服务无法访问(例如程序BUG导致的死锁,或者是其他原因导致的异常,虽然进程无法响应但又不会退出)。

这里简单起见,我们采用方式2。我们在consul的主目录(我的是/Users/zhangzy/linux/consul)下创建一个config文件夹保存配置,一个scripts文件夹保存健康检查的脚本,服务的定义文件放置在config文件夹下。

定义服务的脚本可以很复杂也可以很简单,更多的含义可以参考https://www.consul.io/docs/agent/services.html,这里仅给出范例和主要选项的说明:

创建一个services.json保存至配置目录(启动consul时指定的目录,在我的mac_work这台电脑是:/Users/zhangzy/linux/consul/config):

{
    "services":[
        {
            "id": "datacollect.server1",
            "name":"datacollect.server",
            "tags": ["grpc primary"],
            "port": 9101,
            "meta":{
                "key1":"value1",
                "key2":"value2"
            },
            "enable_tag_override": false,
            "checks":[
                {
                    "args":["python", "/Users/zhangzy/linux/consul/scripts/check_proc.py", "datacollect.server", "1"],
                    "interval": "10s",
                    "timeout": "2s"
                }
            ]
        }
    ]
}
checks.args节点中的的datacollect.server是要检查的控制台程序。你可以使用任意一个控制台程序,哪怕只是输出一个helloworld。但注意这个控制台程序必须是长期运行的服务类型。

几个重要的配置说明一下:

有时候,同一台机器会跑多个相同名称的进程,此时这里check_proc.py就会无法区分,因为其调用的pgrep会返回多个结果。一个简单的解决方案是将应用程序重命名成类似datacollect.server1、datacollect.server2 这样,然后再配置健康检查的脚本。

编写健康检查的脚本

接下来,编写check_proc.py。对于script检查而言,通过返回码说明检查的结果:

import subprocess
import sys

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print "not enough arguments, usage: <process_name> <process_count>"
        sys.exit(2)

    process = sys.argv[1]
    count = int(sys.argv[2])
    try:
        out_bytes = subprocess.check_output("pgrep -fa " + process, shell=True)
    except subprocess.CalledProcessError as e:
        out_bytes = e.output
        print "process not exists"
        sys.exit(2)

    found = 0
    out_bytes
    lines = out_bytes.decode('utf-8').split("\n")

    list = []

    for line in lines:        
        if process in line and "python" not in line:            
            list.append(line)
            found +=1

    if found == 0:      #fail
        print "process not exists"
        sys.exit(2)
    if found > count:      #warning
        print "too many running, need {}, have {}. \n".format(count, found) + ";\n".join(list)
        sys.exit(1)
    if found < count:
        print "too few running, need {}, have {}. \n".format(count, found)+ ";\n".join(list)
        sys.exit(1)
   
    print list[0]
    sys.exit(0) #pass

这个检查接受2个参数,第1个参数是启动的命令(或者是应用程序名),第2个参数是预期个数。当实际个数为0时返回失败,大于0但不等于预期个数返回警告,等于预期个数时返回成功。

因为我们是通过python启动检查脚本的,这个命令中也包含了要检查的进程datacollect.server,因此在返回的结果中会连这个检查进程也包含进去,所以在后面需要额外剔除一下。

更新Consul配置

有两个办法可以让consul重新加载配置,比较简单的方法是执行consul reload:

$ ./consul reload
Configuration reload triggered

还有一个方法就是发送SIGHUP信号量,即 kill -1 [PID]。

之后再次打开Web UI,可以看到已经有了健康检查和结果输出:

mac_work下的服务和健康检查结果
这里直接输出了服务的进程ID,因为我本机运行了2个同名的服务应用,因此这里输出了两个id。此时简单的处理办法是不要使用相同的应用名称启动服务。

分别定义服务和健康检查

在上面的配置中,我们将健康检查的规则写在了服务定义中,除此以外,也可以分开来单独定义。在config文件夹下再创建一个checks.json文件夹,写入内容如下:

{
  "checks": [
    {
      "id": "chk_service1",
      "name": "chk_service1",
      "notes": "检查服务 datacollect.server1 是否运行",
      "args": ["python", "/Users/zhangzy/linux/consul/scripts/check_proc.py", "datacollect.server1", "1"],
      "interval": "10s",
      "service_id": "datacollect.server1"
    },
    {
      "id": "chk_service2",
      "name": "chk_service2",
      "notes": "检查服务 datacollect.server2 是否运行",
      "args": ["python", "/Users/zhangzy/linux/consul/scripts/check_proc.py", "datacollect.server2", "1"],
      "interval": "10s",
      "service_id": "datacollect.server2"
    }
  ]
}

几个主要的配置项目含义如下:

修改config/service.json,将其中的checks部分删除。此时我同时开启了两个服务datacollect.server1和datacollect.server2,所以服务定义如下:

{
    "services":[
        {
            "id": "datacollect.server1",
            "name":"datacollect.server",
            "tags": ["grpc primary"],
            "port": 9101,
            "meta":{
                "key1":"value1",
                "key2":"value2"
            },
            "enable_tag_override": false
        },
        {
            "id": "datacollect.server2",
            "name":"datacollect.server",
            "tags": ["grpc primary"],
            "port": 9102,
            "meta":{
                "key1":"value1",
                "key2":"value2"
            },
            "enable_tag_override": false
        }
    ]
}
这里两个服务的name相同,因为它们提供的是相同的服务。

使用consul reload重新加载配置,在Web UI可以看到这样的结果:

mac_work 下的服务
mac_work 下的健康检查

总结

这篇文章中,我们看了如何使用配置文件定义consul的服务和健康检查。这里的服务是一个console控制台应用,使用编写script的方式进行健康检查。本来在这篇文章中我想将http服务器的服务定义和健康检查也写进来,但考虑到篇幅已经很长,那么就在后续文章中介绍了。

感谢阅读,希望这篇文章能给你带来帮助!