Post

kubernetes cookbook 之优雅退出篇

云原生(cloud native)应用其中有一条就是能够快速迭代,应用不断升级过程中不可避免就是启停操作,删除旧版本,更新新版本,如果应用是部署在 kubernetes 上面的,那么 update deployment 过程中 kubelet 会发送 SIGTERM 信号给容器 PID 1 的进程,做为应用程序应当捕获这个信号,可以在真正退出之前做一些清理工作,例如通知注册中心自己已消亡。

捕获 SIGTERM 信号

go 语言中的 signal 包提供了接收信号的方法

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
import (
	"context"
	"os"
	"os/signal"
)

var onlyOneSignalHandler = make(chan struct{})
var shutdownHandler chan os.Signal
var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}

func SetupSignalContext() context.Context {
	close(onlyOneSignalHandler) // panics when called twice

	shutdownHandler = make(chan os.Signal, 2)

	ctx, cancel := context.WithCancel(context.Background())
	signal.Notify(shutdownHandler, shutdownSignals...) // 当接受到信号时发送给shutdownHandler channel
	go func() {
		<-shutdownHandler // 阻塞
		cancel()
		<-shutdownHandler
		os.Exit(1) // second signal. Exit directly.
	}()

	return ctx
}

SetupSignalContext() 返回给调用者一个 context.Context 对象,那么调用者就可以通过 ctx.Done() 判断是否接收到退出信号:

1
2
3
4
5
6
7
8
9
10
11
ctx := SetupSignalContext()
Run(ctx)

func Run(ctx context.Context) {
	fmt.Printf("hello world!\n")
	select {
	case <-ctx.Done():
		fmt.Println("got shutdown signal")
		break
	}
}

systemd 通知

当程序以 systemd 规范的服务启动时,例如 canoe 的 service 配置文件:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=canoe
Documentation=canoe project

[Service]
ExecStart=/usr/local/bin/canoe
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

那么在执行命令 systemctl start canoe 服务启动后,需要通知 systemd 服务已经就绪,通信的方法就是发送 READY=1 到 unixgram

go-systemd 这个库已经封装好了

1
2
3
4
5
6
7
8
// 应用就绪后执行
import "github.com/coreos/go-systemd/v22/daemon"

func main() {
	// do something
	do()
	go daemon.SdNotify(false, "READY=1")
}
This post is licensed under CC BY 4.0 by the author.