cloudflare / tableflip

Graceful process restarts in Go
BSD 3-Clause "New" or "Revised" License
2.91k stars 148 forks source link

Does tableflip work with systemd's socket activation? #14

Closed matthewmueller closed 5 years ago

matthewmueller commented 5 years ago

Hi there, I'm wondering if this library can work alongside systemd's socket activation?

What I'm trying to do is basically use systemd's root access to listen on port 80 and pass that file descriptor into my Go application so my Go app doesn't have to use sudo.

Is this possible with tableflip? Does tableflip even make sense in this context? My thinking is that tableflip is would still be useful for doing the upgrade attempts.

Any feedback here would be greatly appreciated. Thanks!

lmb commented 5 years ago

You should be able to achieve this by using Fds.Listener and Fds.AddListener.

Pseudo-code:

ln, _ := upg.Fds.Listener("systemd", "something")
if ln == nil {
  // Get ln from systemd, probably via os.NewFile / net.FileListener
  upg.Fds.AddListener("systemd", "something", ln)
}
lmb commented 5 years ago

@matthewmueller did you have any luck with the above?

matthewmueller commented 5 years ago

@lmb, hey sorry for the late response! I want to say I tried this but wasn't able to get it working. I ended up sticking nginx in front for now.

I'll close this for now because I have something that's working, but I'll probably give this another shot at a later time because I just love that it's a single binary system.

If it's helpful to anyone else, here's my findings.

From the socket activation side it looks like this:

file := os.NewFile(uintptr(3), "socket")
ln, err := net.FileListener(file)
if err != nil {
    panic(err)
}

// serve, and somehow get it working with tableflip

And here's a test app that could simulate socket activation:

ln, err := net.Listen("tcp", ":8000")
if err != nil {
    panic(err)
}

file, err := ln.(*net.TCPListener).File()
if err != nil {
    panic(err)
}

cmd := exec.Command("go", "run", "main.go")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = append(cmd.ExtraFiles, file)

// serve from the other file
if err := cmd.Run(); err != nil {
    panic(err)
}

I think the ideal situation would be to say systemctl reload app and have it somehow do the upgrade, rolling back if there's been a failure. This might not make sense with socket activation in which case it might make more sense to just add capabilities to the systemd unit file for the protected port and call it the way godoc suggests.

Like I mentioned, I wasn't able to get this working nicely, but I'd love for someone else to take the lead!