All it takes is integrating just one simple call to gozd.Daemonize(). Then you will get:
- upgrade binary/service with absolutely zero downtime. high availability!
- listen to multiple port and/or socket in the same program. also able to add/remove/update them with zero downtime.
- gracefully shutdown service without breaking any existing connections.
go get -u github.com/tomasen/zero-downtime-daemon
There are sample programs in the "examples" directory.
- tcp_daemon.go
demonstrate typical tcp service daemon, listen to multiple socket and ports - args_n_conf.go
demonstrate controling daemon reload config from file by command line arguments - fcgi_std.go
demonstrate typical fcgi service daemon - fcgi_daemon.go
demonstrate extended fcgi service daemon, able to process server params - http_daemon.go demonstrate typical http service daemon
- https_daemon.go demonstrate typical https service daemon
- mixed_daemon.go (advanced usage) demonstrate mixed service(tcp/fcgi/http/https) daemon, listen to diffrent socket and ports
Basic integration steps are:
- Initialize a channel and prepare a goroutine to handler new net.Listener
- Call
gozd.Daemonize(Context, chan net.Listener)
to initializegozd
& obtain a channel to receive exit signal fromgozd
. - Wait till daemon send a exit signal, do some cleanup if you want.
kill -TERM <pid>
send signal to gracefully shutdown daemon without breaking existing connections and services.
kill -HUP <pid>
send signal to start daemon's latest binary, without breaking existing connections and services, and also absolutely zero downtime. old process will be gracefully shut down.
ctx := gozd.Context{
Hash:[DAEMON_NAME],
Command:[start,stop,reload],
Logfile:[LOG_FILEPATH,""],
Maxfds: {[RLIMIT_NOFILE_SOFTLIMIT],[RLIMIT_NOFILE_HARDLIMIT]}
User: [USERID],
Group: [GROUPID],
Directives:map[string]gozd.Server{
[SERVER_ID]:gozd.Server{
Network:["unix","tcp"],
Address:[SOCKET_FILE(eg./tmp/daemon.sock),TCP_ADDR(eg. 127.0.0.1:80)],
Chmod:0666,
},
...
},
}
cl := make(chan net.Listener,1)
go handleListners(cl)
sig, err := gozd.Daemonize(ctx, cl)
// ...
for s := range sig {
switch s {
case syscall.SIGHUP, syscall.SIGUSR2:
// do some custom jobs while reload/hotupdate
case syscall.SIGTERM:
// do some clean up and exit
return
}
}
Gateway, Load Balancer, Stateless Service
test cases
- race condition test
- stress test
- more context config validations
- better default place to store and lock pid
Help is needed to write more test cases and stress test.
Patches or suggestions that can make the code simpler or easier to use are welcome to submit issue.
The basic principle: master process fork the process, and child process evecve corresponding binary which inherit the file descriptions of the listening port and/or socket.
os.StartProcess
did the trick to append files that contains handle that is can be inherited. Then the child process can start listening from same handle which we passed fd number via environment variable as index. After that we use net.FileListener
to recreate net.Listener interface to gain access to the socket created by last master process.
We also expand the net.Listener and net.Conn, so that the master process will stop accept new connection and wait until all existing connection to dead naturally before exit the process.
The detail in in the code of reload() in daemon.go.
The zero downtime idea and code is inspired by nginx and beego. Thanks.