How can Nginx be updated without any requests?

According to Nginx documentation :

If you need to replace the nginx binary with a new one (when upgrading to a new version or adding / removing a module server), you can do this without any service downtime - no incoming requests will be lost.

My colleague and I tried to find out: how does it work? . We know (think) that:

  • Only one process can listen on port 80 at a time
  • Nginx creates a socket and connects it to port 80
  • The parent process and any of its children can bind to the same socket, since Nginx can have several working children responding to requests

We also conducted some experiments with Nginx, for example:

  • Send kill -USR2 current master process
  • ps -ef | grep unicorn ps -ef | grep unicorn to see any unicorn processes, with their own pids and their parent pids
  • Please note that the new master process is, firstly, a child of the old master process, but when the old master process is gone, the new master process has ppid of 1.

Thus, apparently, the new master process can listen on the same socket as the old one when they both work, because at that time the new master is a child of the old master. But somehow, a new master process can become ... um ... no one is a child?

I assume this is standard Unix stuff, but my understanding of processes and ports and sockets is rather vague . Can someone explain this in more detail? Are any of our assumptions wrong? And is there such a book that I can read to really feel for this stuff?

+4
source share
2 answers

For specifics: http://www.csc.villanova.edu/~mdamian/Sockets/TcpSockets.htm describes the C library for TCP sockets.

I think the key is that after the fork process, while holding the socket file descriptor, the parent and child can both call accept () on it.

So, here is the flow. Nginx, it started fine:

  • Calls socket () and bind () and listen () to configure the socket referenced by the file descriptor (integer).
  • Starts a thread that calls accept () on a file descriptor in a loop to process incoming connections.

Then the Nginx forks. The parent continues to work as usual, but the child immediately launches a new binary file. exec () destroys the old program, memory and threads, but inherits open file descriptors: see http://linux.die.net/man/2/execve . I suspect that the call to exec () passes the handle number of the open file as a command line parameter.

Child running as part of an update:

  • Reads the descriptor number of an open file from the command line.
  • Starts a thread that calls accept () on a file descriptor in a loop to process incoming connections.
  • Tells the parent computer to refuse (stop receiving () and terminate existing connections) and die.
+4
source

I have no idea how nginx does it, but basically it can just exec new binary file that has a socket to listen to a new process with it (in fact, it remains the same process, it just replaces the executable program with This). The listening socket has a lag in incoming connections, and as long as it loads fast enough, it should be able to start them before overflowing. If not, perhaps it will work first, and then wait until it is ready to process incoming requests, and then send a listening socket command (file descriptors are inherited during forcing, both have access to it) through some internal mechanism, before you go out. Noting your observations, this is similar to what it does (if your parent process dies, your ppid is reassigned to init, i.e. Pid 1)

If it has several processes competing for reception on the same listening socket (again, I don’t know how nginx does it, maybe it has a dispatch process?), Then you can replace them one by one, do a new one program, as indicated above, but one at a time so as to never throw the ball. Please note: during such a process, there will never be any new pid or parent / child changes.

At least, I think, probably, how I would do it, from my head.

+1
source

Source: https://habr.com/ru/post/1344195/


All Articles