最近在工作中发现一个有意思的现象,我用 ctrl+c 关闭本地 consul 的时候,报警系统并没有发出告警,说我的 node 异常,自己看了一下代码,发现 consul 的关闭还是有点猫腻的,仔细来讲讲
consul agent 在正常关闭的时候会向集群发送 leave 信令,宣告自己离开集群,那么什么才叫正常关闭呢?
还是上代码:
摘自command.go handleSignals方法
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
|
func (c *Command) handleSignals(config *Config, retryJoin <-chan struct{}, retryJoinWan <-chan struct{}) int {
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
// Wait for a signal
WAIT:
var sig os.Signal
select {
case s := <-signalCh:
sig = s
case <-c.rpcServer.ReloadCh():
sig = syscall.SIGHUP
case <-c.ShutdownCh:
sig = os.Interrupt
case <-retryJoin:
return 1
case <-retryJoinWan:
return 1
case <-c.agent.ShutdownCh():
// Agent is already shutdown!
return 0
}
c.Ui.Output(fmt.Sprintf("Caught signal: %v", sig))
// Check if this is a SIGHUP
if sig == syscall.SIGHUP {
if conf := c.handleReload(config); conf != nil {
config = conf
}
goto WAIT
}
// Check if we should do a graceful leave
graceful := false
if sig == os.Interrupt && !(*config.SkipLeaveOnInt) {
graceful = true
} else if sig == syscall.SIGTERM && config.LeaveOnTerm {
graceful = true
}
// Bail fast if not doing a graceful leave
if !graceful {
return 1
}
// Attempt a graceful leave
gracefulCh := make(chan struct{})
c.Ui.Output("Gracefully shutting down agent...")
go func() {
if err := c.agent.Leave(); err != nil {
c.Ui.Error(fmt.Sprintf("Error: %s", err))
return
}
close(gracefulCh)
}()
// Wait for leave or another signal
select {
case <-signalCh:
return 1
case <-time.After(gracefulTimeout):
return 1
case <-gracefulCh:
return 0
}
}
|
首先 agent 监听了三个系统信令,os.Interrupt, syscall.SIGTERM, syscall.SIGHUP
看26行,syscall.SIGHUP,用于 reloadconfig, 这个忽略掉
接下来才是进入正题,如何正确的关闭 consul
首先我们要知道 consul agent 关闭后 consul server 一般是什么体现.
- consul ui 显示 Node 异常, check 显示是 fialling
- consul ui 中搜索不到这个 node
是什么造成这个差别呢,答案在上面代码里面的第49行到55行,正常关闭后, node 会向 server 发送一条 leave 的信令,告诉 server 我离开了,你不用管我了,但是如果是宕机的话,又不能发送这个 leave 信令,让 server 知道我falling 了,这里通过34行定义的graceful来处理,如果配置里面说需要发送 leave 信令,那么就发送一个.
这里说一下, consul 的 service 或者 node 状态监控可以通过consul_alert这个项目来做(这里吐个槽,虽然我在用这个程序做报警,但是真心觉得这个代码写得不敢恭维,不停的 exet 子进程,不停的初始化,想要写alert 扩展的,一定要考虑重复初始化到知道内存泄漏)
原理说完,槽吐完,接下来说点好的,这也是我觉得 consul 开发者很贴心的一个地方.
作者把os.Interrupt, syscall.SIGTERM两个信令分开处理,
os.Interrupt
这个信令对应的其实就是 ctrl+c, 这一般是我们在开发时才会用到,那么和这个信令配合的配置是config.SkipLeaveOnInt ,这个配置项不配置,默认就是 false, 那么`sig == os.Interrupt && !(config.SkipLeaveOnInt)` 就为 true, 接着就会执行 leave信令,这完全适用于我们在开发环境中,使用自己开发电脑上的 consul agent, 退出就自动注销,不用怕收到报警.
syscall.SIGTERM
这个信令一般我们执行
kill -15 ${pid}
就会发送,当然kill ${pid}
默认就是发送15号信令,这个信令配合config.LeaveOnTerm配置项来处理是否发送 leave 信令,这种一般是 agent 在后台运行时才会用到的,这种情况大家都知道主要场景是在生产环境,如果配置config.LeaveOnTerm= true 的话,那么停机维护的时候,也收不到烦人的报警.贴心吧
有人说,宕机怎么办,我很遗憾的告诉你 os.KILL 信令,程序接收到也没有时间执行下面的操作,直接被杀掉了,在服务器宕机的时候也许都没机会发出 kill 命令,在程序被 kernel 杀掉也就是 kill -9,当然程序也没办法执行下面的操作,当然在 server 端看到的状态就是 falling,接收收到报警,找运维人员处理就好了.
最后总结一下,关闭 consul 的正确姿势:
在前台运行的情况下:
ctrl+c +最简化配置即可正常关闭,
在后台运行的情况下:
配置中指定
LeaveOnTerm: true
,维护时,使用kill -15 ${pid}
来关闭进程,即可正常关闭掉 node, 并注销成功.
补充说明:
leave 的信令发送时异步发送的,所以如果网络不太好的情况下,也许会 leave 还没完全发出去程序就关闭掉了.代码见上面49行,我没想明白为什么要一步处理,同步处理,就算发送失败重试三次再管也好的….