zerodown

package module
v0.0.0-...-9235749 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 23, 2023 License: MIT Imports: 9 Imported by: 0

README

Fornaxian Zerodown

Extremely simple zero-downtime deployments. Used in pixeldrain.com

Use in combination with https://github.com/libp2p/go-reuseport.

How does it work?

Zerodown captures the initialization of your application to create a parent and a child process. The parent process starts your application as a child. All arguments and variables are passed through.

When the SIGHUP signal is caught by the parent process it starts a new child process from the same executable. If the executable has been updated it will run the updated code.

Usage

zerodown.Init() should be the very first thing your program runs when starting. Right at the top of the main function. Then you can start your whole application server. Listen on ports, connect to the database, etc.

When you are ready to serve requests you call zerodown.StartupFinished(). This will tell the parent process that you are done and it will shutdown the previous process.

Example

func main() {
	// Initialize the zerodown parent process. When this function returns true
	// the process must exit
	if zerodown.Init() {
		return
	}

	// Initialize your server: Connect to database, listen on a port, start a
	// server, etc
	listener, err := reuseport.Listen("tcp", ":443");
	if err != nil {
		panic(err)
	}

	// Tell zerodown that this process is ready to serve requests. This will
	// cause the previous process to shut down.
	zerodown.StartupFinished()

	// Listen for signals and exit when done. Zerodown will send a SIGINT signal
	// when it wants us to shut down. It's very important that we listen for
	// that one
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)

	sig := <-signals
	fmt.Printf("Caught %s signal, stopping...\n", sig)

	listener.Close()
}

Creating Listeners

If you're running a server application of some kind you'll need a socket to listen on. Normally only a single process can listen on a TCP port at one time, luckily for us SO_REUSEPORT exists. This allows multiple processes to listen on a network port. Now we can seamlessly pass traffic from the old process to the new process without any downtime.

Another approach is to start the listeners in the parent process and pass them to the child as file descriptors. This can be done by using the net.ListenTCP() function to get your listener and calling File() on it:

listener, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 443})
file, err := listener.File()
zerodown.ExtraFiles = []*os.File{file}

The child can then get the file descriptor from the parent and convert it back to a listener:

listener := net.FileListener(os.NewFile(3, "MyListener"))

The example directory contains an example for passing down listeners and systemd socket activation.

Example systemd service file

With this systemd service you can use systemctl reload to reload your server.

[Unit]
Description=My API server
After=network.target

[Service]
Type=simple
ExecStart=/bin/myapiserver

KillMode=control-group
KillSignal=SIGINT

# The HUP signal tells zerodown to restart the child process
ExecReload=kill -HUP $MAINPID

# Allow non-root process to bind to privileged ports
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Logger = log.Default()

	// Time to wait for a new process to initialize. When this time has passed
	// the old process will be killed whether the new processes has finished
	// initializing or not.
	StartupTimeout = time.Minute * 10

	// Signals which cause the child process to be restarted
	ReloadSignals = []os.Signal{syscall.SIGHUP}

	// Signals which cause the server to shut down. The first signal in this
	// list is used to shut down the child process when it's time to restart
	StopSignals = []os.Signal{syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT}

	// Signals to pass through to the child process
	PassthroughSignals []os.Signal

	// Signal the child process sends to the parent process to indicate that
	// initialization is finished. Make this this is the same on both the parent
	// and the child
	StartupFinishedSignal = syscall.SIGUSR1

	// Extra file descriptors to pass to the child process
	ExtraFiles []*os.File

	// Extra environment variables to pass to the child process
	ExtraVariables []string
)

Functions

func Init

func Init() (exit bool)

Initialize the zerodown parent process. This should be the very first thing your main function calls. If this function returns exit=true then your main function should return.

Correct usage:

func main() {
	if zerodown.Init() {
		return
	}

	// Initialize your server: Connect to database, listen on a port, etc

	zerodown.StartupFinished()

	// Listen for signals and exit when done
}

func IsChild

func IsChild() bool

func IsParent

func IsParent() bool

func Restart

func Restart() (err error)

Restart allows a child process to call for a restart. The parent process will start a new child and will shut down the current process

func StartupFinished

func StartupFinished()

Types

This section is empty.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL