News for hackers. Stuff that matters.
Re:Upgrading Executable on the Fly by mholt on Tuesday January 04, 2022 @02:21PM (#29798846)
We did this for Caddy 1 too [1]. It was really cool. I am not sure how many people used this feature, so I haven't implemented it for Caddy 2 yet, and in the ~two years that Caddy 2 has been released, I've only had the request once. It's a bit tricky/tedious to do properly, but I'm willing to bring it over to Caddy 2 with a sufficient sponsorship.

[1]: https://github.com/caddyserver/caddy/blob/v1/upgrade.go

Re:Upgrading Executable on the Fly by zimbatm on Tuesday January 04, 2022 @04:52PM (#29801081)
If Caddy were to support systemd socket activation, this self-restart dance is not necessary as the parent process (systemd) is holding the socket for you. And for other systems, they can use https://github.com/zimbatm/socketmaster instead. I believe this to be more elegant and robust than the nginx approach as there is no PID re-parenting issues.

But I suspect that most Caddy deployments are done via docker, and that requires a whole container restart anyways.

Re:Upgrading Executable on the Fly by tyingq on Tuesday January 04, 2022 @05:13PM (#29801346)
It's kind of fun to watch things go out of fashion and back in. We used to use inetd, mostly because memory was expensive, so it could spawn a service only when a request came in, then the spawned process would exit and give the memory back to the os. Then someone decided tcpd should sit between inetd and servers, for security and logging. Then, every service just ran as it's own daemon. Now I'm occasionally seeing posts like this reviving inetd.
Re:Upgrading Executable on the Fly by zimbatm on Tuesday January 04, 2022 @06:24PM (#29802427)
It's the same but a bit different.

Inetd is listening to a port and then for each new connection, spawning a new process, binding stdin/stout to the socket pair. The main issue was that it could lead to system resource exhaustion pretty easily if too many connections were being opened and there were no good ways to control that.

With systemd, the listening socket is only bound by systemd and passed to the service. The service itself is responsible for accepting and handling the new connections. So it has the control on the rate of new connections, and can also more easily share memory. The main advantage of that approach is that as soon as systemd binds the socket, new connections won't be rejected by the system and will be on hold until the service accept() them. So no connection gets dropped, even during a restart. The service itself is still responsible for gracefully shutting down existing connections on SIGTERM.

Re:Upgrading Executable on the Fly by tyingq on Tuesday January 04, 2022 @06:30PM (#29802510)
Sure, though systemd does support the inetd style setup also.
Re:Upgrading Executable on the Fly by ta988 on Tuesday January 04, 2022 @11:09PM (#29805019)
Yes despite the haters systemD is going the right direction. Just hope it doesn't crumble under its complexity.
Re:Upgrading Executable on the Fly by mholt on Tuesday January 04, 2022 @05:03PM (#29801209)
Good point, and I'm not sure which deployment method is more popular.

In general I am personally not a fan of Docker due to added complexities (often unnecessary for static binaries like Caddy) and technical limitations such as this. All my Caddy deployments use systemd (which I don't love either, sigh).

Re:Upgrading Executable on the Fly by zimbatm on Tuesday January 04, 2022 @06:34PM (#29802561)
Technically, it should be possible to pass a file descriptor to a docker process, but I haven't seen that before.

I don't like all parts of systemd but I think the socket activation is pretty elegant. The decoupling also allows to bind on port 80 and 443 (because systemd runs as root), and then still have Caddy run as a user process. I think it would unlock nice things for Caddy.

Re:Upgrading Executable on the Fly by nextaccountic on Thursday January 06, 2022 @04:31AM (#29821303)
If I'm running on containers, does this mean that I need to run systemd inside docker?
Re:Upgrading Executable on the Fly by zimbatm on Monday January 10, 2022 @11:40AM (#29877458)
For containers you need a load-balancer in front to handle failover and rolling deploys.
Re:Upgrading Executable on the Fly by kevincox on Thursday January 06, 2022 @12:51PM (#29826979)
I think one caveat is that systemd stops the old instance before starting the new one. This will give you a latency blip as requests will be queued during that time. The nginx approach doesn't introduce any latency impact.
Re:Upgrading Executable on the Fly by eliaspro on Tuesday January 04, 2022 @03:50PM (#29800270)
I'm torn on this feature.

In the one hand, an application should never be able to replace itself with "random code" to be executed. I want my systems to be immutable. I want my services to be run with the smallest set of privileges required.

On the other hand, it encourages "consumer level" users to keep their software up-to-date, even when it wasn't installed from a distribution's repository etc.

So I think in general it's a good feature to have, as advanced users/distributions will restrict what a service/process is able to to anyways and won't have any downsides of not using this feature.

It should be optional, that's all!

Re:Upgrading Executable on the Fly by mholt on Tuesday January 04, 2022 @03:55PM (#29800360)
> an application should never be able to replace itself with "random code" to be executed.

To clarify: it doesn't, nor has it ever worked that way. You have to be the one to do that (or someone with privileges to write to that file on disk). Most production setups don't give Caddy that permission. And you have to trigger the upgrade too.

Re:Upgrading Executable on the Fly by eliaspro on Tuesday January 04, 2022 @06:53PM (#29802772)
Ok, this explains a lot and also clearly shows I never used this feature :)

I always assumed it would be the Caddy process itself taking care of downloading the update & replacing the binary, before restarting it.

Re:Upgrading Executable on the Fly by mholt on Tuesday January 04, 2022 @09:29PM (#29804241)
Caddy 2 can do that at least, but it's something you have to command it to do. :)
Re:Upgrading Executable on the Fly by blibble on Tuesday January 04, 2022 @04:06PM (#29800511)
if you can log into the machine and replace the nginx executable you are probably capable of running it too
Re:Upgrading Executable on the Fly by JesseObrien on Tuesday January 04, 2022 @03:41PM (#29800133)
Can you explain any of the technical details around this perchance? I'm super curious. I know that SO_REUSEPORT[1] exists but is that the only little trick to make this work? From what I've read with SO_REUSEPORT it can open up that port to hijacking by rogue processes, so is that fine to rely on?

[1] https://lwn.net/Articles/542629/

Re:Upgrading Executable on the Fly by duskwuff on Tuesday January 04, 2022 @03:58PM (#29800393)
You don't even need that. If the old server process exec()s the new one, it can pass on its file descriptors -- including the listening socket -- when that happens.
Re:Upgrading Executable on the Fly by mholt on Tuesday January 04, 2022 @04:51PM (#29801061)
Yep, we don't use SO_REUSEPORT. We just pass it from the old process to the new one.
Re:Upgrading Executable on the Fly by tyingq on Tuesday January 04, 2022 @04:00PM (#29800429)
You could also be fancy and pass open sockets over a unix domain socket with sendmsg().
Re:Upgrading Executable on the Fly by xyzzy_plugh on Tuesday January 04, 2022 @08:17PM (#29803598)
This is the best way as it avoids any sort of session/parenting issues which are not always easy to solve portably as a parent.
Re:Upgrading Executable on the Fly by fragmede on Tuesday January 04, 2022 @04:13PM (#29800601)
If an attacker is already running rogue processes on your box, the minor details surrounding SO_REUSEPORT is the least of your worries. An attacker could just restart nginx, and won't care about lost requests.
Re:Upgrading Executable on the Fly by tyingq on Tuesday January 04, 2022 @03:59PM (#29800410)
>it can open up that port to hijacking by rogue processes

That seems relevant if the process is using a non-privileged port that's >= 1024. If we're talking about privileged ports (<= 1023), though, only another root process could hijack that, and those can already hijack you many other ways.

Re:Upgrading Executable on the Fly by cmeacham98 on Tuesday January 04, 2022 @06:19PM (#29802356)
What about processes that aren't root but hold CAP_NET_BIND_SERVICE?
Re:Upgrading Executable on the Fly by tyingq on Tuesday January 04, 2022 @06:33PM (#29802540)
Sure, should have mentioned that, and perhaps namespaces too.
Re:Upgrading Executable on the Fly by khc on Tuesday January 04, 2022 @09:21PM (#29804171)
poked around a bit of that from a previous job, here's what I remember:

1. there's a control process and worker processes

2. on upgrade, control process launches new worker processes from the new binary

3. requests are drained from old worker processes

4. most of the time nginx request handlers allocate from a per-request allocation pool, so requests mostly don't share memory

5. for the cases where there are global states, there's a separate shared memory pool that you need to allocate from (which is kind of hard to work if you are not using built-in nginx primitives)

Re:Upgrading Executable on the Fly by latchkey on Tuesday January 04, 2022 @07:31PM (#29803136)
Someone did this for golang. It isn't perfect, but works for some basic use cases... https://github.com/jpillora/overseer
Re:Upgrading Executable on the Fly by bragr on Tuesday January 04, 2022 @02:41PM (#29799173)
I've implemented this a few times in a few languages based on exactly what nginx does. It works well, and it is pretty straight forward if you are comfortable with posix style signals, sockets, and daemons.

I'm not sure it is super critical in the age of containerized workloads with rolling deploys but at the very least the connection draining is a good pattern to implement to prevent deploy/scaling related error spikes.

Re:Upgrading Executable on the Fly by wereHamster on Tuesday January 04, 2022 @03:10PM (#29799655)
Even with containerized workloads, you still have an ingress, or SPOF (or multiple, when using multicast), and the seamless restart is meant for exactly those processes. Nginx is often used (https://kubernetes.github.io/ingress-nginx/), or when you use AWS, GCS etc they provide such a service for you.

Not sure how the cloud providers do it though, maybe combination of low DNS TTL and rolling restart since they often have huge fleets of servers which handle ingress?

Re:Upgrading Executable on the Fly by nullify88 on Tuesday January 04, 2022 @03:24PM (#29799893)
A container though should be immutable and ideally shouldn't have changes made to it. If the container were to die, it'd revert back to the old version? It looks to me like these seamless upgrades would be an anti pattern to containers.

With ingress you'd have a load balancer in front or have it routed in the network layer using BGP.

Re:Upgrading Executable on the Fly by krab on Tuesday January 04, 2022 @04:20PM (#29800700)
I think the parent was talking more about the fact that at some point, you have a component that should be available as much as possible. In the case you mention, that would be the load balancer. Being able to upgrade it in place might be easier than other ways.
Re:Upgrading Executable on the Fly by wereHamster on Tuesday January 04, 2022 @04:05PM (#29800492)
How do you restart the load balancer though, without dropping traffic?
Re:Upgrading Executable on the Fly by nullify88 on Tuesday January 04, 2022 @04:40PM (#29800934)
You would need more than one to do a rolling restart. Alternatively to do it with one instance of a software load balancer is a bit more work, spin another instance up and update DNS. Wait for traffic to the old one to die as TTLs expire, then decommission.

But I agree it isn't as easy as a in place upgrade.

Re:Upgrading Executable on the Fly by deadbunny on Tuesday January 04, 2022 @06:04PM (#29802160)
2 LBs and a VIP/DNS switch.
Re:Upgrading Executable on the Fly by cbb330 on Tuesday January 04, 2022 @04:23PM (#29800734)
two nginx load balancers, reroute to the secondary via dns, restart primary
Re:Upgrading Executable on the Fly by Grollicus on Tuesday January 04, 2022 @04:19PM (#29800687)
If you really want to have no SPOF you'd probably build something like this:

Multihomed IP <-> Loadbalancer <-> Application

By having the same setup running on multiple locations you can replace the load banacers by taking one location offline (stop announcing the corresponding route). Application instances can be replaced by taking the application instance out of the load balancer.

Re:Upgrading Executable on the Fly by xyzzy_plugh on Tuesday January 04, 2022 @08:20PM (#29803618)
> Not sure how the cloud providers do it though

At least some I am familiar with operate at the packet level and can hand off live "connections" to a peer or hot standby, along with full session state.

Remember that with TCP or anything else, the abstract session is an illusion.

Re:Upgrading Executable on the Fly by moderation on Tuesday January 04, 2022 @03:16PM (#29799760)
Re:Upgrading Executable on the Fly by monroeclinton on Tuesday January 04, 2022 @05:38PM (#29801781)
Also HAProxy, they both use UNIX sockets via ancillary messages+SCM_RIGHTS I believe.

https://www.haproxy.com/blog/truly-seamless-reloads-with-hap...

Re:Upgrading Executable on the Fly by majke on Tuesday January 04, 2022 @03:20PM (#29799824)
Just a shout out: it's super hard to do it for UDP / QUIC / H3. Beware.

(but I don't think nginx supports h3 out of the box yet)

Re:Upgrading Executable on the Fly by xyzzy_plugh on Tuesday January 04, 2022 @08:22PM (#29803653)
It's fundamentally identical. How do you think one hands off a TLS session? Or any other state?

It's only a problem if your state is tangled and impossible to serialize or bundle up to hand off.

UDP is perhaps the easiest because there's nothing to do in the basic case, for example with DNS.

Re:Upgrading Executable on the Fly by krab on Tuesday January 04, 2022 @04:24PM (#29800756)
Why so? I thought UDP was stateless, making that process even easier. But I never implemented it.
Re:Upgrading Executable on the Fly by TimWolla on Tuesday January 04, 2022 @04:44PM (#29800994)
UDP itself is stateless, but QUIC itself is stateful. Without knowing the background I would assume the issue to be that the incoming UDP packets will be routed to the new process after the reload and that new process is not aware of the existing QUIC connections, because the state resides in the old process. Thus it is not able to decrypt the packets for example.
Re:Upgrading Executable on the Fly by petters on Tuesday January 04, 2022 @05:58PM (#29802056)
How are quick/http3 servers usually upgraded? As you say, it seems tricky.
Re:Upgrading Executable on the Fly by monroeclinton on Tuesday January 04, 2022 @05:31PM (#29801659)
I've been working on something similar in a load balancer I've been writing in Rust. It's still a work in progress.

Basically the parent executes the new binary after it receives a USR1 signal. Once the child is healthy it kills the parent via SIGTERM. The listener socket file descriptor is passed over an environment variable.

https://github.com/monroeclinton/- (this is the proper url, it's called dash)

Re:Upgrading Executable on the Fly by bob1029 on Tuesday January 04, 2022 @05:31PM (#29801681)
I've considered building something like this to allow for us to update customer software while it's serving users.

In my proposals, there would be a simple application-aware http proxy process that we'd maintain and install on all environments. It would handle relaying public traffic to the appropriate final process on an alternate port. There would be a special pause command we could invoke on the proxy that would buy us time to swap the processes out from under the TCP requests. A second resume command would be issued once the process is running and stable. Ideally, the whole deal completes in ~5 seconds. Rapid test rollbacks would be double that. You can do most of the work ahead of time by toggling between an A and B install path for the binaries, with a third common data path maintained in the middle (databases, config, etc)

With the above proposal, the user experience would be a brief delay at time of interaction, but we already have some UX contexts where delays of up to 30 seconds are anticipated. Absolutely no user request would be expected to drop with this approach, even in a rollback scenario. Our product is broad enough that entire sections of it can be a flaming wasteland while other pockets of users are perfectly happy, so keeping the happy users unbroken is key.

Re:Upgrading Executable on the Fly by kayodelycaon on Tuesday January 04, 2022 @05:41PM (#29801826)
https://en.wikipedia.org/wiki/Blue-green_deployment

DNS not required. You can use a load balancer to do the same thing. If you don't want a full second setup, do a rolling restart of application servers instead.

Edit: I forgot... you can do this with containers too.

Re:Upgrading Executable on the Fly by rootlocus on Tuesday January 04, 2022 @03:11PM (#29799665)
How do the two processes listen to the same port?
Re:Upgrading Executable on the Fly by nullify88 on Tuesday January 04, 2022 @04:12PM (#29800592)
Once the USR2 signal is received the master process forks, the child process inherits the parents file descriptors including listen(). One process stops accepting connections creating a queue in the kernel. The new process takes over and starts accepting connections.

You can follow the trail by searching for ngx_exec_new_binary in the nginx repo.

Re:Upgrading Executable on the Fly by krab on Tuesday January 04, 2022 @04:22PM (#29800719)
Just to add - Nginx normally spawns several worker processes that all process connections to the same port.
Re:Upgrading Executable on the Fly by nullify88 on Tuesday January 04, 2022 @04:55PM (#29801118)
Correct but to clarify, only the master process binds to the ports. The master process creates socketpairs to the workers for interprocess communication. The workers accept connections over the shared socket.

https://www.nginx.com/blog/socket-sharding-nginx-release-1-9...

Page also has an example of how SO_REUSEPORT effects flow.

Re:Upgrading Executable on the Fly by krab on Wednesday January 05, 2022 @02:46AM (#29806268)
Oh, thanks! I didn't know that. I supposed it worked by inheriting the listening socket but I didn't check.
Re:Upgrading Executable on the Fly by loeg on Tuesday January 04, 2022 @03:20PM (#29799835)
There’s an ioctl for this on FreeBSD and Linux — SO_REUSEPORT. You could also just leave the listening socket open when exec’ing the new httpd, or send it with a unix domain socket.
Re:Upgrading Executable on the Fly by markbnj on Tuesday January 04, 2022 @03:58PM (#29800394)
This article on how haproxy uses SO_REUSEPORT goes into some more detail: https://www.haproxy.com/blog/truly-seamless-reloads-with-hap...
Re:Upgrading Executable on the Fly by ctrlrsf on Tuesday January 04, 2022 @03:22PM (#29799851)
Using socket option SO_REUSEPORT allows multiple processes to bind to same port.
Re:Upgrading Executable on the Fly by riyadparvez on Tuesday January 04, 2022 @06:32PM (#29802534)
Is there any restrictions on this option? Eg only children of the same parent process are allowed to bind to same port. Otherwise how does the packet distribution work? And how does the response from that port work?
Re:Upgrading Executable on the Fly by garaetjjte on Wednesday January 05, 2022 @11:42AM (#29811256)
>So long as the first server sets this option before binding its socket, then any number of other servers can also bind to the same port if they also set the option beforehand. [...] To prevent unwanted processes from hijacking a port that has already been bound by a server using SO_REUSEPORT, all of the servers that later bind to that port must have an effective user ID that matches the effective user ID used to perform the first bind on the socket.

https://lwn.net/Articles/542629/

Re:Upgrading Executable on the Fly by VWWHFSfQ on Tuesday January 04, 2022 @03:24PM (#29799883)
is this what it's actually doing though? It doesn't say the reuseport option to the listen directive is required for this.
Re:Upgrading Executable on the Fly by bogomipz on Tuesday January 04, 2022 @05:18PM (#29801424)
I am curious does anyone know why Nginx uses SIGWINCH for this? I know Apache uses WINCH as well which makes me wonder if there was some historical reason a server process wound up using a signal meant for a TTY?
Re:Upgrading Executable on the Fly by xyzzy_plugh on Tuesday January 04, 2022 @08:25PM (#29803678)
It's otherwise unused in the context of a daemon. SIGHUP is similarly popular and historically designed for use with a TTY.
Re:Upgrading Executable on the Fly by bogomipz on Tuesday January 04, 2022 @08:38PM (#29803806)
That's what I suspected - that it would pretty much be guaranteed to not to be needed however I was not able to find any historical UNIX lore mentioning it specifically. Cheers.
Re:Upgrading Executable on the Fly by jabedude on Tuesday January 04, 2022 @04:30PM (#29800822)
Seems like a useful feature for a service manager like systemd to have for its managed services. It is already able to perform inetd style socket activation, I imagine this would be a welcome feature
Re:Upgrading Executable on the Fly by jasonjayr on Tuesday January 04, 2022 @04:43PM (#29800969)
inetd style socket activation (iirc) forks a process for every connection.

So, simply replacing the binary on disk will cause all new connections going forward to use the new binary, while existing held connections (with in-memory references to the old binary's inode) will finish the operations. Once they are done and all references to that inode are gone, the blocks referencing the binary will be removed.

Re:Upgrading Executable on the Fly by wahern on Tuesday January 04, 2022 @05:26PM (#29801577)
> inetd style socket activation (iirc) forks a process for every connection.

inetd supports both process-per-connection and single process/multiple connections using the "nowait" and "wait" declarations, respectively. The former passes an accept'd socket, the latter passes the listening socket.

Re:Upgrading Executable on the Fly by tannhaeuser on Tuesday January 04, 2022 @06:48PM (#29802720)
CGI (apache's mod_cgi) has supported this since almost the beginning of the web as well. Deploying a new CGI is as simple as replacing the CGI binary.
Re:Upgrading Executable on the Fly by TimWolla on Tuesday January 04, 2022 @04:41PM (#29800948)
This is already possible. You can configure whether you want inetd style socket activation (where systemd calls accept() and passes you the client socket)), or just systemd listening to the socket (where systemd passes you the listen socket and your binary calls accept()).

https://www.freedesktop.org/software/systemd/man/systemd.soc...

Re:Upgrading Executable on the Fly by dang on Tuesday January 04, 2022 @06:35PM (#29802573)
One thread with one comment from a long time ago:

Upgrading an Nginx executable on the fly - https://news.ycombinator.com/item?id=8677077 - Nov 2014 (1 comment)

Re:Upgrading Executable on the Fly by politelemon on Tuesday January 04, 2022 @04:31PM (#29800832)
So if I understood correctly, would it be like this

cp new/nginx /path/to/nginx kill -SIGUSR2 <processid>

That does sound pretty neat if you're not running nginx in a container. I wonder if they've built a Windows equivalent for that.

Re:Upgrading Executable on the Fly by kokizzu2 on Tuesday January 04, 2022 @09:59PM (#29804469)
Re:Upgrading Executable on the Fly by nullify88 on Tuesday January 04, 2022 @03:14PM (#29799727)
The systemv init script for nginx had an upgrade operation (in addition to start/stop/reload etc) which would send the signal. Worked like a charm.
Re:Upgrading Executable on the Fly by secondcoming on Tuesday January 04, 2022 @03:18PM (#29799800)
I've always found the multi-process approach taken by both nginx and apache to be nothing but a hindrance when you have to write a custom module. It means that you may have to use shared memory, which is a PITA.

I don't know why they haven't moved on from it; it only really made sense when uni-core processors were the norm.