最近在工作中發現一個有意思的現象,我用 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行,我沒想明白為什么要一步處理,同步處理,就算發送失敗重試三次再管也好的….